从模型 Demo 到剪辑产品:基于 MI-GAN、ONNX 与 WebGPU 实现浏览器本地视频去水印

简介: 本项目在浏览器中实现本地AI视频去水印,基于MI-GAN ONNX模型与WebGPU加速,支持多区域框选、时间范围设定及动态水印关键帧插值。全程无需上传视频,保障隐私,降低服务端成本,修复结果实时预览并保留原始音轨。(239字)

从模型 Demo 到剪辑产品:基于 MI-GAN、ONNX 与 WebGPU 实现浏览器本地视频去水印

摘要

视频去水印通常依赖服务端 GPU:用户上传视频,服务端逐帧处理,再将结果下载回来。这种方案面临上传耗时、隐私顾虑、GPU 成本和高并发排队等问题。

我们在开源视频剪辑器 Timeline Studio 中实现了一套浏览器本地 AI 去水印方案。用户可以框选一个或多个水印区域,设置独立处理时段,并通过位置关键帧处理动态水印。浏览器使用 MI-GAN ONNX 模型和 WebGPU 逐帧修复视频,最后重新编码并保留原始音轨。

整个过程中,图片与视频帧均在用户设备上处理,无需上传服务器。

项目仓库:

https://github.com/MartinDelophy/ai-video-editor

在线体验:

https://video-editor.ai-creator.top/

一、为什么要在浏览器里做视频去水印

传统在线去水印产品一般采用以下流程:

  1. 用户上传完整视频;
  2. 服务端进行视频解码;
  3. GPU 逐帧执行图像修复;
  4. 重新编码视频;
  5. 用户下载结果。

这种方案实现起来比较直接,但在实际产品中存在几个明显问题:

  • 高清视频上传时间较长;
  • 用户可能不愿上传隐私或商业素材;
  • 服务端 GPU 资源成本较高;
  • 视频处理任务容易形成队列;
  • 移动网络环境下上传容易中断;
  • 上传和下载增加了完整处理时长。

WebGPU 和 ONNX Runtime Web 的逐渐成熟,让我们可以把一部分 AI 推理任务迁移到用户设备上。

浏览器本地处理的主要优势是:

  • 素材无需上传;
  • 减少服务端 GPU 成本;
  • 模型缓存后可以重复使用;
  • 处理结果可以直接进入剪辑器;
  • 更适合隐私敏感的图片和视频。

二、项目与技术栈

Timeline Studio 是一个运行在浏览器中的多轨视频剪辑器,支持媒体导入、字幕、AI 配音、音频处理、时间线编辑、视频导出和浏览器本地 AI 能力。

本次去水印功能主要使用:

  • React;
  • Vite;
  • MI-GAN;
  • ONNX Runtime Web;
  • WebGPU;
  • Web Worker;
  • Canvas;
  • ImageBitmap;
  • HTMLVideoElement;
  • FFmpeg WASM;
  • Blob 与 Object URL。

本地启动项目:

git clone https://github.com/MartinDelophy/ai-video-editor.git
cd ai-video-editor

npm install
npm run dev

浏览器 AI 推理依赖 WebGPU,建议使用较新版本的 Chrome 或 Edge。

三、整体架构

整个视频去水印过程分为以下阶段:

  1. 用户导入图片或视频;
  2. 在独立工作区中框选修复区域;
  3. 为每个区域设置开始时间和结束时间;
  4. 隐藏视频元素按时间逐帧解码;
  5. 将视频帧转换为 ImageBitmap
  6. 根据区域生成模型遮罩;
  7. Web Worker 调用 MI-GAN ONNX 模型;
  8. WebGPU 完成局部图像修复;
  9. 将修复结果融合回完整画面;
  10. 同步更新修复前后预览;
  11. 将全部结果帧重新编码;
  12. 合并原始音轨;
  13. 生成新素材并加入剪辑器。

模型推理和媒体上传完全解耦。视频帧只在浏览器内存、本地 Blob 和 Worker 之间传递。

四、为什么选择 MI-GAN

去水印并不是简单地删除一块像素。

