16、智能视频分析:YOLO物体检测、运动检测、区域入侵告警、WebRTC流中嵌入分析结果

这一章,我们来聊聊怎么让WebRTC流“看懂”画面。

说白了,就是给视频流装上大脑。让它不仅能传输画面,还能告诉你画面里有什么、发生了什么、有没有异常。我最早接触这个方向是在一个安防项目里,客户要求实时检测仓库里有没有人闯入禁区。那时候我就在想,能不能把AI检测和WebRTC揉到一起?后来发现,不仅能,而且效果还不错。

16.1 整体架构:AI分析引擎如何接入WebRTC管道

先看一张图,帮你快速建立整体认知。

WebRTC + AI 智能视频分析架构 摄像头/屏幕捕获 WebRTC 发送端 SFU / 媒体服务器 WebRTC 接收端 AI 分析引擎 (YOLO / 运动检测) 叠加分析结果

整个流程其实不复杂。摄像头采集画面,通过WebRTC发送端推到SFU。SFU一方面转发给接收端,另一方面把视频帧交给AI分析引擎。分析完的结果,再叠加回视频流里。接收端看到的,就是带标注的智能画面了。

核心思路:AI分析不一定要在发送端做,也不一定要在接收端做。放在SFU侧,一次分析,多人共享,效率最高。我在项目中就吃过亏——最开始在每台接收端都跑YOLO,结果CPU直接拉满。后来改到SFU侧,问题迎刃而解。

16.2 YOLO物体检测:从帧提取到推理输出

YOLO,全称You Only Look Once。这名字起得好,看一眼就完事。它把目标检测当成回归问题,一次前向传播就能输出所有物体的位置和类别。

在WebRTC场景里,我们怎么用YOLO?

第一步,从视频流里取帧。WebRTC的VideoTrack本质上是一帧一帧的VideoFrame。我们可以通过track.onframe或者ReadableStream拿到原始帧数据。

// 从WebRTC轨道中提取视频帧
const videoTrack = pc.getReceivers()[0].track;
const processor = new MediaStreamTrackProcessor({ track: videoTrack });
const reader = processor.readable.getReader();

async function processFrames() {
  while (true) {
    const { done, value: frame } = await reader.read();
    if (done) break;
    
    // 将VideoFrame转换为Canvas可用的格式
    const canvas = document.createElement('canvas');
    canvas.width = frame.displayWidth;
    canvas.height = frame.displayHeight;
    const ctx = canvas.getContext('2d');
    ctx.drawImage(frame, 0, 0);
    
    // 送入YOLO模型推理
    const detections = await runYOLO(canvas);
    
    // 处理检测结果...
    frame.close();
  }
}
processFrames();

第二步,把Canvas图像传给YOLO模型。我习惯用ONNX Runtime Web或者TensorFlow.js来跑模型。YOLOv8的ONNX模型大概30MB,在浏览器里跑一次推理大约50-100ms,基本能跟上实时视频。

小技巧:不要每帧都推理。视频一般是30fps,但YOLO跑30次/秒太费资源。我一般设置跳帧策略——每3帧取1帧做检测,或者根据场景动态调整。静止画面可以降到1fps,运动剧烈时再升到10fps。

16.3 运动检测:简单高效的背景差分法

YOLO虽然强,但也不是万能的。有些场景不需要知道“是什么”,只需要知道“有没有动”。比如监控画面里,只要有人走过就触发告警。这时候运动检测比YOLO更轻量、更快速。

运动检测的核心算法是背景差分。说白了,就是拿当前帧减去背景帧,差值大的区域就是运动区域。

// 运动检测:背景差分法
class MotionDetector {
  constructor(width, height, threshold = 30) {
    this.width = width;
    this.height = height;
    this.threshold = threshold;
    this.background = null;
  }

  // 更新背景(每隔N帧更新一次)
  updateBackground(currentFrame) {
    this.background = currentFrame.slice(); // 深拷贝
  }

  // 检测运动区域
  detect(currentFrame) {
    if (!this.background) {
      this.updateBackground(currentFrame);
      return [];
    }

    const diff = new Uint8Array(this.width * this.height);
    let motionPixels = 0;

    for (let i = 0; i < currentFrame.length; i++) {
      const delta = Math.abs(currentFrame[i] - this.background[i]);
      diff[i] = delta > this.threshold ? 255 : 0;
      if (diff[i] === 255) motionPixels++;
    }

    // 计算运动比例
    const motionRatio = motionPixels / (this.width * this.height);
    
    // 动态更新背景(慢速适应光照变化)
    for (let i = 0; i < currentFrame.length; i++) {
      this.background[i] = this.background[i] * 0.95 + currentFrame[i] * 0.05;
    }

    return {
      motionRatio,
      motionMask: diff,
      isMotion: motionRatio > 0.01 // 超过1%像素变化视为运动
    };
  }
}

这段代码看着简单,但实际效果很好。我曾在一个人脸门禁项目里用这个算法做活体检测的预处理——如果有人拿着照片晃动,运动检测能快速识别出异常。

注意:背景差分法对光照变化很敏感。如果场景里有灯开关、窗帘飘动,很容易误报。我的经验是:加一个光照补偿因子,或者用帧间差分法(连续两帧相减)代替背景差分。

16.4 区域入侵告警:划定禁区,智能触发

有了YOLO的检测框,有了运动检测的蒙版,接下来就可以做区域入侵告警了。

