21、WebRTC与AI结合:人脸检测与背景替换、实时美颜滤镜、语音识别集成
说实话,WebRTC 做到一定程度,你会发现一个瓶颈——画面质量。网络再优化,码率再控制,摄像头怼着脸拍,痘痘、暗光、杂乱的背景,全给对面看去了。这时候,AI 就该上场了。
我个人习惯把 WebRTC + AI 的集成分成三个层次:视觉增强(美颜、背景替换)、听觉增强(语音识别、降噪)、智能交互(表情识别、手势控制)。这一章,咱们就聚焦前两个,把最实用的落地场景讲透。
21.1 人脸检测:一切视觉增强的基础
不管你是做美颜还是背景替换,第一步永远是——找到人脸在哪。没有这一步,后面全是瞎搞。
我早期在项目中踩过一个坑:直接用 Canvas 2D 逐帧分析像素,CPU 直接拉满,手机发烫到能煎鸡蛋。后来换成了 MediaPipe Face Detection,才算是走上正轨。
21.1.1 集成 MediaPipe 人脸检测
MediaPipe 是 Google 开源的跨平台框架,专门为实时视频流设计。它能在浏览器里通过 WebAssembly 跑模型,延迟极低。
// 1. 引入 MediaPipe 库
import { FaceDetection } from '@mediapipe/face_detection';
import { Camera } from '@mediapipe/camera_utils';
// 2. 初始化检测器
const faceDetection = new FaceDetection({
locateFile: (file) => {
return `https://cdn.jsdelivr.net/npm/@mediapipe/face_detection/${file}`;
}
});
faceDetection.setOptions({
modelSelection: 1, // 0: 短距离(2m内), 1: 全距离
minDetectionConfidence: 0.7
});
// 3. 绑定摄像头
const camera = new Camera(videoElement, {
onFrame: async () => {
await faceDetection.send({ image: videoElement });
},
width: 640,
height: 480
});
camera.start();
// 4. 获取检测结果
faceDetection.onResults((results) => {
if (results.detections.length > 0) {
const bbox = results.detections[0].boundingBox;
// bbox.xCenter, bbox.yCenter, bbox.width, bbox.height
drawFaceBox(bbox); // 绘制人脸框
}
});
21.2 背景替换:从抠图到合成
背景替换说白了就是两步:抠出人像 + 合成新背景。难点在于实时性——你不能让用户看到一卡一卡的边缘。
21.2.1 基于 TensorFlow.js 的 BodyPix 方案
BodyPix 是 TensorFlow.js 官方的人像分割模型。它能把视频帧里的每个人体像素标出来,精度不错,但速度一般。
import * as bodyPix from '@tensorflow-models/body-pix';
// 加载模型(建议在页面初始化时做)
const net = await bodyPix.load({
architecture: 'MobileNetV1',
outputStride: 16,
multiplier: 0.75,
quantBytes: 2
});
// 每一帧处理
async function processFrame(video, canvas) {
const segmentation = await net.segmentPerson(video, {
flipHorizontal: false,
internalResolution: 'medium',
segmentationThreshold: 0.7
});
// 用 Canvas 2D 合成
const ctx = canvas.getContext('2d');
ctx.drawImage(backgroundImage, 0, 0, canvas.width, canvas.height);
// 把人像部分画上去
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const mask = segmentation.data;
for (let i = 0; i < mask.length; i++) {
if (mask[i] === 1) {
// 保留原视频像素
const videoData = getVideoPixel(video, i);
data[i * 4] = videoData.r;
data[i * 4 + 1] = videoData.g;
data[i * 4 + 2] = videoData.b;
data[i * 4 + 3] = 255;
} else {
// 背景区域透明,显示背景图
data[i * 4 + 3] = 0;
}
}
ctx.putImageData(imageData, 0, 0);
}
21.2.2 用 WebGL 加速合成
纯 CPU 做像素操作太慢了。我建议把合成逻辑搬到 WebGL 着色器 里。这样 GPU 并行处理,帧率直接翻倍。
// 顶点着色器
const vertexShader = `
attribute vec2 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
}
`;
// 片元着色器(核心合成逻辑)
const fragmentShader = `
precision mediump float;
uniform sampler2D u_video;
uniform sampler2D u_background;
uniform sampler2D u_mask;
varying vec2 v_texCoord;
void main() {
vec4 videoColor = texture2D(u_video, v_texCoord);
vec4 bgColor = texture2D(u_background, v_texCoord);
float mask = texture2D(u_mask, v_texCoord).r;
// mask 接近 1 表示人像,接近 0 表示背景
gl_FragColor = mix(bgColor, videoColor, mask);
}
`;
嗯,这里要注意:mask 纹理需要每帧更新。你可以把 MediaPipe 输出的分割结果,用 texImage2D 上传到 GPU。这样 CPU 只负责跑模型,GPU 负责合成,各司其职。
21.3 实时美颜滤镜:不只是磨皮
美颜滤镜听起来高大上,其实核心就三个操作:磨皮(模糊)、美白(调色)、瘦脸(变形)。
21.3.1 磨皮:双边滤波
普通高斯模糊会把边缘也糊掉,人脸轮廓都没了。双边滤波(Bilateral Filter)能保留边缘,只平滑皮肤区域。
// 简化版双边滤波(片元着色器)
const bilateralShader = `
precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_texelSize;
uniform float u_sigma;
void main() {
vec2 uv = v_texCoord;
vec3 center = texture2D(u_image, uv).rgb;
vec3 sum = vec3(0.0);
float totalWeight = 0.0;
// 采样周围 5x5 像素
for (int x = -2; x <= 2; x++) {
for (int y = -2; y <= 2; y++) {
vec2 offset = vec2(float(x), float(y)) * u_texelSize;
vec3 sample = texture2D(u_image, uv + offset).rgb;
// 空间权重
float spatialWeight = exp(-float(x*x + y*y) / (2.0 * u_sigma * u_sigma));
// 颜色权重(边缘保留关键)
float colorWeight = exp(-distance(sample, center) / (2.0 * 0.1 * 0.1));
float weight = spatialWeight * colorWeight;
sum += sample * weight;
totalWeight += weight;
}
}
gl_FragColor = vec4(sum / totalWeight, 1.0);
}
`;
21.3.2 美白:简单调色
美白其实就是一个颜色矩阵变换。把 RGB 往亮色调,同时降低黄色分量。
// 美白着色器
const whiteningShader = `
precision mediump float;
uniform sampler2D u_image;
uniform float u_intensity; // 0.0 ~ 1.0
void main() {
vec4 color = texture2D(u_image, v_texCoord);
// 提高亮度
vec3 bright = color.rgb * (1.0 + u_intensity * 0.3);
// 降低黄色(减少红色和绿色中的蓝色补色)
bright.b = mix(color.b, bright.b, 0.8);
// 增加蓝色调,让皮肤更冷白
bright.b = min(bright.b + u_intensity * 0.05, 1.0);
gl_FragColor = vec4(bright, color.a);
}
`;
我个人习惯把美白强度控制在 0.3~0.5 之间。太强了会显得假白,像戴了面具。你想想看,用户开视频会议,对面看到一张惨白的脸,多吓人。
21.4 语音识别集成:让视频通话“听得懂”
语音识别和 WebRTC 结合,最常见的场景是实时字幕和语音指令。比如开会时自动生成字幕,或者说“静音”就自动关麦。
21.4.1 获取音频流并送入识别引擎
WebRTC 的 MediaStream 可以直接传给 Web Speech API 或第三方服务(如 Google Cloud Speech-to-Text)。
// 获取本地麦克风流
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
// 创建 AudioContext 处理音频
const audioContext = new AudioContext();
const source = audioContext.createMediaStreamSource(stream);
// 创建 Web Speech API 识别器
const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
recognition.lang = 'zh-CN';
recognition.continuous = true; // 持续识别
recognition.interimResults = true; // 返回中间结果
// 绑定音频流到识别器(注意:Web Speech API 自动使用默认麦克风)
// 这里需要手动将 stream 连接到识别器
// 实际上 Web Speech API 不支持直接传入 MediaStream,需要 hack
// 更可靠的方式是用 WebSocket 发送音频到后端
// 方案一:直接用浏览器麦克风(简单但不灵活)
recognition.onresult = (event) => {
for (let i = event.resultIndex; i < event.results.length; i++) {
const transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
console.log('最终结果:', transcript);
// 显示到字幕区域
showSubtitle(transcript);
} else {
console.log('中间结果:', transcript);
}
}
};
recognition.start();
21.4.2 用 WebSocket 传输音频到后端
更专业的做法是把 PCM 音频数据通过 WebSocket 发送到服务器,由服务器做语音识别,再把结果推回来。
// 前端:采集音频并发送
const socket = new WebSocket('wss://your-asr-server.com/ws');
const mediaRecorder = new MediaRecorder(stream, {
mimeType: 'audio/webm;codecs=opus'
});
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0 && socket.readyState === WebSocket.OPEN) {
socket.send(event.data);
}
};
// 每 100ms 采集一次
mediaRecorder.start(100);
// 接收识别结果
socket.onmessage = (event) => {
const result = JSON.parse(event.data);
if (result.type === 'final') {
updateSubtitle(result.text);
}
};
为什么不用 WebRTC 的 RTCPeerConnection 传音频?因为语音识别需要的是原始音频流,而不是编码后的 RTP 包。你想想看,编码后的 Opus 数据再解码,质量会有损失,影响识别准确率。
21.5 知识体系总览
下面这张图,把本章涉及的核心模块和它们之间的关系梳理清楚了。你可以把它当作一个技术选型参考。
21.6 性能优化建议
把 AI 集成到 WebRTC 里,最大的敌人是延迟。视频通话本身就有端到端延迟,再加上 AI 处理,很容易超过 500ms,用户能明显感觉到卡顿。
我总结了几条实战经验:
- 降低分辨率:AI 处理用 320x240 就够了,输出到 WebRTC 再用 720p。人脸检测不需要高清图。
- 跳帧处理:不是每一帧都要跑 AI。比如美颜可以每 2 帧处理一次,中间帧直接复用上一帧的结果。人眼基本看不出来。
- Web Worker 分离:把 AI 模型放到 Web Worker 里跑,避免阻塞主线程的渲染和编码。
- 硬件加速优先:能用 WebGL 就别用 Canvas 2D,能用 WebGPU 就别用 WebGL。移动端尤其明显。
好了,这一章的内容就到这里。WebRTC 和 AI 的结合,说白了就是用算力换体验。你投入多少计算资源,用户就能得到多好的画面和交互。但别忘了,实时通信的第一要务是流畅——再美的滤镜,卡成 PPT 也没人用。