想象你正在看一场直播:主播小美站在乱糟糟的卧室前,但观众看到的却是干净的海边或高级会议室。这背后没有绿幕,而是一位“舞台导演”老张拿着一把“智能剪刀”在干活——第一步,快速扫一眼画面,把主播的身体轮廓剪成一张透明卡片;第二步,把卡片边缘的毛刺砂平;第三步,把卡片贴在镜头上,卡片内的人像清晰,卡片外的背景则被虚化或换成另一张海报。整个过程每秒重复二三十次,观众才感觉不到卡顿。本文要讲的,就是在浏览器里如何用 AI 完成这套“剪卡片、磨边、换背景”的功夫。

岗位关联

掌握浏览器端实时背景虚化与替换,你就能为视频会议、直播、在线教育等产品加入端侧 AI 虚拟背景能力,成为“前端 + AI”复合型工程师。

目录
  1. 可视化演示
  2. 视频处理链路
  3. 人像分割模型
  4. 掩码平滑
  5. 背景合成
  6. 性能优化
  7. 工程实践
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

可视化展示人像分割模型推理、掩码平滑与帧合成管线、以及实时性能优化策略,交互调节参数体验背景虚化与替换效果。

本节配有交互动效(进入视口后自动加载,约 3KB)。

人像分割模型推理 MediaPipe/U2Net分割人像前景 逐像素二分类生成前景掩码 mask = sigmoid(model(frame)) 推理延迟决定实时性下限

视频处理链路

老张的直播间里,最忙的不是主播,而是那条从摄像头到屏幕的“传送带”。摄像头每秒钟吐出一批照片,后台不能所有事一起做;先采集、再推理、最后合成,每一步都要在规定时间内完成,观众才不会看到画面一卡一卡。

浏览器里,这条传送带由三个环节接力:getUserMedia() 负责从摄像头取流,requestVideoFrameCallback 负责卡着每一帧的节奏按门铃,最终通过 Canvas 或 WebGL 把结果画出来。帧一到就被转成 VideoFrame,接着缩放到适合模型的小尺寸作为推理输入,推理返回的掩码再与原帧一起进入合成阶段。

这四段时间是串行相加的,任意一段超时都会直接压低帧率。所以工程上第一步不是调模型,而是把整条链路的时间拆开看。

老张把传送带的每一环都贴在墙上,哪一环慢了就优化哪一环。浏览器端做实时特效也是一样的思路:先画链路图,再谈优化。

T总=T采集+T推理+T平滑+T合成\displaystyle T_{\text{总}} = T_{\text{采集}} + T_{\text{推理}} + T_{\text{平滑}} + T_{\text{合成}}
总耗时 = 采集 + 推理 + 掩码平滑 + 背景合成,瓶颈在哪一段,就先优化哪一段。
  1. 总耗时一帧从摄像头到屏幕所需的全部处理时间。
    T总T_{\text{总}}
  2. 采集耗时摄像头把光信号变成数字帧并送到内存的时间。
    T采集T_{\text{采集}}
  3. 推理耗时分割模型对输入帧计算概率图的时间。
    T推理T_{\text{推理}}
  4. 平滑与合成耗时掩码处理和背景合成都属于“后期制作”,同样占时间。
    T平滑+T合成T_{\text{平滑}} + T_{\text{合成}}

人像分割模型

老张的“智能剪刀”其实是一个像素画师。它不画人脸框,而是给画面里的每一粒像素打分:像人的地方打 1,不像人的地方打 0,边缘处打 0.5 这样的中间分。最后这些分数拼在一起,就是一张“人像概率图”。

浏览器端常使用轻量分割网络,比如以 MobileNet 为骨干的自拍分割模型。它先读入一帧缩小后的图片,内部算出一个叫 logits 的原始分数,再经过 sigmoid 函数压到 0 到 1 之间。为了实时,不能直接吃 4K 原图,通常先把帧缩到 256×256 或 512×512,得到概率图后再放大回原图尺寸。

小裁缝不必逐帧看 4K 的每个毛孔,只看一张低分辨率小样就能把轮廓圈出来,速度自然快很多。这就是低分辨率推理加概率图输出的价值。

P=σ(z)=11+e−z\displaystyle P = \sigma(z) = \frac{1}{1 + e^{-z}}
sigmoid 把任意实数分数压到 0~1 之间,变成“某个像素属于人的概率”。
  1. 原始分数 \(z\)模型对每个像素打出的原始响应,可能是正数也可能是负数。
    zz
  2. 指数项 \(e^{-z}\)当 \(z\) 很大时,这一项趋近 0,概率接近 1。
    e−ze^{-z}
  3. 分母 \(1 + e^{-z}\)让整个分数被限制在 0 到 1 之间。
    1+e−z1 + e^{-z}
  4. 概率 \(P\)最终输出代表“这个像素属于前景人物”的概率。
    P=11+e−zP = \frac{1}{1 + e^{-z}}

