16、视频处理:视频亮度/对比度调节、美颜滤镜(Canvas处理)、虚拟背景(MediaPipe集成思路)、视频轨道约束

视频处理这块,说实话是WebRTC应用里最「出彩」的部分。用户不会关心你的信令协议多优雅,但一眼就能看出画面亮不亮、背景乱不乱。我早期做视频会议项目时,就被客户吐槽过「你们这画面怎么跟监控探头似的」——嗯,从那以后我就把视频处理列为了必修课。

今天咱们就聊聊四个核心话题:亮度/对比度调节、美颜滤镜、虚拟背景,还有视频轨道约束。前两个用Canvas就能搞定,第三个需要MediaPipe这样的AI库,最后一个则是浏览器原生能力。

16.1 视频亮度与对比度调节

说白了,亮度就是整体画面的明暗程度,对比度则是亮部和暗部的差异大小。调节这两个参数,最直接的方式就是操作Canvas的像素数据。

我个人习惯用getImageData拿到像素数组,然后遍历每个像素的RGB值做数学变换。公式其实不复杂:

// 亮度调节:每个通道加上偏移量
newValue = originalValue + brightnessOffset;

// 对比度调节:以128为基准拉伸
newValue = (originalValue - 128) * contrastFactor + 128;

我在项目中遇到过一个问题:直接遍历4K视频的像素,帧率直接掉到个位数。后来改用requestAnimationFrame配合OffscreenCanvas,才把性能拉回来。你想想看,每帧处理几百万个像素,优化不好肯定卡。

核心思路:把视频帧绘制到Canvas上,读取像素数据,修改后再绘制回去。整个过程在requestAnimationFrame循环里完成。

这里给个完整的亮度调节示例:

function adjustBrightness(video, canvas, brightness) {
  const ctx = canvas.getContext('2d');
  const width = video.videoWidth;
  const height = video.videoHeight;
  canvas.width = width;
  canvas.height = height;

  function processFrame() {
    ctx.drawImage(video, 0, 0, width, height);
    const imageData = ctx.getImageData(0, 0, width, height);
    const data = imageData.data;

    for (let i = 0; i < data.length; i += 4) {
      data[i] += brightness;     // R
      data[i + 1] += brightness; // G
      data[i + 2] += brightness; // B
    }

    ctx.putImageData(imageData, 0, 0);
    requestAnimationFrame(processFrame);
  }

  processFrame();
}

小技巧:调节亮度时记得做边界裁剪(clamp),把值限制在0-255之间。否则会出现「过曝」或「死黑」的像素块。

16.2 美颜滤镜:Canvas处理方案

美颜滤镜说白了就是「磨皮+美白」。磨皮的核心是模糊算法,美白则是调整色温和亮度。我最早做美颜时,天真地以为用高斯模糊就行,结果发现人脸边缘也糊了——后来才加上边缘保留滤波。

一个轻量级的方案是用双边滤波(Bilateral Filter)。它既平滑皮肤,又保留边缘细节。不过计算量比较大,移动端慎用。

更实用的做法是:

  1. 高斯模糊:对全图做一次轻量模糊
  2. 混合模式:把模糊后的图和原图按比例混合
  3. 色温调整:增加蓝色通道值,让肤色更白皙
function beautyFilter(video, canvas, strength) {
  const ctx = canvas.getContext('2d');
  // ... 绘制视频帧 ...

  // 1. 高斯模糊(用Canvas内置filter)
  ctx.filter = `blur(${strength * 2}px)`;
  ctx.drawImage(video, 0, 0);
  ctx.filter = 'none';

  // 2. 混合原图(透明度控制磨皮强度)
  ctx.globalAlpha = 1 - strength;
  ctx.drawImage(video, 0, 0);
  ctx.globalAlpha = 1.0;
}

注意:Canvas的filter属性在部分移动端浏览器上性能不佳。如果遇到卡顿,建议用getImageData手动实现模糊。

我曾经在直播项目里试过用WebGL做美颜,效果确实好,但开发成本高。对于大多数场景,Canvas方案已经够用了。记住一个原则:先保证帧率,再追求效果

