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)。它既平滑皮肤,又保留边缘细节。不过计算量比较大,移动端慎用。
更实用的做法是:
- 高斯模糊:对全图做一次轻量模糊
- 混合模式:把模糊后的图和原图按比例混合
- 色温调整:增加蓝色通道值,让肤色更白皙
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,白色区域是人,黑色区域是背景。
基本流程如下:
- 从摄像头获取视频帧
- 用MediaPipe模型推理,得到人像mask
- 把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原生支持的能力,不需要任何第三方库。
约束条件通过getUserMedia的constraints参数设置。常用的有:
| 约束项 | 说明 | 示例值 |
|---|---|---|
width |
视频宽度 | { ideal: 1280, min: 640 } |
height |
视频高度 | { ideal: 720, min: 480 } |
frameRate |
帧率 | { ideal: 30, min: 15 } |
facingMode |
摄像头方向 | 'user' 或 'environment' |
aspectRatio |
宽高比 | 16/9 或 4/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很实用,但要注意:切换时画面可能会短暂黑屏。
知识体系总览
下面这张图概括了本章的核心内容,从原始视频流到最终输出,每一步都有对应的处理技术:
从图中可以看到,视频处理其实分两条线:一条是像素级操作(亮度、美颜),另一条是AI辅助处理(虚拟背景)。而视频轨道约束则是贯穿始终的基础能力——没有合适的输入流,后面处理得再好也白搭。
好了,这一章的内容就到这里。记住一个核心原则:先保证基础画质,再叠加特效。别一上来就搞美颜,结果画面卡成PPT,用户会骂娘的。
公众号:蓝海资料掘金营,微信deep3321