水印遮挡了原始画面,系统需要根据周围内容推测被遮挡区域原本应该是什么。例如:

  • 人脸皮肤;
  • 头发纹理;
  • 天空和云层;
  • 建筑墙面;
  • 衣服纹理;
  • 草地和水面;
  • 背景光影。

这类任务属于图像修复,也就是 Image Inpainting。

部分图像修复模型虽然效果很好,但权重较大,推理速度也比较慢,更适合运行在服务端 GPU 上。

我们选择 MI-GAN,主要考虑以下因素:

  • 模型相对轻量;
  • 局部区域修复速度较快;
  • 适合矩形遮罩;
  • 可以使用 ONNX Runtime Web;
  • WebGPU 后端具备较好的浏览器运行能力;
  • 比较适合角标、水印和局部对象移除。

为了避免模型运行阻塞 React 主线程,我们将推理放在 Web Worker 中执行。

主线程负责交互和媒体显示,Worker 负责:

  • 模型加载;
  • Tensor 构造;
  • WebGPU Session 初始化;
  • 图像预处理;
  • MI-GAN 推理;
  • 输出像素转换;
  • 结果 Blob 生成。

五、修复区域使用相对坐标

用户在预览画面中框选水印时,我们不会直接保存屏幕像素,而是保存相对坐标:

const selection = {
   
  x: 0.78,
  y: 0.88,
  width: 0.2,
  height: 0.09,
};

这些值表示区域相对于完整画面的比例。

在模型处理时,再映射到真实视频帧:

const pixelRegion = {
   
  x: Math.round(selection.x * frameWidth),
  y: Math.round(selection.y * frameHeight),
  width: Math.round(selection.width * frameWidth),
  height: Math.round(selection.height * frameHeight),
};

这种方式可以保证:

  • 桌面端和移动端配置一致;
  • 预览缩放不会影响修复范围;
  • 横屏、竖屏和方形视频都能正确映射;
  • 大画布和普通画布可以共享区域数据;
  • 视频导出时仍能使用原始分辨率。

六、视频区域需要独立时间范围

图片只需要修复一次,但视频中的水印可能只在某个时间段出现。

因此,每个视频修复区域都拥有独立的开始时间和结束时间:

const repairRegion = {
   
  id: "repair-region-1",
  start: 0,
  end: 4,
  selection: {
   
    x: 0.78,
    y: 0.88,
    width: 0.2,
    height: 0.09,
  },
  keyframes: [],
};

处理某一个视频帧时,根据当前时间筛选有效区域:

const activeRegions = repairRegions.filter((region) => {
   
  return (
    currentTime >= region.start &&
    currentTime <= region.end
  );
});

如果当前帧没有命中任何区域,就直接保留原始帧。

如果命中了一个或多个区域,则调用 MI-GAN 进行修复。

这样可以避免对整个视频无差别运行模型,减少不必要的计算。

七、支持多个水印区域

实际视频中可能同时包含多个需要移除的内容:

  • 左上角平台 Logo;
  • 右下角用户名;
  • 底部 AI 生成标记;
  • 视频日期;
  • 摄像机时间;
  • 其他小面积对象。

因此,图片和视频都支持多个区域。

当同一帧命中多个区域时,我们会依次进行修复:

let bitmap = originalBitmap;

for (const region of activeRegions) {
   
  const result = await repairMiganFrame({
   
    bitmap,
    selection: region.selection,
    signal,
  });

  bitmap.close();
  bitmap = await createImageBitmap(result.blob);
}

后一个区域会基于前一个区域的输出继续处理。

这样最终得到的是一张已经完成全部区域修复的视频帧。

八、动态水印如何处理

时间范围只能解决水印什么时候出现,却不能解决水印移动的问题。

例如:

  • 前三秒在左下角;
  • 中间移动到顶部;
  • 最后出现在右下角。

如果只使用一个固定矩形,模型就无法持续覆盖水印。

我们为每个修复区域增加了位置关键帧。

用户在不同时间点移动修复框,并记录当前位置:

const positionKeyframe = {
   
  time: 2.5,
  selection: {
   
    x: 0.12,
    y: 0.82,
    width: 0.2,
    height: 0.08,
  },
};

