24、WebRTC与AI结合:WebRTC + TensorFlow.js实现人脸检测、WebRTC + 语音识别、WebRTC + 背景替换
说实话,WebRTC 做到一定程度,你会发现一个有意思的现象——纯音视频传输本身已经没什么可卷的了。真正让应用拉开差距的,是「能不能在实时流里做点聪明的事」。
我个人习惯把这种能力叫做「流内智能」。说白了,就是在视频帧和音频帧还没送到对端之前,先让 AI 模型看一眼、听一耳朵,然后做点什么。今天我们就来聊聊三个最实用的方向:人脸检测、语音识别、背景替换。
核心思路:WebRTC 提供实时媒体流,TensorFlow.js 提供浏览器端 AI 推理能力。两者结合,不需要后端 GPU,不需要昂贵的服务器,直接在用户浏览器里完成智能处理。
24.1 为什么是 TensorFlow.js?
你可能会问:为什么不用 Python?为什么不用服务端推理?
嗯,这里有个关键点——延迟。WebRTC 的实时性要求极高,如果每一帧都要发到服务器去跑模型,再等结果回来,那延迟早就爆炸了。TensorFlow.js 跑在浏览器里,直接操作 Canvas 或 WebGL,能做到毫秒级响应。
我在项目中遇到过类似场景:一开始用 Python 服务端做人脸检测,结果 720p 视频流下延迟飙到 300ms 以上,用户明显感觉到画面卡顿。后来换成 TensorFlow.js 的 blazeface 模型,延迟直接降到 30ms 以内。
我的建议:如果模型推理时间超过 50ms,就不要逐帧处理了。可以每 3-5 帧处理一次,或者降低输入分辨率。用户的眼睛没那么敏感,但卡顿感非常敏感。
24.2 WebRTC + TensorFlow.js 人脸检测
人脸检测是最常见的 AI 增强场景。比如视频会议中自动框选人脸、美颜、或者只传输人脸区域来节省带宽。
实现流程其实很简单:
- 从
getUserMedia拿到MediaStream - 把视频帧绘制到
Canvas上 - 用 TensorFlow.js 模型检测人脸
- 把检测结果(边界框、关键点)叠加到画布上
- 把处理后的画布作为新的视频轨道发送出去
核心代码大概长这样:
// 加载人脸检测模型
const model = await blazeface.load();
// 从视频流中获取帧
const video = document.getElementById('localVideo');
const canvas = document.getElementById('outputCanvas');
const ctx = canvas.getContext('2d');
async function detectFrame() {
// 检测人脸
const predictions = await model.estimateFaces(video, false);
// 绘制原始帧
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
if (predictions.length > 0) {
predictions.forEach(pred => {
const start = pred.topLeft;
const end = pred.bottomRight;
const size = [end[0] - start[0], end[1] - start[1];
// 画框
ctx.strokeStyle = '#00FF00';
ctx.lineWidth = 3;
ctx.strokeRect(start[0], start[1], size[0], size[1]);
// 画关键点
pred.landmarks.forEach(landmark => {
ctx.fillStyle = '#FF0000';
ctx.beginPath();
ctx.arc(landmark[0], landmark[1], 3, 0, 2 * Math.PI);
ctx.fill();
});
});
}
requestAnimationFrame(detectFrame);
}
detectFrame();
注意:Canvas 的 toDataURL 或 toBlob 性能很差,千万不要在每一帧都调用。正确做法是用 canvas.captureStream() 直接生成新的 MediaStream,然后替换掉原来的视频轨道。
24.3 WebRTC + 语音识别
语音识别在 WebRTC 里其实更微妙。因为 WebRTC 的音频流是经过编码的(Opus),你不能直接拿 PCM 数据喂给语音识别模型。
我踩过这个坑。一开始我尝试从 RTCRtpReceiver 里拿原始音频数据,结果发现浏览器根本不暴露这个接口。后来换了个思路:用 MediaStream 的 AudioContext 创建 MediaStreamSource,然后通过 ScriptProcessorNode 或 AudioWorklet 拿到原始 PCM 数据。
具体做法:
- 从
getUserMedia或RTCPeerConnection的ontrack拿到音频轨道 - 创建
AudioContext和MediaStreamSource - 用
AudioWorklet处理音频数据 - 把 PCM 数据传给语音识别模型(比如
@tensorflow-models/speech-commands)
代码示例:
const audioContext = new AudioContext();
const source = audioContext.createMediaStreamSource(remoteStream);
// 创建 AudioWorklet
await audioContext.audioWorklet.addModule('audio-processor.js');
const workletNode = new AudioWorkletNode(audioContext, 'audio-processor');
source.connect(workletNode);
workletNode.connect(audioContext.destination);
// 在 audio-processor.js 中
class AudioProcessor extends AudioWorkletProcessor {
process(inputs, outputs, parameters) {
const input = inputs[0];
if (input && input.length > 0) {
const channelData = input[0];
// 这里 channelData 就是 PCM 数据
// 可以传给语音识别模型
this.port.postMessage(channelData);
}
return true;
}
}
registerProcessor('audio-processor', AudioProcessor);
避坑指南:我曾经在 AudioWorklet 里直接跑语音识别模型,结果发现模型加载和推理会阻塞音频处理线程,导致声音断断续续。正确做法是在主线程或 Web Worker 里跑模型,AudioWorklet 只负责传递数据。
24.4 WebRTC + 背景替换
背景替换是视频会议里最受欢迎的功能之一。实现方式有两种:
| 方案 | 原理 | 性能 | 适用场景 |
|---|---|---|---|
| 语义分割 | 用模型(如 BodyPix、MediaPipe)分割出人体区域 | 中等,需要 GPU | 高质量背景替换 |
| 绿幕抠像 | 基于颜色键控,纯色背景直接替换 | 极快,CPU 即可 | 有绿幕条件的场景 |
我个人更推荐语义分割方案,因为用户不可能都有绿幕。TensorFlow.js 的 body-pix 模型可以做到实时分割,精度也够用。
实现流程:
- 加载
body-pix模型 - 对每一帧进行分割,得到人体掩码
- 用掩码把原图的人体部分抠出来
- 把抠出来的人体叠加到新的背景上
- 用
canvas.captureStream()输出新视频流
核心代码:
const net = await bodyPix.load({
architecture: 'MobileNetV1',
outputStride: 16,
multiplier: 0.75,
quantBytes: 2
});
async function replaceBackground(video, backgroundImage) {
const segmentation = await net.segmentPerson(video, {
flipHorizontal: false,
internalResolution: 'medium',
segmentationThreshold: 0.7
});
const mask = bodyPix.toMask(segmentation);
const maskedImage = bodyPix.drawMask(
canvas, video, mask, 1, 0, false
);
// 把人体叠加到新背景上
ctx.drawImage(backgroundImage, 0, 0);
ctx.drawImage(maskedImage, 0, 0);
}
性能警告:背景替换非常消耗性能。如果你在低端设备上跑,建议降低分辨率(比如 480p),或者降低模型精度(multiplier: 0.5)。我曾经在 MacBook Air 上跑 720p 的背景替换,帧率直接掉到 15fps,用户反馈「像幻灯片一样」。
24.5 知识体系总览
下面这张图概括了 WebRTC + AI 的核心架构:
24.6 实战中的坑与建议
最后分享几个我在实际项目中踩过的坑:
- 模型加载时机:不要在
getUserMedia成功后才加载模型,那样用户会等很久。我习惯在页面加载时就预加载模型,等用户授权摄像头时模型已经 ready 了。 - 分辨率匹配:模型输入分辨率不一定要和视频分辨率一致。比如视频是 1080p,但模型只需要 224x224,那就先缩放到 224x224 再推理,结果再映射回原图坐标。这样能省 90% 的计算量。
- 轨道替换:处理完视频后,记得用
pc.getSenders()找到对应的RTCRtpSender,然后调用sender.replaceTrack(newTrack)。不要重新创建RTCPeerConnection,那样会重新协商,造成短暂断流。 - 音频处理:语音识别模型通常需要 16kHz 采样率的单声道音频。WebRTC 默认是 48kHz,记得在
AudioContext里做重采样。
我的个人习惯:在开发阶段,我会在页面上加一个「性能监控」面板,实时显示 FPS、模型推理时间、内存占用。这样能快速定位瓶颈。上线前再把这个面板隐藏掉,但保留日志输出,方便线上排查问题。
好了,WebRTC 与 AI 结合的内容就聊到这里。这三个方向——人脸检测、语音识别、背景替换——是目前最成熟也最实用的组合。你可以在自己的项目里先挑一个最需要的方向入手,跑通之后再逐步叠加其他能力。