16.3 虚拟背景:MediaPipe集成思路

虚拟背景这两年特别火。Zoom、腾讯会议都在用。实现原理其实不复杂:先做人体分割(把人和背景分开),然后用新背景替换原背景。

MediaPipe是Google开源的媒体处理框架,它提供了Selfie Segmentation模型,专门做人像分割。我集成的时候,发现它返回的是一个灰度mask,白色区域是人,黑色区域是背景。

基本流程如下:

  1. 从摄像头获取视频帧
  2. 用MediaPipe模型推理,得到人像mask
  3. 把mask和原图、新背景图合成
// 伪代码示意
async function applyVirtualBackground(video, canvas, bgImage) {
  const model = await SelfieSegmentation.create();
  const ctx = canvas.getContext('2d');

  async function processFrame() {
    const result = await model.segmentPeople(video);
    const mask = result.segmentationMask;

    // 合成:mask区域显示原图,非mask区域显示背景
    ctx.drawImage(bgImage, 0, 0);
    ctx.globalCompositeOperation = 'destination-out';
    ctx.drawImage(mask, 0, 0);
    ctx.globalCompositeOperation = 'source-over';
    ctx.drawImage(video, 0, 0);

    requestAnimationFrame(processFrame);
  }

  processFrame();
}

关键点:MediaPipe的模型在移动端也能跑,但建议用WebGL后端加速。如果设备性能差,可以降低输入分辨率,比如从720p降到360p。

我记得第一次跑通虚拟背景时,同事都惊呆了——「你把自己P到了月球上?」。不过实际部署时要注意:模型加载需要时间,最好提前初始化,别让用户等。

16.4 视频轨道约束

视频轨道约束,说白了就是告诉浏览器「我要什么样的视频流」。这是WebRTC原生支持的能力,不需要任何第三方库。

约束条件通过getUserMediaconstraints参数设置。常用的有:

约束项 说明 示例值
width 视频宽度 { ideal: 1280, min: 640 }
height 视频高度 { ideal: 720, min: 480 }
frameRate 帧率 { ideal: 30, min: 15 }
facingMode 摄像头方向 'user''environment'
aspectRatio 宽高比 16/94/3

我建议用ideal而不是exact。为什么呢?因为exact是硬性要求,如果设备不支持,直接报错。而ideal是「尽量满足」,浏览器会选最接近的参数。

const constraints = {
  video: {
    width: { ideal: 1280 },
    height: { ideal: 720 },
    frameRate: { ideal: 30, min: 20 },
    facingMode: 'user'
  },
  audio: false
};

const stream = await navigator.mediaDevices.getUserMedia(constraints);

避坑指南:我曾经在项目中用了exact: 1920,结果用户的笔记本摄像头不支持1080p,直接抛OverconstrainedError。后来改成ideal,问题解决。

另外,视频轨道约束还可以动态调整。比如用户从「语音通话」切换到「视频会议」,可以调用stream.getVideoTracks()[0].applyConstraints()来改变分辨率。这个API很实用,但要注意:切换时画面可能会短暂黑屏

知识体系总览

下面这张图概括了本章的核心内容,从原始视频流到最终输出,每一步都有对应的处理技术:

视频处理知识体系 原始视频流 亮度/对比度 美颜滤镜 虚拟背景 Canvas像素操作 getImageData / putImageData Canvas滤镜 + 混合 高斯模糊 / 双边滤波 MediaPipe分割 Selfie Segmentation 视频轨道约束(getUserMedia) 亮度/对比度 → Canvas像素级处理 | 美颜 → Canvas滤镜 | 虚拟背景 → MediaPipe AI分割

从图中可以看到,视频处理其实分两条线:一条是像素级操作(亮度、美颜),另一条是AI辅助处理(虚拟背景)。而视频轨道约束则是贯穿始终的基础能力——没有合适的输入流,后面处理得再好也白搭。

好了,这一章的内容就到这里。记住一个核心原则:先保证基础画质,再叠加特效。别一上来就搞美颜,结果画面卡成PPT,用户会骂娘的。


公众号:蓝海资料掘金营,微信deep3321