处理两个关键帧之间的视频帧时,系统自动插值:

function interpolateSelection(before, after, currentTime) {
   
  const duration = Math.max(
    0.001,
    after.time - before.time,
  );

  const ratio =
    (currentTime - before.time) / duration;

  const keys = ["x", "y", "width", "height"];

  return Object.fromEntries(
    keys.map((key) => [
      key,
      before.selection[key] +
        (after.selection[key] -
          before.selection[key]) *
          ratio,
    ]),
  );
}

用户不需要逐帧调整区域,只需要记录几个关键位置。

九、为什么需要隐藏视频元素

早期实现直接使用弹窗里可见的视频元素提取帧。

处理器会不断修改:

video.currentTime = targetTime;

这会带来一个问题:

左侧原始画面在模型推理开始前就已经跳到了下一帧,而右侧修复结果要等模型完成后才出现。

对比线左右因此不是同一个时间点。

最终我们将计算视频与展示视频分离。

隐藏视频负责:

  • 跳转到目标时间;
  • 等待 seeked
  • 提取原始帧;
  • 生成 ImageBitmap
  • 为模型提供输入。

可见视频只负责显示已经完成的帧。

隐藏视频的逐帧读取逻辑类似:

async function seekVideoFrame(video, time, signal) {
   
  if (signal.aborted) {
   
    throw new DOMException(
      "Canceled",
      "AbortError",
    );
  }

  const target = Math.max(
    0,
    Math.min(video.duration - 0.001, time),
  );

  if (
    Math.abs(video.currentTime - target) < 0.0005
  ) {
   
    return;
  }

  await new Promise((resolve, reject) => {
   
    const onSeeked = () => {
   
      cleanup();
      resolve();
    };

    const onAbort = () => {
   
      cleanup();
      reject(
        new DOMException(
          "Canceled",
          "AbortError",
        ),
      );
    };

    const cleanup = () => {
   
      video.removeEventListener(
        "seeked",
        onSeeked,
      );

      signal.removeEventListener(
        "abort",
        onAbort,
      );
    };

    video.addEventListener(
      "seeked",
      onSeeked,
      {
    once: true },
    );

    signal.addEventListener(
      "abort",
      onAbort,
      {
    once: true },
    );

    video.currentTime = target;
  });
}

跳转完成后,再读取当前视频帧:

await seekVideoFrame(
  hiddenVideo,
  sourceTime,
  signal,
);

const bitmap =
  await createImageBitmap(hiddenVideo);

十、修复前后拖动对比

用户仅仅看到修复结果,很难判断模型究竟改动了什么。

因此,我们实现了可以左右拖动的对比线:

  • 左侧显示原始帧;
  • 右侧显示修复帧;
  • 用户可以拖动竖线检查局部差异;
  • 图片和视频使用相同的交互。

修复结果覆盖在原始画面上,再通过 clip-path 控制可见区域:

.repair-compare-result {
   
  position: absolute;
  inset: 0;
  clip-path: inset(
    0
    0
    0
    var(--compare-position)
  );
}

这种方式可以保证两边拥有相同的尺寸、位置、缩放和纵横比。

视频处理完成后,还需要同步原视频和修复视频的:

  • currentTime
  • playbackRate
  • 播放;
  • 暂停;
  • 时间轴跳转。

十一、解决画面与进度不同步

逐帧修复最容易被忽略的问题,是界面上存在多个不同的“进度”。

它们包括:

  • 模型下载进度;
  • WebGPU 初始化进度;
  • 当前帧推理进度;
  • 已完成帧数量;
  • PNG 解码进度;
  • 最终视频编码进度。

如果把这些进度混合为一个百分比,就会出现:

  • 进度条已经向前;
  • 画面还是上一帧;
  • 播放头没有变化;
  • 无修复区域的帧不计入进度;
  • 最后突然跳到 91% 或 92%。

我们的解决方案是使用“已提交帧”作为唯一的视频进度时钟。

每一帧的顺序必须是:

  1. 完成 MI-GAN 推理;
  2. 生成结果 Blob;
  3. 完成结果图片解码;
  4. 更新左右画面;
  5. 更新时间和播放头;
  6. 更新已完成帧百分比。

