在浏览器中实现视频智能降噪:DRUNet、ONNX Runtime Web 与 WebCodecs 的工程实践

简介: Timeline Studio 是一款开源浏览器端视频编辑器,集成本地AI智能降噪(基于DRUNet+WebGPU)、AI配音、字幕生成、视频修复等功能。全程离线处理,无需上传素材,保障隐私安全。支持MP4流式编码与多级降噪模式,兼顾细节保留与性能优化。

开源项目地址

本文功能已经集成到开源浏览器视频编辑器 Timeline Studio:

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

项目支持浏览器本地视频剪辑、AI 配音、智能降噪、视频修复、字幕、音频处理和视频导出。如果这套实现对你有帮助,欢迎前往 GitHub 体验、提交 Issue 或点一个 Star。


随着短视频、直播录屏和移动端拍摄内容越来越多,暗光噪点、彩色噪点和压缩伪影已经成为视频后期处理中非常常见的问题。

传统视频降噪通常依赖桌面软件或云端 GPU 服务。前者需要安装专业工具,后者需要上传素材,可能产生带宽、计算费用和隐私风险。

我们在 Timeline Studio 中实现了一套浏览器本地视频智能降噪能力:使用 DRUNet 进行单帧图像降噪,通过 ONNX Runtime Web 调用 WebGPU,并结合 WebCodecs 构建完整的视频解码、推理、编码和音频封装管线。

整个处理过程都在用户浏览器中完成,视频素材无需上传服务器。

一、功能目标

这项能力需要满足以下要求:

  • 自动识别细颗粒噪点和彩色噪点;
  • 尽量保留人物五官、字幕、产品轮廓和服装纹理;
  • 支持当前帧前后对比;
  • 提供自动、轻度、均衡和强力四种模式;
  • 处理完整视频时保留源帧率和源音频;
  • 结果生成新素材,不覆盖原始文件;
  • 模型推理、视频解码和编码全部在浏览器本地完成。

二、整体技术架构

完整处理流程如下:

视频文件
  ↓
WebCodecs / Mediabunny 解码
  ↓
RGBA 像素缓冲区
  ↓
DRUNet ONNX 推理
  ↓
根据降噪强度混合原始画面和模型结果
  ↓
WebCodecs H.264 编码
  ↓
AAC 音频重新封装
  ↓
生成新的 MP4 素材

核心技术包括:

  • DRUNet:负责图像噪声抑制和细节恢复;
  • ONNX Runtime Web:在浏览器中运行 ONNX 模型;
  • WebGPU:优先使用 GPU 完成神经网络推理;
  • WASM:作为不支持 WebGPU 环境的兼容回退;
  • WebCodecs:高效解码和编码视频帧;
  • Mediabunny:处理媒体轨道、时间戳和 MP4 封装。

三、为什么选择 DRUNet

DRUNet 是一种面向图像恢复任务的卷积神经网络。相比简单的高斯模糊或中值滤波,它能够更好地区分真实纹理与随机噪声。

它比较适合处理:

  • 暗光环境中的亮度噪点;
  • 相机传感器产生的彩色噪点;
  • 视频压缩形成的细碎伪影;
  • 人像皮肤和背景暗部中的颗粒感。

浏览器端使用 FP16 输入和输出,从而降低显存占用以及 CPU 与 GPU 之间的数据传输量。

const inputTensor = new ort.Tensor(
  "float16",
  tensorData,
  [1, 3, height, width]
);

const output = await session.run({
   
  input: inputTensor
});

为避免产生过度磨皮的效果,我们没有直接用模型结果完全替换原始画面,而是根据用户选择的强度进行混合:

result = original + (denoised - original) * strength;

这样能够在降噪强度和纹理保留之间取得更自然的平衡。

四、持续复用模型和 WebGPU Session

视频可能包含数百甚至数千帧。如果每处理一帧都重新加载模型和创建 WebGPU Session,初始化成本会远高于真正的推理时间。

因此,我们将 Session 保存在长期运行的 Worker 中:

let sessionPromise = null;

async function getSession() {
   
  if (!sessionPromise) {
   
    sessionPromise = ort.InferenceSession.create(modelData, {
   
      executionProviders: [{
   
        name: "webgpu",
        preferredLayout: "NCHW"
      }],
      graphOptimizationLevel: "all"
    });
  }

  return sessionPromise;
}

模型只在第一次使用时下载和初始化。之后无论是预览当前帧,还是处理其他视频片段,都会复用同一个 Session。

WebGPU 初始化失败时,系统会自动回退到 WASM:

try {
   
  return await createWebGpuSession();
} catch {
   
  return await createWasmSession();
}

这既能利用高性能设备的 GPU,也能兼容更多浏览器环境。

五、让下一帧解码与当前帧推理并行

最初的视频处理流程是完全串行的:

解码第 N 帧
→ 推理第 N 帧
→ 编码第 N 帧
→ 解码第 N+1 帧

这种方式会让 GPU 在等待下一帧解码时处于空闲状态。

重构后的管线会在当前帧进入推理前,立即请求下一帧:

let nextDecoded = iterator.next();

for (let index = 0; index < totalFrames; index += 1) {
   
  const decoded = await nextDecoded;
  const bitmap = await createImageBitmap(decoded.value.canvas);

  // 提前请求下一帧,让解码与当前推理重叠
  nextDecoded = iterator.next();

  const result = await enhanceFrame(bitmap);
  await videoSource.add(
    index * frameDuration,
    frameDuration
  );
}