掩码平滑

刚剪出来的卡纸边缘常带着毛刺,胸口甚至有几个小洞。老张的台子上有一个“打磨台”:先用砂纸把边磨平,再用“记忆布”把上一帧的卡纸叠在当前帧上,防止轮廓来回抖。

模型直接输出的软掩码不是一刀切的 0 或 1,边缘通常是 0.3、0.7 这样的渐变值。高斯模糊负责羽化边缘,形态学腐蚀让轮廓往内收一点,避免背景颜色渗进人物,闭运算则用来填补空洞。除此之外,还要做时空滤波:当前帧掩码和上一帧掩码按比例混合,抑制单帧抖动。

老张把砂纸和记忆布放在固定位置,每帧都来一遍,观众的肉眼就不容易察觉轮廓在发抖。软掩码加时空滤波,是“边缘干净”的关键。

Mt=βMcur+(1−β)Mt−1\displaystyle M_t = \beta M_{\text{cur}} + (1-\beta) M_{t-1}
\(M_t\) 是平滑后的掩码,\(\beta\) 控制当前帧信息占比,另一部分来自上一帧。
  1. 当前帧掩码 \(M_{\text{cur}}\)模型刚输出的本帧概率图。
    McurM_{\text{cur}}
  2. 上一帧掩码 \(M_{t-1}\)上一帧保留下来的人像概率图。
    Mt−1M_{t-1}
  3. 权重 \(\beta\)表示更相信当前帧还是更相信历史信息,一般取 0.5~0.8。
    β\beta
  4. 加权求和把两帧信息按权重相加,得到更稳定的新掩码。
    Mt=βMcur+(1−β)Mt−1M_t = \beta M_{\text{cur}} + (1-\beta) M_{t-1}

背景合成

剪影卡片打磨好了,接下来是“叠片”:前台投影仪把主播原片打到幕布上,幕后用手电筒把背景海报的亮度调高或调低。老张手里握着的旋钮叫 \(\alpha\),越靠近 1,前景越实;越靠近 0,背景越明显。

合成阶段读入两路信号:原帧作为前景,虚化后的帧或另一张图片作为背景。逐像素混合公式是 out = α·fg + (1−α)·bg。在代码里,Canvas 的 drawImage 加混合模式或 WebGL 着色器都可以实现;背景虚化常用 ctx.filter = 'blur(...)',海报替换则直接加载图片或视频纹理。合成后还可以对前景做一次轻微锐化,就像给主播补一道轮廓光。

老张在调光台上把“前景灯光”和“背景灯光”分开调,这个公式就是那台双旋钮调光台的核心。

out=α×fg+(1−α)×bg\displaystyle \text{out} = \alpha \times \text{fg} + (1-\alpha) \times \text{bg}
\(\alpha\) 越大,最终画面越接近前景;\(\alpha\) 越小,背景越明显。
  1. 混合权重 \(\alpha\)来自平滑后掩码的像素值,范围是 0~1。
    α\alpha
  2. 前景贡献 \(\alpha \times \text{fg}\)人像像素乘以权重,保留清晰的主体。
    α×fg\alpha \times \text{fg}
  3. 背景贡献 \((1-\alpha) \times \text{bg}\)剩余权重给虚化背景或替换图片。
    (1−α)×bg(1-\alpha) \times \text{bg}
  4. 合成结果 \(\text{out}\)前景与背景按权重相加,得到最终画面。
    out=α×fg+(1−α)×bg\text{out} = \alpha \times \text{fg} + (1-\alpha) \times \text{bg}

性能优化

如果坚持让小裁缝看 4K 原图,每秒 30 帧的要求会立刻压垮他。精明的导演会让他先看 256×256 的小样,剪完轮廓再把卡纸放大回原图,这就是“低分辨率推理 + 掩码上采样”。

性能优化的核心是减少无效计算。计算成本近似与输入宽高成正比,从 1280×720 缩到 256×256,理论算力会降到原来的约十二分之一。此外,推理可以放进 Web Worker,避免阻塞页面渲染;低端机上还可以动态降到 15FPS,或每隔一帧推理一次。WebGPU/WebGL 则把卷积和矩阵运算交给 GPU,让主线程专心绘制。