核心逻辑如下:

await commitVisibleFrame({
   
  blob: resultBlob,
  index,
  time: timelineTime,
});

reportCompletedFrame(index);

不需要修复的帧也必须推进进度:

if (!activeRegions.length) {
   
  const originalFrame =
    await captureOriginalFrame(video);

  await commitVisibleFrame({
   
    blob: originalFrame,
    index,
    time: timelineTime,
    repaired: false,
  });

  reportCompletedFrame(index);
  return originalFrame;
}

模型下载和 WebGPU 初始化只更新当前阶段名称,不再提前推动视频帧百分比。

十二、为什么要等待结果图片解码

MI-GAN 的结果会被转换为 PNG Blob。

创建 Object URL 并设置到 <img> 后,浏览器仍可能需要时间解码图片。

如果立即推进播放头和进度,就可能出现:

  • 时间已经变化;
  • 进度已经增加;
  • 右侧仍显示上一帧。

因此,我们先等待图片解码:

const url =
  URL.createObjectURL(frameBlob);

const decodedImage = new Image();
decodedImage.src = url;

try {
   
  await decodedImage.decode();
} catch {
   
  // 某些浏览器不支持 decode,
  // 仍可继续使用 Object URL。
}

解码完成后再提交 UI:

const previousUrl =
  previewRef.current?.url;

previewRef.current = {
   
  blob: frameBlob,
  url,
  time: timelineTime,
};

setPreview(previewRef.current);

await new Promise((resolve) => {
   
  requestAnimationFrame(resolve);
});

if (previousUrl) {
   
  URL.revokeObjectURL(previousUrl);
}

旧帧要在新帧可见以后再释放,否则容易产生闪烁。

十三、视频重新编码与音轨保留

全部帧处理完成后,需要把 PNG 帧序列重新编码成视频。

编码时还需要保留:

  • 原始视频时长;
  • 原始音轨;
  • 播放速度;
  • 视频宽高;
  • 帧率;
  • 时间轴范围。

编码阶段与逐帧推理阶段应当分开显示。

我们最终采用以下阶段:

  1. 准备视频;
  2. 下载 MI-GAN 模型;
  3. 初始化 WebGPU;
  4. 逐帧修复;
  5. 准备视频合成器;
  6. 合成修复视频;
  7. 创建修复素材;
  8. 完成。

最终输出为 MP4 Blob,并作为新素材加入剪辑器。

原始素材不会被直接覆盖,只有用户点击“应用到视频”后,结果才会替换当前时间线片段。

十四、任务取消与资源回收

长视频处理必须支持真正的取消。

取消不能只是关闭弹窗,而是需要停止:

  • 当前模型推理;
  • 后续帧循环;
  • 视频跳转;
  • Canvas 截图;
  • PNG 解码;
  • 视频编码;
  • 音轨处理;
  • Worker 请求;
  • 临时 Object URL。

我们使用 AbortController 贯穿完整处理链路:

const controller =
  new AbortController();

await repairVideo({
   
  signal: controller.signal,
});

function cancelRepair() {
   
  controller.abort();
}

取消后不会:

  • 自动应用半成品;
  • 替换当前视频;
  • 下载不完整文件;
  • 继续在后台处理剩余帧。

同时需要释放:

  • ImageBitmap
  • 临时视频元素;
  • Object URL;
  • Worker 请求;
  • 编码器资源。

十五、产品交互设计

AI 去水印涉及的配置较多,如果全部塞进右侧属性面板,会让剪辑器显得非常拥挤。

我们采用了“能力入口 + 独立工作区”的方式。

右侧属性面板只展示:

  • 去水印 / 对象移除;
  • 高清修复。

点击后进入独立工作区,完成:

  • 框选区域;
  • 添加多个区域;
  • 设置时间范围;
  • 记录位置关键帧;
  • 单帧测试;
  • 逐帧修复;
  • 撤销与重做;
  • 前后对比;
  • 应用或取消。

