纯前端 AI 视频特效实践:在浏览器中完成实物识别与动态描边
标题:

在 Timeline Studio 这个项目里,实现了一套完全在浏览器本地运行的实物描边功能。它能识别商品、鞋子、玩具这些物体,生成 Alpha 蒙版,然后在图片或视频里渲染出可调节的描边效果。
项目完整代码已开源,放在 GitHub 上:
https://github.com/MartinDelophy/ai-video-editor
整个过程都在浏览器内完成,不需要把用户素材上传到任何服务器。
整体实现思路
说到实物描边,很多人第一反应是边缘检测,但这招其实不太灵。边缘检测会同时提取人物、背景和纹理,根本没法确定用户到底想处理哪个物体。
所以,项目把处理流程拆成了四个阶段:
目标检测↓
目标身份锁定↓
提示式物体分割↓
描边材质渲染
简单来说,目标检测负责找到画面里有哪些可能的“参赛选手”,提示式分割负责给真正的目标画一个像素级的蒙版。对于视频,还需要通过光流传播和稀疏锚点分析来保持前后帧的稳定。
使用 Worker 执行目标检测
模型推理这事儿,放在 Web Worker 里做,避免阻塞 React 界面和时间线的交互。主线程这边只需要发送待分析画面就行:
worker.postMessage({ requestId,type: "detect",blob,scoreThreshold: 0.24,
});
Worker 在初始化 ONNX Runtime 会话时,会优先尝试 WebGPU,如果不行,就回退到 WASM:
async function createSession(modelBytes) { if (na vigator.gpu) { try {
return await ort.InferenceSession.create(modelBytes, {
executionProviders: ["webgpu"],
graphOptimizationLevel: "all",
}); } catch (error) {
console.warn("WebGPU 初始化失败,回退 WASM"); }}
return ort.InferenceSession.create(modelBytes, { executionProviders: ["wasm"], graphOptimizationLevel: "all",});
}
这样做的好处是,既能充分利用支持 WebGPU 的设备,也能兼容那些普通浏览器环境。
锁定用户真正想处理的物体
检测模型可能会同时返回好几个候选框。项目没有直接选择置信度最高的那个,而是综合计算了多个因素:
- 检测置信度
- 物体面积
- 物体是否靠近画面中心
- 与上一帧目标的重叠程度
- 前后帧类别是否一致
排序代码简化后是这样的:
function calculateRank(item, previous) { const area = getArea(item.box);const centerScore = getCenterScore(item.box);const previousIoU = boxIoU(item.box, previous?.box);const identityBonus = previous?.label === item.label ? 0.5 : 0;
return ( item.score * 0.55 + Math.min(area, 0.25) + centerScore * 0.2 + previousIoU * 1.2 + identityBonus);
}
在视频中,上一帧的目标会被赋予更高的权重,这样就能避免其他物体突然进入画面后发生错误切换。
从检测框生成物体 Alpha
目标确定之后,系统会根据物体的中心生成一个提示点,然后调用提示式分割模型:
const result = segmenter.segment(image, { keypoint: { x: clamp(point.x, 0, 1), y: clamp(point.y, 0, 1),},
});
const mask = result.confidenceMasks.at(-1);
const probabilities = mask.getAsFloat32Array();
置信度结果会转换成 0~255 的 Alpha 值:
const alpha = new Uint8ClampedArray(probabilities.length);
for (let i = 0; i < probabilities.length; i++) { const probability = clamp(probabilities[i], 0, 1);
alpha[i] = probability >= threshold ? Math.round(probability * 255) : 0;
}
得到 Alpha 之后,还得做一个保守的质量检查。如果蒙版出现以下情况,系统就不会直接接受:
- 蒙版越过了目标区域
- 出现了多个明显的连通区域
- 混入了其他物体或背景
- 面积相对上一帧异常变化
- 目标身份发生了跳变
相比之下,修复一个已经错误的蒙版代价太大,直接跳过那些不可靠的帧反而更安全。
视频使用稀疏锚点和光流传播
如果每帧都执行完整的检测和分割,计算开销会非常大。所以视频分析采用了稀疏锚点策略。
const needsAnchor =index === 0 ||index - lastAnchorIndex >= anchorInterval ||!trackedMaskBox;
if (needsAnchor) { currentAlpha = await analyzeObjectAnchor(frame);lastAnchorIndex = index;
} else { currentAlpha = await propagateAlphaWithFlow( previousFrame, currentFrame, currentAlpha,);
}
只有首帧、固定间隔或者目标丢失时,才会重新执行完整的分析。中间帧则通过光流来传播 Alpha。
传播完成后,还会检查面积变化是否合理:
const ratio = nextArea / trackedArea;
if (!nextBox || ratio < 0.35 || ratio > 2.8) { currentAlpha = null;trackedMaskBox = null;
}
一旦传播结果不可信,系统就会重新检测目标,而不是继续传播错误的蒙版。
分离 AI 分析与视觉样式
模型分析只负责生成可复用的 Alpha。描边的颜色、宽度、阴影和材质,这些都属于独立的效果状态:
const objectOutline = { targetKind: "object",outline: { color: "#f3efe4", width: 12, opacity: 1, glow: 0.35,},material: { id: "paper", edgeDensity: 0.5, shadowDepth: 0.32,},
};
用户调整样式时,只需要重新合成画面,不需要再次运行检测和分割模型。
同一份 Alpha 同时用于编辑器预览和最终导出,这样可以保证两条渲染链路的效果完全一致。
总结
浏览器本地实物描边的核心,其实不依赖于某一个模型,而是一整套处理策略:
- Worker 隔离模型推理
- WebGPU 与 WASM 兼容回退
- 检测与提示式分割组合
- 目标身份锁定
- 保守的蒙版质量门槛
- 稀疏锚点与光流传播
- AI 分析和视觉渲染解耦