4、动态障碍物检测:帧差法与背景建模、光流法原理与实现、基于深度学习的检测方法、多传感器融合检测

动态障碍物检测,说白了就是让机器人「看见」周围哪些东西在动。

我刚开始做机器人时,以为这很简单——摄像头一拍,动的东西不就出来了?结果第一次在工厂测试,AGV 被飘过的塑料袋吓得急刹,差点把货架撞翻。嗯,从那以后我才明白,动态检测远没想象中那么轻松。

这一章,我会把四种主流方法掰开揉碎讲清楚。每种方法都有它的脾气,也有它的用武之地。

核心观点:没有万能的方法,只有合适的场景。帧差法快但糙,光流法细但慢,深度学习准但吃算力,多传感器融合稳但复杂。

动态障碍物检测 帧差法与背景建模 光流法原理与实现 深度学习方法 多传感器融合检测 帧差法 背景建模 稀疏光流 稠密光流 YOLO/SSD 光流+检测 四种方法各有优劣,实际项目中常组合使用 相机+激光雷达 相机+毫米波

4.1 帧差法与背景建模

帧差法是我最早接触的动态检测方法。原理简单得令人发指:拿当前帧减去上一帧,差值大的像素就是运动区域。

你想想看,如果画面里有个行人走过,相邻两帧之间,行人位置的像素值变化会很大。背景基本不变,差值接近零。就这么简单。

// 帧差法核心代码(C++ / OpenCV)
cv::Mat frame1, frame2, diff, binary;
cap >> frame1;
cap >> frame2;

cv::absdiff(frame1, frame2, diff);           // 计算帧差
cv::threshold(diff, binary, 30, 255, cv::THRESH_BINARY);  // 二值化

// 形态学操作去噪
cv::erode(binary, binary, cv::Mat());
cv::dilate(binary, binary, cv::Mat());

我的经验:阈值设30是个不错的起点。但光线变化大的场景,我建议用自适应阈值。曾经在仓库测试,日光灯频闪导致帧差法疯狂误报,换成自适应阈值后问题解决。

帧差法的优点很明显:计算量极小,实时性极好。缺点也致命:只能检测运动物体的边缘,内部会出现「空洞」。而且对运动速度敏感——太快了拖影,太慢了检测不到。

背景建模是帧差法的升级版。它不再用上一帧做参考,而是维护一个「背景模型」。常用的有混合高斯模型(GMM)和 ViBe 算法。

我个人习惯用 OpenCV 的 createBackgroundSubtractorMOG2。它能为每个像素建立多个高斯分布,适应光照渐变和微小抖动。

cv::Ptr<cv::BackgroundSubtractor> bgSubtractor;
bgSubtractor = cv::createBackgroundSubtractorMOG2(500, 16, true);

cv::Mat frame, fgMask;
cap >> frame;
bgSubtractor->apply(frame, fgMask);  // 前景提取

避坑指南:我曾经在户外场景用 GMM,结果树影晃动被当成运动物体。后来加了阴影检测参数 detectShadows=true,误报率降了 60%。记住,背景建模对光照突变非常敏感,室内用效果远好于室外。

4.2 光流法原理与实现

光流法,说白了就是追踪像素点的运动轨迹。它假设同一个点在相邻帧之间亮度不变,然后通过求解光流方程得到运动矢量。

为什么叫「光流」?因为光线在视网膜上流动,就像水流一样。每个像素都有一个速度矢量,指向它运动的方向。

光流法分两种:稀疏光流和稠密光流。

类型 代表算法 计算量 适用场景
稀疏光流 Lucas-Kanade (LK) 特征点跟踪、视觉里程计
稠密光流 Farneback / DIS 运动分割、行为识别

稀疏光流只计算特征点(如角点)的运动。我最早做视觉 SLAM 时就用 LK 光流跟踪特征点,效果不错。

// 稀疏光流 LK 实现
std::vector<cv::Point2f> prevPts, nextPts;
std::vector<uchar> status;
std::vector<float> err;

cv::calcOpticalFlowPyrLK(prevGray, currGray, prevPts, nextPts, 
                          status, err, cv::Size(21,21), 3);

// 只保留跟踪成功的点
for (int i = 0; i < status.size(); i++) {
    if (status[i]) {
        // 绘制运动轨迹
        cv::line(frame, prevPts[i], nextPts[i], cv::Scalar(0,255,0), 2);
    }
}

稠密光流计算每个像素的运动矢量。Farneback 算法是经典实现,但计算量很大。我记得在 Jetson Nano 上跑 640x480 的稠密光流,帧率只有 15fps 左右。