这种设计也更适合移动端。桌面端使用大弹窗,移动端则切换为全屏工作区。

十六、浏览器本地方案的边界

浏览器本地去水印并不能覆盖所有情况。

它更适合:

  • 平台角标;
  • 用户名水印;
  • AI 生成标记;
  • 日期和时间;
  • 小面积文字;
  • 局部静态对象;
  • 缓慢移动的水印。

以下情况难度更高:

  • 大面积遮挡人脸;
  • 快速运动的水印;
  • 遮挡复杂文字;
  • 背景纹理变化剧烈;
  • 超高分辨率长视频;
  • 设备没有 WebGPU;
  • 浏览器内存不足。

后续可以继续探索:

  • 光流跟踪;
  • 视频时序一致性模型;
  • 前后帧融合;
  • 分块推理;
  • 更强的修复模型;
  • WebCodecs 编码;
  • 浏览器本地与云端混合处理。

十七、总结

从一个 MI-GAN 模型 Demo 到剪辑软件里的去水印功能,中间还有大量工程问题需要解决。

除了模型本身,我们还需要处理:

  • WebGPU 推理;
  • Worker 隔离;
  • 多区域遮罩;
  • 区域时间范围;
  • 动态位置关键帧;
  • 视频逐帧解码;
  • 修复前后同步;
  • 画面与进度同步;
  • PNG 解码;
  • Blob URL 回收;
  • 视频重新编码;
  • 原始音轨保留;
  • 任务取消;
  • 多语言;
  • 移动端交互。

最终实现的完整链路是:

  1. 用户框选水印;
  2. 设置生效时间和位置关键帧;
  3. 隐藏视频逐帧解码;
  4. MI-GAN 在 WebGPU 上修复;
  5. 左右画面、播放头和进度同步更新;
  6. 重新编码并保留音轨;
  7. 结果加入剪辑器素材库;
  8. 用户确认后应用到当前视频。

浏览器已经不再只是 AI 服务的调用入口。

随着 WebGPU、ONNX Runtime Web、WebCodecs 和浏览器媒体能力不断成熟,越来越多过去依赖服务端 GPU 的 AI 视频能力,可以开始迁移到用户本地设备。

项目地址

GitHub:

https://github.com/MartinDelophy/ai-video-editor

在线体验:

https://video-editor.ai-creator.top/

如果项目对你有帮助,欢迎在 GitHub 点 Star、提交 Issue 或参与功能改进。