老张给后台立了一条规矩:能用小样解决的问题,绝不动原图;能交给工人的活,绝不占用前台时间。性能优化就是把算力花在真正影响观感的地方。

C∝w×h\displaystyle C \propto w \times h
算力成本与输入分辨率成正比,降低分辨率是最直接的提速手段。
  1. 计算成本 \(C\)模型处理一帧所需的计算量。
    CC
  2. 宽度 \(w\) 与高度 \(h\)输入图像的宽和高。
    w,hw, h
  3. 正比关系 \(\propto\)宽高越大,计算量越大。
    C∝w×hC \propto w \times h
  4. 分辨率换算宽高各减半时,计算量变成原来的四分之一。
    w×hw \times h

工程实践

正式直播前,老张一定要做“彩排”:如果设备太老,舞台灯光自动降级为“只开一盏灯”,而不是让整个直播间黑屏。这就是降级策略。

工程上建议做三级降级:支持 WebGPU/WASM 时,做完整分割加背景替换;只支持 WebGL 时,降低分辨率或改为背景虚化;最差情况直接关闭特效。延迟控制用 performance.now() 统计每帧耗时,超过预算就自动下调分辨率或跳帧。兼容性方面要覆盖 Chrome、Safari、微信内置浏览器,用能力检测决定走哪条管线。上线前还要综合画质和流畅度做体验评估。

老张最后看的不是某一边多惊艳,而是整体观感。产品上线前也要用这个公式评估,而不是只看某台旗舰手机跑得多快。

---

体验分=a×画质分+b×流畅分\displaystyle \text{体验分} = a \times \text{画质分} + b \times \text{流畅分}
体验分由画质分和流畅分加权得到,\(a\) 与 \(b\) 根据产品定位调整。
  1. 画质分边缘自然度、背景替换真实感等主观评分。
    画质分\text{画质分}
  2. 流畅分帧率、卡顿率、处理延迟等客观指标。
    流畅分\text{流畅分}
  3. 权重 \(a\) 与 \(b\)不同产品侧重点不同,会议更看重流畅,直播更看重画面。
    a,ba, b
  4. 加权求和把两个维度合并成一个可对比的体验分。
    体验分=a×画质分+b×流畅分\text{体验分} = a \times \text{画质分} + b \times \text{流畅分}

AI 应用场景

在线会议隐私保护

阶段:媒体采集与实时推理

解决的问题:远程办公时家里环境杂乱,直接用摄像头容易泄露隐私。传统做法是手动关摄像头或使用静态图片,体验生硬。AI 人像分割能在端侧实时把人物抠出,背景替换成统一会议室画面,既保护隐私又自然。

具体案例:某 WebRTC 视频会议产品在浏览器端接入人像分割模型,通过 Canvas 合成虚拟背景,端侧完成全部计算,帧延迟控制在 80ms 以内,用户无需安装额外客户端。

直播电商虚拟场景

阶段:实时视频合成

解决的问题:电商主播需要频繁更换直播间主题,实体背景搭建成本高、切换慢。AI 背景替换让主播在普通房间就能一键进入“货架背景”“品牌主视觉”“海边度假”等虚拟场景。

具体案例:一个美妆直播间使用浏览器端背景替换,主播身后的实体货架被实时替换成产品主视觉海报,同时前景人物边缘保持清晰,观众在移动端也能流畅观看。

在线教育虚拟课堂

阶段:视频合成与效果评估

解决的问题:老师在家授课时背景杂乱,分散学生注意力。AI 虚化或替换背景后,学生注意力更集中,也降低了老师布置家庭书房的心理压力。

具体案例:某在线教育平台在网页端加入“虚拟黑板”模式,老师只要出现在画面中,背景就被替换成一块干净的黑板,板书和老师形象叠加显示。

Python 演示

python45 行
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()
目的:用 OpenCV 读取视频或摄像头,通过 MediaPipe 输出人像分割掩码,再做掩码平滑与 Alpha 混合,实现背景虚化或替换。
  1. result = seg.process(rgb)把 RGB 帧交给分割模型,得到人像概率图。
  2. mask[..., None]把 H×W 的掩码扩展成 H×W×1,方便与三通道帧相乘。
  3. frame * mask + backdrop * (1 - mask)完成前景与背景的 Alpha 混合。
  4. cv2.GaussianBlur(hard, (0, 0), sigmaX=9)对掩码边缘做羽化,去掉硬边。
  5. 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. 人像分割模型在浏览器端主要输出的数据是什么?