实用建议:如果只是检测障碍物运动方向,稀疏光流就够了。稠密光流适合做精细的运动分割,但算力要求高。我一般只在服务器端用稠密光流做离线分析。

4.3 基于深度学习的检测方法

深度学习来了之后,动态检测的玩法彻底变了。以前靠手工特征,现在靠卷积神经网络自动提取。

主流做法分两类:

  • 端到端检测:用 YOLO、SSD 等目标检测网络直接识别障碍物,然后结合多帧信息判断是否运动
  • 光流+检测:用 FlowNet 等网络直接预测光流,再结合检测结果

我个人更推荐第一种。YOLOv8 在嵌入式设备上也能跑到 30fps,检测精度还高。你想想看,以前用帧差法只能知道「有东西在动」,现在用 YOLO 能直接告诉你「那是个行人,朝东走」。

# YOLOv8 动态检测示例(Python)
from ultralytics import YOLO

model = YOLO('yolov8n.pt')  # 轻量版,适合嵌入式

results = model(frame)
for r in results:
    boxes = r.boxes
    for box in boxes:
        cls = int(box.cls[0])
        conf = float(box.conf[0])
        if conf > 0.5 and cls in [0, 1, 2]:  # 人、自行车、汽车
            x1, y1, x2, y2 = box.xyxy[0]
            cv2.rectangle(frame, (x1,y1), (x2,y2), (0,255,0), 2)

我的经验:纯深度学习方案有个坑——它只能检测训练集里出现过的类别。有一次项目里遇到叉车托盘,YOLO 死活识别不出来。后来我加了光流辅助判断:检测不到类别没关系,只要有运动区域就标记为「未知障碍物」。

深度学习的优势是精度高、泛化能力强。代价是需要 GPU、需要标注数据、模型部署也麻烦。但说实话,现在边缘计算设备越来越强,这个代价越来越值得付。

4.4 多传感器融合检测

单一传感器总有短板。相机怕暗、怕逆光;激光雷达怕雨雪、怕反射;超声波精度低、距离近。多传感器融合,就是让它们互相补台。

我做过一个项目,AGV 在仓库里运行。白天用相机做动态检测没问题,但晚上光线不足时,相机基本废了。后来加了 16 线激光雷达,用点云做运动检测,问题解决。

融合策略一般分三个层次:

  • 数据级融合:直接把图像和点云对齐,在像素级别融合。精度高,但计算量大
  • 特征级融合:分别提取图像特征和点云特征,再融合。折中方案
  • 决策级融合:各自独立检测,最后投票或加权。实现简单,鲁棒性好

我个人习惯用决策级融合。为什么?因为每个传感器都有自己的检测结果,融合时容易 debug。如果某个传感器坏了,其他传感器还能工作,系统不会完全瘫痪。

// 决策级融合伪代码
struct Detection {
    float x, y, w, h;
    float confidence;
    int source;  // 0: camera, 1: lidar, 2: radar
};

std::vector<Detection> cameraDetections = detectFromCamera(frame);
std::vector<Detection> lidarDetections = detectFromLidar(pointCloud);

// 融合策略:IOU 匹配 + 置信度加权
for (auto& cam : cameraDetections) {
    for (auto& lid : lidarDetections) {
        float iou = computeIOU(cam, lid);
        if (iou > 0.5) {
            // 融合结果:取两者置信度最大值
            Detection fused;
            fused.x = (cam.x + lid.x) / 2;
            fused.y = (cam.y + lid.y) / 2;
            fused.confidence = max(cam.confidence, lid.confidence);
            fused.source = 3;  // 融合源
            finalDetections.push_back(fused);
        }
    }
}

避坑指南:多传感器融合最大的坑是时间同步。相机 30fps,激光雷达 10fps,毫米波雷达 20fps。如果不做时间对齐,融合结果就是「关公战秦琼」。我曾经吃过这个亏,后来用 ROS 的 message_filters 做时间同步,问题解决。

还有一个容易被忽略的问题:传感器标定。相机和激光雷达的外参如果不准,融合出来的障碍物位置会偏移。我建议每季度做一次标定校验,尤其是机器人经过剧烈震动后。

总结一下:帧差法和背景建模适合简单场景、算力受限的场合;光流法适合需要运动矢量的应用;深度学习是当前的主流,精度最高;多传感器融合是工业级方案的标配,稳定可靠。

实际项目中,我通常这样搭配:用深度学习做目标检测,用光流做运动跟踪,用激光雷达做距离验证。三种方法互补,效果远好于单一方案。


专注资料整理