24、WebRTC与AI结合:WebRTC + TensorFlow.js实现人脸检测、WebRTC + 语音识别、WebRTC + 背景替换

说实话,WebRTC 做到一定程度,你会发现一个有意思的现象——纯音视频传输本身已经没什么可卷的了。真正让应用拉开差距的,是「能不能在实时流里做点聪明的事」。

我个人习惯把这种能力叫做「流内智能」。说白了,就是在视频帧和音频帧还没送到对端之前,先让 AI 模型看一眼、听一耳朵,然后做点什么。今天我们就来聊聊三个最实用的方向:人脸检测、语音识别、背景替换。

核心思路:WebRTC 提供实时媒体流,TensorFlow.js 提供浏览器端 AI 推理能力。两者结合,不需要后端 GPU,不需要昂贵的服务器,直接在用户浏览器里完成智能处理。

24.1 为什么是 TensorFlow.js?

你可能会问:为什么不用 Python?为什么不用服务端推理?

嗯,这里有个关键点——延迟。WebRTC 的实时性要求极高,如果每一帧都要发到服务器去跑模型,再等结果回来,那延迟早就爆炸了。TensorFlow.js 跑在浏览器里,直接操作 CanvasWebGL,能做到毫秒级响应。

我在项目中遇到过类似场景:一开始用 Python 服务端做人脸检测,结果 720p 视频流下延迟飙到 300ms 以上,用户明显感觉到画面卡顿。后来换成 TensorFlow.js 的 blazeface 模型,延迟直接降到 30ms 以内。

我的建议:如果模型推理时间超过 50ms,就不要逐帧处理了。可以每 3-5 帧处理一次,或者降低输入分辨率。用户的眼睛没那么敏感,但卡顿感非常敏感。

24.2 WebRTC + TensorFlow.js 人脸检测

人脸检测是最常见的 AI 增强场景。比如视频会议中自动框选人脸、美颜、或者只传输人脸区域来节省带宽。

实现流程其实很简单:

  1. getUserMedia 拿到 MediaStream
  2. 把视频帧绘制到 Canvas
  3. 用 TensorFlow.js 模型检测人脸
  4. 把检测结果(边界框、关键点)叠加到画布上
  5. 把处理后的画布作为新的视频轨道发送出去

核心代码大概长这样:

// 加载人脸检测模型
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 的 toDataURLtoBlob 性能很差,千万不要在每一帧都调用。正确做法是用 canvas.captureStream() 直接生成新的 MediaStream,然后替换掉原来的视频轨道。

24.3 WebRTC + 语音识别

语音识别在 WebRTC 里其实更微妙。因为 WebRTC 的音频流是经过编码的(Opus),你不能直接拿 PCM 数据喂给语音识别模型。

我踩过这个坑。一开始我尝试从 RTCRtpReceiver 里拿原始音频数据,结果发现浏览器根本不暴露这个接口。后来换了个思路:用 MediaStreamAudioContext 创建 MediaStreamSource,然后通过 ScriptProcessorNodeAudioWorklet 拿到原始 PCM 数据。

具体做法:

  1. getUserMediaRTCPeerConnectionontrack 拿到音频轨道
  2. 创建 AudioContextMediaStreamSource
  3. AudioWorklet 处理音频数据
  4. 把 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 模型可以做到实时分割,精度也够用。

实现流程:

  1. 加载 body-pix 模型
  2. 对每一帧进行分割,得到人体掩码
  3. 用掩码把原图的人体部分抠出来
  4. 把抠出来的人体叠加到新的背景上
  5. 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 的核心架构:

WebRTC + AI 核心架构 WebRTC 媒体流 TensorFlow.js 模型推理 人脸检测 语音识别 背景替换 增强后的 WebRTC 流 浏览器端 AI 推理,零服务器成本

24.6 实战中的坑与建议

最后分享几个我在实际项目中踩过的坑:

  • 模型加载时机:不要在 getUserMedia 成功后才加载模型,那样用户会等很久。我习惯在页面加载时就预加载模型,等用户授权摄像头时模型已经 ready 了。
  • 分辨率匹配:模型输入分辨率不一定要和视频分辨率一致。比如视频是 1080p,但模型只需要 224x224,那就先缩放到 224x224 再推理,结果再映射回原图坐标。这样能省 90% 的计算量。
  • 轨道替换:处理完视频后,记得用 pc.getSenders() 找到对应的 RTCRtpSender,然后调用 sender.replaceTrack(newTrack)。不要重新创建 RTCPeerConnection,那样会重新协商,造成短暂断流。
  • 音频处理:语音识别模型通常需要 16kHz 采样率的单声道音频。WebRTC 默认是 48kHz,记得在 AudioContext 里做重采样。

我的个人习惯:在开发阶段,我会在页面上加一个「性能监控」面板,实时显示 FPS、模型推理时间、内存占用。这样能快速定位瓶颈。上线前再把这个面板隐藏掉,但保留日志输出,方便线上排查问题。

好了,WebRTC 与 AI 结合的内容就聊到这里。这三个方向——人脸检测、语音识别、背景替换——是目前最成熟也最实用的组合。你可以在自己的项目里先挑一个最需要的方向入手,跑通之后再逐步叠加其他能力。


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