想象你正在看一场直播:主播小美站在乱糟糟的卧室前,但观众看到的却是干净的海边或高级会议室。这背后没有绿幕,而是一位“舞台导演”老张拿着一把“智能剪刀”在干活——第一步,快速扫一眼画面,把主播的身体轮廓剪成一张透明卡片;第二步,把卡片边缘的毛刺砂平;第三步,把卡片贴在镜头上,卡片内的人像清晰,卡片外的背景则被虚化或换成另一张海报。整个过程每秒重复二三十次,观众才感觉不到卡顿。本文要讲的,就是在浏览器里如何用 AI 完成这套“剪卡片、磨边、换背景”的功夫。
掌握浏览器端实时背景虚化与替换,你就能为视频会议、直播、在线教育等产品加入端侧 AI 虚拟背景能力,成为“前端 + AI”复合型工程师。
可视化演示
可视化展示人像分割模型推理、掩码平滑与帧合成管线、以及实时性能优化策略,交互调节参数体验背景虚化与替换效果。
本节配有交互动效(进入视口后自动加载,约 3KB)。
人像分割模型推理 MediaPipe/U2Net分割人像前景 逐像素二分类生成前景掩码 mask = sigmoid(model(frame)) 推理延迟决定实时性下限
视频处理链路
老张的直播间里,最忙的不是主播,而是那条从摄像头到屏幕的“传送带”。摄像头每秒钟吐出一批照片,后台不能所有事一起做;先采集、再推理、最后合成,每一步都要在规定时间内完成,观众才不会看到画面一卡一卡。
浏览器里,这条传送带由三个环节接力:getUserMedia() 负责从摄像头取流,requestVideoFrameCallback 负责卡着每一帧的节奏按门铃,最终通过 Canvas 或 WebGL 把结果画出来。帧一到就被转成 VideoFrame,接着缩放到适合模型的小尺寸作为推理输入,推理返回的掩码再与原帧一起进入合成阶段。
这四段时间是串行相加的,任意一段超时都会直接压低帧率。所以工程上第一步不是调模型,而是把整条链路的时间拆开看。
老张把传送带的每一环都贴在墙上,哪一环慢了就优化哪一环。浏览器端做实时特效也是一样的思路:先画链路图,再谈优化。
- 总耗时一帧从摄像头到屏幕所需的全部处理时间。
- 采集耗时摄像头把光信号变成数字帧并送到内存的时间。
- 推理耗时分割模型对输入帧计算概率图的时间。
- 平滑与合成耗时掩码处理和背景合成都属于“后期制作”,同样占时间。
人像分割模型
老张的“智能剪刀”其实是一个像素画师。它不画人脸框,而是给画面里的每一粒像素打分:像人的地方打 1,不像人的地方打 0,边缘处打 0.5 这样的中间分。最后这些分数拼在一起,就是一张“人像概率图”。
浏览器端常使用轻量分割网络,比如以 MobileNet 为骨干的自拍分割模型。它先读入一帧缩小后的图片,内部算出一个叫 logits 的原始分数,再经过 sigmoid 函数压到 0 到 1 之间。为了实时,不能直接吃 4K 原图,通常先把帧缩到 256×256 或 512×512,得到概率图后再放大回原图尺寸。
小裁缝不必逐帧看 4K 的每个毛孔,只看一张低分辨率小样就能把轮廓圈出来,速度自然快很多。这就是低分辨率推理加概率图输出的价值。
- 原始分数 \(z\)模型对每个像素打出的原始响应,可能是正数也可能是负数。
- 指数项 \(e^{-z}\)当 \(z\) 很大时,这一项趋近 0,概率接近 1。
- 分母 \(1 + e^{-z}\)让整个分数被限制在 0 到 1 之间。
- 概率 \(P\)最终输出代表“这个像素属于前景人物”的概率。
掩码平滑
刚剪出来的卡纸边缘常带着毛刺,胸口甚至有几个小洞。老张的台子上有一个“打磨台”:先用砂纸把边磨平,再用“记忆布”把上一帧的卡纸叠在当前帧上,防止轮廓来回抖。
模型直接输出的软掩码不是一刀切的 0 或 1,边缘通常是 0.3、0.7 这样的渐变值。高斯模糊负责羽化边缘,形态学腐蚀让轮廓往内收一点,避免背景颜色渗进人物,闭运算则用来填补空洞。除此之外,还要做时空滤波:当前帧掩码和上一帧掩码按比例混合,抑制单帧抖动。
老张把砂纸和记忆布放在固定位置,每帧都来一遍,观众的肉眼就不容易察觉轮廓在发抖。软掩码加时空滤波,是“边缘干净”的关键。
- 当前帧掩码 \(M_{\text{cur}}\)模型刚输出的本帧概率图。
- 上一帧掩码 \(M_{t-1}\)上一帧保留下来的人像概率图。
- 权重 \(\beta\)表示更相信当前帧还是更相信历史信息,一般取 0.5~0.8。
- 加权求和把两帧信息按权重相加,得到更稳定的新掩码。
背景合成
剪影卡片打磨好了,接下来是“叠片”:前台投影仪把主播原片打到幕布上,幕后用手电筒把背景海报的亮度调高或调低。老张手里握着的旋钮叫 \(\alpha\),越靠近 1,前景越实;越靠近 0,背景越明显。
合成阶段读入两路信号:原帧作为前景,虚化后的帧或另一张图片作为背景。逐像素混合公式是 out = α·fg + (1−α)·bg。在代码里,Canvas 的 drawImage 加混合模式或 WebGL 着色器都可以实现;背景虚化常用 ctx.filter = 'blur(...)',海报替换则直接加载图片或视频纹理。合成后还可以对前景做一次轻微锐化,就像给主播补一道轮廓光。
老张在调光台上把“前景灯光”和“背景灯光”分开调,这个公式就是那台双旋钮调光台的核心。
- 混合权重 \(\alpha\)来自平滑后掩码的像素值,范围是 0~1。
- 前景贡献 \(\alpha \times \text{fg}\)人像像素乘以权重,保留清晰的主体。
- 背景贡献 \((1-\alpha) \times \text{bg}\)剩余权重给虚化背景或替换图片。
- 合成结果 \(\text{out}\)前景与背景按权重相加,得到最终画面。
性能优化
如果坚持让小裁缝看 4K 原图,每秒 30 帧的要求会立刻压垮他。精明的导演会让他先看 256×256 的小样,剪完轮廓再把卡纸放大回原图,这就是“低分辨率推理 + 掩码上采样”。
性能优化的核心是减少无效计算。计算成本近似与输入宽高成正比,从 1280×720 缩到 256×256,理论算力会降到原来的约十二分之一。此外,推理可以放进 Web Worker,避免阻塞页面渲染;低端机上还可以动态降到 15FPS,或每隔一帧推理一次。WebGPU/WebGL 则把卷积和矩阵运算交给 GPU,让主线程专心绘制。
老张给后台立了一条规矩:能用小样解决的问题,绝不动原图;能交给工人的活,绝不占用前台时间。性能优化就是把算力花在真正影响观感的地方。
- 计算成本 \(C\)模型处理一帧所需的计算量。
- 宽度 \(w\) 与高度 \(h\)输入图像的宽和高。
- 正比关系 \(\propto\)宽高越大,计算量越大。
- 分辨率换算宽高各减半时,计算量变成原来的四分之一。
工程实践
正式直播前,老张一定要做“彩排”:如果设备太老,舞台灯光自动降级为“只开一盏灯”,而不是让整个直播间黑屏。这就是降级策略。
工程上建议做三级降级:支持 WebGPU/WASM 时,做完整分割加背景替换;只支持 WebGL 时,降低分辨率或改为背景虚化;最差情况直接关闭特效。延迟控制用 performance.now() 统计每帧耗时,超过预算就自动下调分辨率或跳帧。兼容性方面要覆盖 Chrome、Safari、微信内置浏览器,用能力检测决定走哪条管线。上线前还要综合画质和流畅度做体验评估。
老张最后看的不是某一边多惊艳,而是整体观感。产品上线前也要用这个公式评估,而不是只看某台旗舰手机跑得多快。
---
- 画质分边缘自然度、背景替换真实感等主观评分。
- 流畅分帧率、卡顿率、处理延迟等客观指标。
- 权重 \(a\) 与 \(b\)不同产品侧重点不同,会议更看重流畅,直播更看重画面。
- 加权求和把两个维度合并成一个可对比的体验分。
AI 应用场景
在线会议隐私保护
阶段:媒体采集与实时推理
解决的问题:远程办公时家里环境杂乱,直接用摄像头容易泄露隐私。传统做法是手动关摄像头或使用静态图片,体验生硬。AI 人像分割能在端侧实时把人物抠出,背景替换成统一会议室画面,既保护隐私又自然。
具体案例:某 WebRTC 视频会议产品在浏览器端接入人像分割模型,通过 Canvas 合成虚拟背景,端侧完成全部计算,帧延迟控制在 80ms 以内,用户无需安装额外客户端。
直播电商虚拟场景
阶段:实时视频合成
解决的问题:电商主播需要频繁更换直播间主题,实体背景搭建成本高、切换慢。AI 背景替换让主播在普通房间就能一键进入“货架背景”“品牌主视觉”“海边度假”等虚拟场景。
具体案例:一个美妆直播间使用浏览器端背景替换,主播身后的实体货架被实时替换成产品主视觉海报,同时前景人物边缘保持清晰,观众在移动端也能流畅观看。
在线教育虚拟课堂
阶段:视频合成与效果评估
解决的问题:老师在家授课时背景杂乱,分散学生注意力。AI 虚化或替换背景后,学生注意力更集中,也降低了老师布置家庭书房的心理压力。
具体案例:某在线教育平台在网页端加入“虚拟黑板”模式,老师只要出现在画面中,背景就被替换成一块干净的黑板,板书和老师形象叠加显示。
Python 演示
import sysimport cv2import numpy as npimport mediapipe as mp def smooth_mask(mask): mask = cv2.GaussianBlur(mask, (0, 0), sigmaX=7) _, hard = cv2.threshold(mask, 0.1, 1.0, cv2.THRESH_BINARY) hard = cv2.erode(hard, None, iterations=1) return cv2.GaussianBlur(hard, (0, 0), sigmaX=9) def composite(frame, mask, backdrop): mask = mask[..., None].astype(np.float32) return (frame * mask + backdrop * (1 - mask)).astype(np.uint8) def main(): src = sys.argv[1] if len(sys.argv) > 1 else 0 cap = cv2.VideoCapture(src) seg = mp.solutions.selfie_segmentation.SelfieSegmentation(model_selection=1) bg = cv2.imread("background.jpg") while True: ok, frame = cap.read() if not ok: break rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) result = seg.process(rgb) mask = result.segmentation_mask.astype(np.float32) mask = smooth_mask(mask) if bg is None: backdrop = cv2.GaussianBlur(frame, (0, 0), 31) else: backdrop = cv2.resize(bg, (frame.shape[1], frame.shape[0])) out = composite(frame, mask, backdrop) cv2.imshow("Background Blur/Replace", out) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows() if __name__ == "__main__": main()
result = seg.process(rgb)把 RGB 帧交给分割模型,得到人像概率图。mask[..., None]把 H×W 的掩码扩展成 H×W×1,方便与三通道帧相乘。frame * mask + backdrop * (1 - mask)完成前景与背景的 Alpha 混合。cv2.GaussianBlur(hard, (0, 0), sigmaX=9)对掩码边缘做羽化,去掉硬边。src = sys.argv[1] if len(sys.argv) > 1 else 0支持传入视频文件路径,缺省时使用摄像头。
常见误区
误区把模型输入设成 1920×1080,边缘一定更清楚,卡顿忍一忍就过去了。
正解模型输入越大,每帧推理耗时增长越明显,实时性崩了之后观感反而更差。正确做法是低分辨率推理 + 软掩码 + 上采样,把算力花在边缘羽化和合成质量上。
为何常见:看到高分辨率图片更清晰,就误以为分割也一样,忽略了浏览器端每帧处理的实时约束。
误区掩码要一刀切成 0 或 1,边缘才干净。
正解直接把 0.4 的软掩码砍成 0 会产生锯齿和抖动。保留软掩码并做高斯羽化、腐蚀,再交给 Alpha 混合,才能得到自然过渡的边缘。
为何常见:二值化看起来更“确定”,但视频每一帧边缘位置都在变化,硬切会让轮廓闪烁。
误区只要分割模型跑到 30FPS,整个背景特效就一定流畅。
正解整条链路还包括采集、掩码平滑、背景合成和绘制。主线程如果被大量数组遍历阻塞,模型再快也白搭。要用 Web Worker 并行、GPU 合成和控制分辨率。
为何常见:只盯着模型 benchmark,忽略了浏览器端 IO、绘制和线程切换的开销。
误区把分割计算放到 Web Worker,就可以直接拿页面上的 Canvas 画图。
正解Worker 是脱离 DOM 的线程,不能访问页面 Canvas 元素。正确做法是使用 OffscreenCanvas 把绘制任务转移进去,再通过提交操作送回主线程。
为何常见:把 Worker 理解成万能后台线程,忽略了它没有 DOM 访问权限这一基本限制。
面试题
Q1. 人像分割模型在浏览器端主要输出的数据是什么?
- A.一张彩色抠图结果
- B.一组人物关键点坐标
- C.每个像素的前景概率图← 正确答案
- D.一段压缩后的背景码流
答案:C。人像分割属于“语义分割”任务,输出的是每个像素属于前景人物的概率图,而不是人脸框或关键点。有了概率图后,再通过掩码平滑和 Alpha 混合生成最终效果。
Q2. 下列哪种方式最适合在浏览器中逐帧驱动背景特效?
- A.使用定时器高频轮询
- B.使用帧同步回调驱动← 正确答案
- C.使用点击事件逐帧触发
- D.使用网络请求拉取帧
答案:B。requestVideoFrameCallback 是浏览器提供的帧同步回调,能跟摄像头帧率对齐;定时器会漂移,点击触发和网络请求无法满足实时性要求。
Q3. 为兼顾实时性,通常让分割模型处理什么样的输入?
- A.原图 4K 全分辨率
- B.缩小后的视频帧← 正确答案
- C.用户上传的图片
- D.服务器返回的截图
答案:B。模型输入分辨率越大,计算成本越高。浏览器端实时应用通常把输入缩到 256×256 或 512×512,输出概率图后再放大回原图尺寸。
Q4. 在 Alpha 混合公式 out = α×fg + (1−α)×bg 中,α 接近 1 表示什么?
- A.前景完全透出← 正确答案
- B.背景完全透出
- C.画面整体变暗
- D.画面整体变亮
答案:A。α 是前景权重,接近 1 时 α×fg 占主导,背景权重 1−α 接近 0,所以最终画面更像前景。
Q5. 模型输出的掩码边缘有锯齿,最直接的平滑方法是?
- A.提高模型输入分辨率
- B.对掩码做高斯模糊← 正确答案
- C.降低视频处理帧率
- D.增加模型网络层数
答案:B。高斯模糊能对掩码边缘做羽化,让硬边变成渐变过渡。提高分辨率或增加层数只会增加计算量,不能直接解决边缘锯齿。
Q6. 在 Web Worker 中执行分割推理时,主线程不能直接操作什么?
- A.离屏 Canvas 对象
- B.图像位图对象
- C.页面 Canvas 元素← 正确答案
- D.WebGL 上下文
答案:C。Web Worker 没有 DOM 访问权限,不能直接操作页面上的 Canvas 元素;如果要并行绘制,必须使用 OffscreenCanvas 转移绘制任务。
Q7. 当设备不支持 WebGPU 时,合理的降级策略是?
- A.立即停止视频采集
- B.切到 WebGL 低分辨率← 正确答案
- C.弹窗要求升级浏览器
- D.永久关闭全部特效
答案:B。渐进增强和优雅降级是工程惯例。WebGPU 不可用时,可以用 WebGL 或 WASM 推理,并降低输入分辨率,保证基础功能可用。
Q8. 以下哪些方式能提升浏览器端背景替换的实时性能?
- A.降低模型输入分辨率← 正确答案
- B.把推理移入 Web Worker← 正确答案
- C.每帧做全分辨率推理
- D.动态跳过部分帧推理← 正确答案
答案:ABD。低分辨率推理减少计算量,Worker 并行避免阻塞主线程,动态跳帧在低端机上保证流畅。每帧全分辨率推理恰是性能瓶颈。
Q9. 掩码平滑阶段通常包含哪些操作?
- A.高斯模糊羽化边缘← 正确答案
- B.形态学腐蚀与膨胀← 正确答案
- C.时间域滤波抑制闪烁← 正确答案
- D.对视频做 H.264 编码
答案:ABC。高斯模糊让边缘柔软,腐蚀膨胀修复空洞和收边,时间域滤波让掩码不抖动。H.264 编码属于视频压缩,与掩码平滑无关。
Q10. 关于背景合成与工程优化,下列说法正确的有哪些?
- A.只在人物边缘做精细过渡← 正确答案
- B.整帧用 CPU 循环合成
- C.用 GPU 完成背景与前景合成← 正确答案
- D.硬掩码会让边缘更自然
答案:AC。只在掩码边缘做高质量过渡可以节省算力,GPU 合成能显著提升吞吐。整帧 CPU 循环效率低,硬掩码会产生锯齿和闪烁,都不是好方案。