区域入侵的逻辑其实就三步:

  1. 在画面上画一个多边形区域(禁区)
  2. 判断检测框或多边形是否与禁区相交
  3. 如果相交,触发告警

判断两个多边形是否相交,可以用分离轴定理(SAT),也可以用更简单的“点是否在多边形内”算法。我一般用射线法——从检测框中心点发出一条射线,看与禁区边界的交点数量。奇数就在内部,偶数就在外部。

// 射线法判断点是否在多边形内
function isPointInPolygon(point, polygon) {
  const [x, y] = point;
  let inside = false;
  for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
    const [xi, yi] = polygon[i];
    const [xj, yj] = polygon[j];
    if ((yi > y) !== (yj > y) && 
        x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
      inside = !inside;
    }
  }
  return inside;
}

// 入侵检测逻辑
function checkIntrusion(detections, restrictedZone) {
  const alerts = [];
  for (const det of detections) {
    const centerX = det.x + det.width / 2;
    const centerY = det.y + det.height / 2;
    if (isPointInPolygon([centerX, centerY], restrictedZone)) {
      alerts.push({
        label: det.label,
        confidence: det.confidence,
        message: `检测到 ${det.label} 进入禁区`
      });
    }
  }
  return alerts;
}

实际项目经验:有一次我在做周界安防,客户要求“人不能进,但猫狗可以进”。怎么办?很简单,YOLO检测出“人”类别才触发告警,检测出“猫”“狗”就忽略。这就是AI检测比纯运动检测更智能的地方——它能区分“谁”在动。

16.5 在WebRTC流中嵌入分析结果

分析结果出来了,怎么让接收端看到?

有三种主流方式:

方式 原理 延迟 适用场景
Canvas叠加 在接收端用Canvas绘制检测框和文字 低(<50ms) 浏览器端查看
数据通道传输 通过WebRTC DataChannel发送JSON结果 低(<30ms) 需要二次处理的场景
视频帧直接绘制 在SFU侧将结果绘制到视频帧上再转发 中(<100ms) 所有接收端统一显示

我个人最推荐第二种——数据通道传输。为什么?因为它把“显示”和“分析”解耦了。接收端拿到JSON数据后,可以自由选择怎么渲染。想画框就画框,想显示文字就显示文字,甚至可以用语音播报。

// 通过DataChannel发送检测结果
const dataChannel = pc.createDataChannel('ai-analysis');
dataChannel.onopen = () => {
  // 每帧检测完成后发送结果
  setInterval(async () => {
    const frame = await captureFrame();
    const detections = await runYOLO(frame);
    const alerts = checkIntrusion(detections, restrictedZone);
    
    dataChannel.send(JSON.stringify({
      timestamp: Date.now(),
      detections: detections.map(d => ({
        label: d.label,
        confidence: d.confidence,
        bbox: [d.x, d.y, d.width, d.height]
      })),
      alerts: alerts
    }));
  }, 100); // 10fps
};

// 接收端解析并渲染
dataChannel.onmessage = (event) => {
  const data = JSON.parse(event.data);
  // 在Canvas上绘制检测框
  drawDetections(data.detections);
  // 显示告警信息
  if (data.alerts.length > 0) {
    showAlerts(data.alerts);
  }
};

避坑指南:我曾经在数据通道里发送过大的JSON(比如把整张图片的像素数据都塞进去),结果导致通道拥塞,视频也跟着卡顿。记住,数据通道只传元数据,不要传原始图像。检测框坐标、类别、置信度,这些就够了。

16.6 性能优化:别让AI拖垮WebRTC

AI分析很吃资源。如果处理不好,WebRTC的实时性会大打折扣。这里分享几个我踩过的坑和优化方案。

1. 推理频率控制

不要每帧都推理。我一般用动态帧率:

  • 画面静止时:1fps
  • 有轻微运动时:5fps
  • 有大量运动时:10fps

这样平均推理负载能降低60%以上。

2. 模型量化

YOLOv8的FP32模型大概120MB,量化成INT8后只有30MB,推理速度提升3倍,精度只下降1-2%。在浏览器里跑,量化几乎是必选项。

3. 使用Web Workers

把AI推理放到Worker线程里,避免阻塞主线程的渲染和WebRTC处理。我习惯开2-3个Worker,轮流处理帧,形成流水线。

// 主线程
const worker = new Worker('ai-worker.js');
worker.postMessage({ type: 'init', model: 'yolov8n-int8.onnx' });

// 每帧发送给Worker处理
worker.postMessage({ type: 'infer', frame: canvasData }, [canvasData.buffer]);
worker.onmessage = (e) => {
  const { detections, alerts } = e.data;
  // 更新UI
};

4. 硬件加速

WebGL和WebGPU可以加速模型推理。TensorFlow.js默认就支持WebGL后端。如果用户的设备支持,推理时间能从100ms降到20ms。

一句话总结:AI + WebRTC不是简单的拼凑,而是一场精心设计的协奏。YOLO负责“看懂”,运动检测负责“感知”,区域入侵负责“判断”,数据通道负责“传达”。四者配合好了,你的视频流就有了真正的智能。

好了,这一章的内容就到这里。代码示例你都可以直接拿去用,但记得根据实际场景调整参数。毕竟,每个项目的摄像头角度、光照条件、检测目标都不一样。多调试,多测试,你也能做出自己的智能视频分析系统。


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