单选题 · easy

  • A.一张彩色抠图结果
  • B.一组人物关键点坐标
  • C.每个像素的前景概率图← 正确答案
  • D.一段压缩后的背景码流

答案:C。人像分割属于“语义分割”任务,输出的是每个像素属于前景人物的概率图,而不是人脸框或关键点。有了概率图后,再通过掩码平滑和 Alpha 混合生成最终效果。

Q2. 下列哪种方式最适合在浏览器中逐帧驱动背景特效?

单选题 · easy

  • A.使用定时器高频轮询
  • B.使用帧同步回调驱动← 正确答案
  • C.使用点击事件逐帧触发
  • D.使用网络请求拉取帧

答案:B。requestVideoFrameCallback 是浏览器提供的帧同步回调,能跟摄像头帧率对齐;定时器会漂移,点击触发和网络请求无法满足实时性要求。

Q3. 为兼顾实时性,通常让分割模型处理什么样的输入?

单选题 · mid

  • A.原图 4K 全分辨率
  • B.缩小后的视频帧← 正确答案
  • C.用户上传的图片
  • D.服务器返回的截图

答案:B。模型输入分辨率越大,计算成本越高。浏览器端实时应用通常把输入缩到 256×256 或 512×512,输出概率图后再放大回原图尺寸。

Q4. 在 Alpha 混合公式 out = α×fg + (1−α)×bg 中,α 接近 1 表示什么?

单选题 · mid

  • A.前景完全透出← 正确答案
  • B.背景完全透出
  • C.画面整体变暗
  • D.画面整体变亮

答案:A。α 是前景权重,接近 1 时 α×fg 占主导,背景权重 1−α 接近 0,所以最终画面更像前景。

Q5. 模型输出的掩码边缘有锯齿,最直接的平滑方法是?

单选题 · mid

  • A.提高模型输入分辨率
  • B.对掩码做高斯模糊← 正确答案
  • C.降低视频处理帧率
  • D.增加模型网络层数

答案:B。高斯模糊能对掩码边缘做羽化,让硬边变成渐变过渡。提高分辨率或增加层数只会增加计算量,不能直接解决边缘锯齿。

Q6. 在 Web Worker 中执行分割推理时,主线程不能直接操作什么?

单选题 · hard

  • A.离屏 Canvas 对象
  • B.图像位图对象
  • C.页面 Canvas 元素← 正确答案
  • D.WebGL 上下文

答案:C。Web Worker 没有 DOM 访问权限,不能直接操作页面上的 Canvas 元素;如果要并行绘制,必须使用 OffscreenCanvas 转移绘制任务。

Q7. 当设备不支持 WebGPU 时,合理的降级策略是?

单选题 · hard

  • A.立即停止视频采集
  • B.切到 WebGL 低分辨率← 正确答案
  • C.弹窗要求升级浏览器
  • D.永久关闭全部特效

答案:B。渐进增强和优雅降级是工程惯例。WebGPU 不可用时,可以用 WebGL 或 WASM 推理,并降低输入分辨率,保证基础功能可用。

Q8. 以下哪些方式能提升浏览器端背景替换的实时性能?

多选题 · mid

  • A.降低模型输入分辨率← 正确答案
  • B.把推理移入 Web Worker← 正确答案
  • C.每帧做全分辨率推理
  • D.动态跳过部分帧推理← 正确答案

答案:ABD。低分辨率推理减少计算量,Worker 并行避免阻塞主线程,动态跳帧在低端机上保证流畅。每帧全分辨率推理恰是性能瓶颈。

Q9. 掩码平滑阶段通常包含哪些操作?

多选题 · mid

  • A.高斯模糊羽化边缘← 正确答案
  • B.形态学腐蚀与膨胀← 正确答案
  • C.时间域滤波抑制闪烁← 正确答案
  • D.对视频做 H.264 编码

答案:ABC。高斯模糊让边缘柔软,腐蚀膨胀修复空洞和收边,时间域滤波让掩码不抖动。H.264 编码属于视频压缩,与掩码平滑无关。

Q10. 关于背景合成与工程优化,下列说法正确的有哪些?

多选题 · hard

  • A.只在人物边缘做精细过渡← 正确答案
  • B.整帧用 CPU 循环合成
  • C.用 GPU 完成背景与前景合成← 正确答案
  • D.硬掩码会让边缘更自然

答案:AC。只在掩码边缘做高质量过渡可以节省算力,GPU 合成能显著提升吞吐。整帧 CPU 循环效率低,硬掩码会产生锯齿和闪烁,都不是好方案。