开源项目地址
本文功能已经集成到开源浏览器视频编辑器 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 视频编辑能力。