相关文章
|
2月前
|
人工智能 IDE 开发工具
【新版】阿里云 Qoder CN 功能介绍及配置价格表
阿里云Qoder CN(原通义灵码)是阿里云面向国内开发者与企业推出的AI智能编码助手,定位为覆盖软件开发全流程的AI智能体产品系列,以“端到端工程交付、多模型自由切换、多形态无缝接入”为核心优势,为个人开发者、技术团队与企业提供从代码补全、智能问答到工程级任务自动执行的一站式AI编程服务。Qoder CN彻底颠覆传统编码模式,无需复杂环境配置,通过IDE插件、独立IDE、CLI等多种形态,让开发者以自然语言驱动代码生成、调试、重构与部署,大幅提升研发效率、降低技术门槛。本文将全面解析Qoder CN的核心功能、技术架构、使用流程、配置价格及API接入方法,助力用户高效掌握并应用该平台。
605 0
|
2月前
|
缓存 人工智能 API
CLAUDE.md 不只是写规则,写法本身决定你的 token 账单
CLAUDE.md 不只是项目说明文档,它被注入到每次请求的缓存链条里,排在历史对话之前。Anthropic 的 Prompt Cache 用精确前缀匹配:内容不变才能按 10% 价格命中缓存,哪怕改一个字,后面所有历史对话都要按 125% 重新计算。会话越长,中途改动的代价越大。文章从这个机制出发,讲清楚"CLAUDE.md 要简洁""别在会话中途改""别写临时易变内容"这些常见建议背后的真实原因,并给出通过 API 返回的 usage 字段验证缓存是否真正生效的方法。
|
2月前
|
人工智能 BI API
阿里云Qwen3.8-Max-Preview介绍:核心能力、适用场景、支持订阅计划与最新活动
阿里云于2026年7月19日发布Qwen3.8-Max-Preview,为通义千问首个2.4T参数原生多模态旗舰模型,采用第三代MoE架构,支持百万Token上下文、全栈代码工程、原生多模态处理及多智能体协作,较Qwen3.7-Max全面跃升。模型处于"日更进化"预览阶段,现推出限时优惠:常规时段1折、夜间0.2折(22:00-次日08:00),适用于Qoder CN全系产品。个人版Token Plan低至39元/月,团队版150元/席位/月起,配合新用户25元体验包及14天Pro Trial,是开发者与企业低成本体验顶级大模型的绝佳窗口。
|
1月前
|
人工智能 定位技术 API
高德汽车业务 AI Native 工程实践|基于 Qoder 的业务知识工程建设实践
高德企业业务通过 Qoder 知识引擎构建业务知识的"生产—调优—更新—消费"体系,同一类错误不再发生第二次,任务一次性通过率从 37.3% 提升至 61.5%。
287 0
高德汽车业务 AI Native 工程实践|基于 Qoder 的业务知识工程建设实践
|
2月前
|
数据采集 人工智能 安全
GEO
从现状诊断到动态迭代,综合六步法构建品牌在AI搜索中的可见度资产,覆盖关键词定位、知识图谱、内容创作、多模态分发与效果监测。
|
1月前
|
人工智能 安全 语音技术
当语言被困住:我们和 Qoder 跑通了一条有尊严的表达链路
MeantByMe是一款由Qoder赋能的语音补全产品,专为失语患者设计:实时补全含糊话语,经用户确认后播报,守护表达权与尊严。五天内,三人小队借助Qoder知识引擎与多Agent协作,高效完成全栈开发。
164 0
当语言被困住:我们和 Qoder 跑通了一条有尊严的表达链路
|
2月前
|
存储 弹性计算 运维
高寒野外场景下,专网通信系统云端部署与弱网适配优化实践
本文针对高寒林区、工矿、边境等野外场景中低温设备故障多、弱网抖动频、跨域调度不稳等痛点,提出基于阿里云的专网融合调度系统云端迁移方案,实现弹性扩容、弱网优化、低温适配与远程运维,为寒地专网通信数字化上云提供可落地的工程实践。
|
2月前
|
人工智能 自然语言处理 搜索推荐
企业如何用好智能客服系统?2026年真实案例拆解
智能客服的成败,三分靠选型,七分靠运营。2026年,超92%的企业已部署AI客服,但仅35%真正跑出了效能——差距不在"有没有",而在"会不会用"。本文拆解星巴克、长城汽车、东风猛士等企业的真实落地路径,提炼出一套"选对平台→分阶段落地→持续运营"的三步闭环方法论。
|
2月前
|
存储 人工智能 前端开发
为浏览器音视频编辑器开发 Agent Skill,实现 AI 自动化剪辑自测
本文介绍基于离线AI剪辑工具Timeline Studio构建的Agent Skill规范体系,通过开源技能库`edit-timeline-studio`,实现AI对前端编辑器的标准化UI操作、异常处理与全链路自动化测试,推动产品鲁棒性提升
|
2月前
|
人工智能 安全 IDE
最新版阿里云 Qoder CN 服务平台 功能介绍
在软件开发从单体编码向工程化、智能化快速演进的当下,开发者与团队迫切需要一款**深度融入开发流程、支持全场景工程任务、安全合规、成本可控**的AI编码助手。阿里云Qoder CN(原通义灵码)正是为此打造的全栈AI编码智能体服务平台,它以**工程级上下文感知**为核心,融合代码补全、智能问答、文件编辑、自主编程等全链路能力,支持IDE、CLI、JetBrains/VS Code插件三端部署,无缝对接阿里云百炼平台的国产大模型矩阵。Qoder CN不仅提供个人轻量化使用方案,更具备企业级安全管控、私域知识增强与VPC部署能力,完美适配个人开发者、中小团队与大型企业的不同开发需求。本文将从产品定位
387 0

热门文章

最新文章