这样可以让视频解码、GPU 推理和视频编码形成更加连续的流水线,减少不同处理阶段之间的等待。

六、复用画布、张量和像素缓冲区

视频处理中另一个常见的性能问题,是每一帧都创建新的 Canvas、ImageData、Tensor 和 RGBA 数组。

这些对象的体积通常很大。频繁分配会增加垃圾回收压力,处理长视频时可能出现明显卡顿。

我们按照推理分辨率创建固定缓冲池:

const framePool = {
   
  pixels: new Uint8ClampedArray(width * height * 4),
  tensorData: new Uint16Array(width * height * 3),
  enhanced: new Uint8ClampedArray(width * height * 4),
  inputCanvas: new OffscreenCanvas(width, height),
  outputCanvas: new OffscreenCanvas(width, height)
};

Tensor 直接绑定到常驻数组:

framePool.tensor = new ort.Tensor(
  "float16",
  framePool.tensorData,
  [1, 3, height, width]
);

后续帧只更新数组内容,不再重复创建相同尺寸的对象。

浏览器支持时,我们优先通过 VideoFrame.copyTo() 将像素直接写入预分配的 RGBA 缓冲区;不支持时再回退到 Canvas 的 getImageData()

七、低变化相邻帧复用降噪残差

固定机位、采访、课程录制和产品展示视频中,经常存在大量变化很小的相邻帧。

如果每一帧都执行完整神经网络推理,会产生不少重复计算。

完整推理后,我们保存原始帧和增强帧之间的降噪残差:

降噪残差 = 增强帧 - 原始帧

当下一帧变化低于安全阈值时,可以复用该残差:

当前增强帧 = 当前原始帧 + 上一帧降噪残差

需要注意的是,残差复用不是抽帧。

每个视频时间点仍然会生成并编码一张完整画面,因此输出视频继续保持源帧率。

为了避免运动拖影,我们采用了较保守的变化判断。遇到人物移动、镜头切换或较明显的局部变化时,系统会自动重新执行 DRUNet 推理。

八、使用 WebCodecs 进行流式编码

早期方案会把每一帧保存成 PNG,最后再交给 FFmpeg 编码。

这种方案兼容性较好,但存在一些问题:

  • PNG 编码和解码会消耗额外 CPU;
  • 所有帧可能同时占用大量内存;
  • 长视频会生成较大的临时数据;
  • 必须等待帧序列完成后才能进入最终编码。

重构后,降噪结果直接写入 CanvasSource,随后通过 WebCodecs 编码为 H.264:

const videoSource = new CanvasSource(canvas, {
   
  codec: "avc",
  bitrate,
  keyFrameInterval: 2,
  latencyMode: "realtime"
});

await videoSource.add(
  timestamp,
  frameDuration
);

音频轨道会独立解码为 AudioBuffer,再使用 AAC 编码并写入同一个 MP4 文件。

现在整个处理过程是流式的,不再需要在内存中积累完整 PNG 序列。

如果浏览器不支持 WebCodecs,或者无法解码某种视频格式,系统仍会自动回退到 PNG 与 FFmpeg 兼容管线。

九、本地视频降噪的产品价值

浏览器本地智能降噪不仅是一次模型部署,也改变了视频修复功能的使用方式。

1. 素材不需要上传

包含人物、企业内部录屏、客户资料或未公开产品的视频可以直接在本地处理,降低隐私和合规风险。

2. 支持单帧预览

用户可以移动时间轴,选择代表性画面,然后预览当前帧的降噪结果,不必直接等待整段视频处理完成。

3. 原始素材可以恢复

降噪结果会作为新素材加入编辑器,不会覆盖原文件。用户可以随时切换原始版本和降噪版本。

4. 可以继续编辑和导出

处理完成后,用户仍然可以继续进行剪辑、调色、字幕、配音、音频处理和视频导出。

十、总结

在浏览器中实现视频智能降噪,真正困难的部分并不只是让 ONNX 模型运行起来,而是建立一条能够持续处理完整视频的工程管线。

本次实践主要完成了以下工作:

  • 使用 DRUNet 和 ONNX Runtime Web 实现本地模型推理;
  • 优先使用高性能 WebGPU,并提供 WASM 回退;
  • 持续复用模型和 WebGPU Session;
  • 让下一帧解码与当前帧推理并行;
  • 复用 Canvas、Tensor 和像素缓冲区;
  • 对低变化相邻帧安全复用降噪残差;
  • 使用 WebCodecs 流式完成 H.264 和 AAC 编码;
  • 保留源帧率、源音频和可恢复的原始素材。

WebGPU、WebCodecs、WASM 与 ONNX Runtime Web 的组合,让浏览器不再只是视频编辑器的界面层。它已经开始具备承担完整视频 AI 工作流的能力。

如果你想查看完整实现或体验项目,可以访问:

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

欢迎 Star、Fork、提交 Issue,也欢迎一起完善浏览器本地 AI 视频编辑能力。

相关文章
人工智能 缓存 前端开发
12023 63
人工智能 JavaScript 开发工具
4805 17
Web App开发 人工智能 API
1381 1
人工智能 Java BI
1467 1
开发工具 Swift git
1972 6
人工智能 JavaScript 测试技术
2402 2
人工智能 自然语言处理 安全
980 0
人工智能 JavaScript 测试技术
1199 4
缓存 JavaScript Shell
2099 3