ROS2 + OpenCV 实战教程:人脸识别、物体跟踪、ArUco 二维码识别初级

本教程专为 ROS2 学习者设计,补充了市面上大量 ROS1 资料缺失的 ROS2 + OpenCV 实践内容。基于 ROS2 HumbleOpenCV 4.5.4,提供可直接运行的代码和避坑指南。

📑 目录

  • 一、环境准备与安装
  • [二、人脸识别(Haar Cascade)](#二、人脸识别(Haar Cascade))
  • [三、ROS2 OpenCV物体跟踪](#三、ROS2 OpenCV物体跟踪)
  • [四、ArUco 二维码识别](#四、ArUco 二维码识别)
  • [五、将虚拟机文件快速传输到 Windows](#五、将虚拟机文件快速传输到 Windows)
  • 六、常见问题与解决方案

一、环境准备与安装

1.1 安装 OpenCV 和 ROS2 桥接包

bash 复制代码
# 安装 OpenCV Python 版
sudo apt install python3-opencv

# 安装 ROS2 图像桥接工具(cv_bridge)
sudo apt install ros-humble-cv-bridge

# 安装摄像头驱动(推荐 v4l2_camera 或 usb_cam)
sudo apt install ros-humble-v4l2-camera ros-humble-usb-cam

1.2 验证安装

bash 复制代码
python3 -c "import cv2; print(cv2.__version__)"
pkg-config --modversion opencv4   # 应输出 4.5.4 或更高

二、人脸识别(Haar Cascade)

2.1 功能说明

使用 OpenCV 预训练的 Haar Cascade 分类器,实时检测摄像头画面中的人脸并用绿色方框标记。

2.2 完整代码 face_detect.py

python 复制代码
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import cv2
import os
import sys

class FaceDetector(Node):
    def __init__(self):
        super().__init__('face_detector')
        
        # 订阅摄像头话题
        self.subscription = self.create_subscription(
            Image,
            '/image_raw',  # 如果不行,试试 /camera/image_raw
            self.callback,
            10)
        
        self.bridge = CvBridge()
        self.get_logger().info('Face detector node started, waiting for images...')
        
        # 加载人脸检测模型
        cascade_path = '/usr/share/opencv4/haarcascades/haarcascade_frontalface_default.xml'
        if not os.path.exists(cascade_path):
            self.get_logger().error(f'Cascade file not found: {cascade_path}')
            self.get_logger().info('Trying alternative path...')
            cascade_path = '/usr/share/opencv/haarcascades/haarcascade_frontalface_default.xml'
            
        if os.path.exists(cascade_path):
            self.face_cascade = cv2.CascadeClassifier(cascade_path)
            self.get_logger().info(f'Loaded cascade from: {cascade_path}')
        else:
            self.get_logger().error('Cannot find haarcascade file!')
            self.face_cascade = None
        
        self.frame_count = 0
        
    def callback(self, msg):
        if self.face_cascade is None:
            return
            
        try:
            # ROS2图像 -> OpenCV图像
            cv_image = self.bridge.imgmsg_to_cv2(msg, 'bgr8')
            
            # 转换为灰度图
            gray = cv2.cvtColor(cv_image, cv2.COLOR_BGR2GRAY)
            
            # 人脸检测
            faces = self.face_cascade.detectMultiScale(
                gray, 
                scaleFactor=1.1, 
                minNeighbors=5,
                minSize=(30, 30)
            )
            
            # 画框
            for (x, y, w, h) in faces:
                cv2.rectangle(cv_image, (x, y), (x+w, y+h), (0, 255, 0), 2)
                # 可加文字标签
                cv2.putText(cv_image, 'Face', (x, y-10), 
                           cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
            
            # 显示结果
            cv2.imshow('Face Detection - ROS2', cv_image)
            cv2.waitKey(1)
            
            # 每30帧打印一次日志
            self.frame_count += 1
            if self.frame_count % 30 == 0 and len(faces) > 0:
                self.get_logger().info(f'Detected {len(faces)} face(s)')
                
        except Exception as e:
            self.get_logger().error(f'Error: {e}')

def main(args=None):
    rclpy.init(args=args)
    node = FaceDetector()
    
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()
        rclpy.shutdown()
        cv2.destroyAllWindows()

2.3 运行步骤

bash 复制代码
# 终端1:启动摄像头(发布 /image_raw)
 ros2 run usb_cam usb_cam_node_exe
# 终端2:运行人脸检测
python3 face_detect.py

2.4 成功标志

  • 弹出窗口实时显示摄像头画面
  • 人脸区域被绿色方框标出
  • 终端每检测到人脸会打印日志
    如下图所示

    输出日志为:

三、ROS2 OpenCV物体跟踪

3.1、功能说明

基于 ROS2 + OpenCV 实现手动鼠标框选目标物体 ,按下s开始实时追踪,画面绘制追踪框与中心红点,支持丢帧重选、q键退出,适配/image_raw摄像头图像流。

3.2、原版代码存在问题(踩坑汇总)

  1. 追踪器初始化帧不一致
    回调内临时帧用于初始化追踪器,帧内存引用异常,导致选完框按s无任何追踪反应。
  2. OpenCV窗口与ROS图像流不同步
    鼠标回调频繁重建窗口,画面卡顿、选框失效。
  3. 选区判定阈值太小
    微小拖动也能判定选区,容易误选无效区域。
  4. 状态逻辑混乱
    追踪状态、选框状态互相干扰,按下启动键无响应。
  5. 无全局缓存当前帧
    更新追踪使用实时回调帧,初始化与更新帧不统一,直接追踪失效。

3.3、正确使用流程

  1. 启动摄像头节点,保证话题/image_raw正常发布
  2. 运行追踪python脚本
  3. 鼠标左键拖动框选需要追踪的物体
  4. 松开鼠标完成选框,按下键盘s 自动开始追踪
  5. 物体移动绿色框跟随,丢失目标可重新框选按s重追
  6. 按下键盘q直接关闭程序退出

3.4 完整代码 object_tracker_fixed.py

python 复制代码
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import cv2
import numpy as np

class ObjectTrackerFixed(Node):
    def __init__(self):
        super().__init__('object_tracker_fixed')
        
        self.subscription = self.create_subscription(Image, '/image_raw', self.callback, 10)
        self.bridge = CvBridge()
        
        # 追踪状态变量
        self.tracker = None
        self.tracking = False
        self.selecting = False
        self.roi = None
        self.start_point = None
        self.end_point = None
        self.current_frame = None

        self.get_logger().info('✅ 目标追踪器已启动')
        self.get_logger().info('👉 鼠标框选目标 → 按 s 开始追踪 → 按 q 退出')

    def mouse_callback(self, event, x, y, flags, param):
        if not self.tracking:
            if event == cv2.EVENT_LBUTTONDOWN:
                self.selecting = True
                self.start_point = (x, y)
                self.end_point = (x, y)

            elif event == cv2.EVENT_MOUSEMOVE and self.selecting:
                self.end_point = (x, y)

            elif event == cv2.EVENT_LBUTTONUP:
                self.selecting = False
                self.end_point = (x, y)

                x1 = min(self.start_point[0], self.end_point[0])
                y1 = min(self.start_point[1], self.end_point[1])
                x2 = max(self.start_point[0], self.end_point[0])
                y2 = max(self.start_point[1], self.end_point[1])

                # 过滤过小无效选区
                if x2 - x1 > 20 and y2 - y1 > 20:
                    self.roi = (x1, y1, x2 - x1, y2 - y1)
                    self.get_logger().info(f'✅ 已选择追踪区域: {self.roi}')
                    self.get_logger().info('👉 按下 s 键开始追踪')

    def start_tracker(self):
        # 使用全局缓存帧初始化追踪器
        try:
            self.tracker = cv2.TrackerCSRT_create()
            self.tracker.init(self.current_frame, self.roi)
            self.tracking = True
            self.get_logger().info('🚀 成功开启目标追踪')
        except Exception as e:
            self.get_logger().error(f'追踪器初始化失败: {str(e)}')

    def callback(self, msg):
        cv_image = self.bridge.imgmsg_to_cv2(msg, 'bgr8')
        self.current_frame = cv_image.copy()
        display = cv_image.copy()

        # 实时绘制选框
        if self.selecting and self.start_point and self.end_point:
            cv2.rectangle(display, self.start_point, self.end_point, (255, 0, 0), 2)

        # 追踪逻辑
        if self.tracking and self.tracker is not None:
            success, bbox = self.tracker.update(self.current_frame)
            if success:
                x, y, w, h = map(int, bbox)
                cv2.rectangle(display, (x, y), (x + w, y + h), (0, 255, 0), 3)
                cv2.putText(display, "TRACKING", (x, y - 10), 
                            cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
                # 绘制目标中心红点
                center_x = x + w // 2
                center_y = y + h // 2
                cv2.circle(display, (center_x, center_y), 5, (0, 0, 255), -1)
            else:
                cv2.putText(display, "TARGET LOST", (10, 50), 
                            cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)

        # 界面操作提示
        cv2.putText(display, 'Drag box + press s start', (10, 30), 
                    cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
        cv2.putText(display, 'Press q to exit', (10, 60), 
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)

        cv2.imshow('Object Tracker', display)
        cv2.setMouseCallback('Object Tracker', self.mouse_callback)

        # 键盘按键监听
        key = cv2.waitKey(1) & 0xFF
        if key == ord('s') and self.roi is not None and not self.tracking:
            self.start_tracker()
        elif key == ord('q'):
            rclpy.shutdown()

def main(args=None):
    rclpy.init(args=args)
    node = ObjectTrackerFixed()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()
        rclpy.shutdown()
        cv2.destroyAllWindows()

if __name__ == '__main__':
    main()

3.5、运行命令

  1. 启动摄像头
bash 复制代码
ros2 run usb_cam usb_cam_node_exe
  1. 新开终端运行追踪脚本
bash 复制代码
python3 object_tracker_fixed.py

3.6、追踪器选型建议

  1. cv2.TrackerCSRT_create():精度高、慢速目标首选,适合静态识别ArUco、标志物
  2. cv2.TrackerKCF_create():速度更快,适合移动较快物体,轻微遮挡也可稳定追踪

3.7、成功运行现象

  1. 鼠标拖动出现蓝色预选框
  2. 选区完成按s出现绿色追踪框+中心红点
  3. 终端打印开启追踪日志
  4. 物体移动框体自动跟随,丢失目标文字提示丢失
    成功运行后如下:

    输出日志:

四、ArUco 二维码识别

4.1 功能说明

检测标准 ArUco 标记(6×6 字典,ID=0),输出标记 ID 并绘制绿色边界框。关键要点:必须使用打印的 ArUco 码,避免手机屏幕反光/摩尔纹干扰。

4.2 踩坑全记录

错误现象 原因 解决方案
检测不到任何标记 使用了棋盘格标定板 必须使用标准 ArUco 码
drawMarker 未定义 OpenCV 版本 API 变化 使用 cv2.aruco.drawMarker()
能检测到轮廓但无 ID 生成字典与检测字典不一致 统一使用 DICT_6X6_250
手机屏幕识别失败 摩尔纹、反光 打印到 A4 纸 或使用高亮屏幕
弱光下识别率低 对比度不够 增加图像预处理(直方图均衡化、CLAHE)

4.3 生成 ArUco 标记(aruco_create.py

python 复制代码
#!/usr/bin/env python3
import cv2
import numpy as np

aruco_dict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_6X6_250)
marker_id = 0
marker_size = 800          # 像素
border_size = 100

marker_image = np.zeros((marker_size, marker_size), dtype=np.uint8)
cv2.aruco.drawMarker(aruco_dict, marker_id, marker_size, marker_image, 1)

final_image = cv2.copyMakeBorder(marker_image, border_size, border_size,
                                 border_size, border_size, cv2.BORDER_CONSTANT, value=255)
cv2.imwrite('aruco_id0.png', final_image)
print("✅ ArUco 码已生成:aruco_id0.png")
cv2.imshow('ArUco Marker', final_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

4.4 终极检测代码(aruco_detect_final.py

python 复制代码
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import cv2
import numpy as np

class UltimateArucoDetector(Node):
    def __init__(self):
        super().__init__('ultimate_aruco_detector')
        self.subscription = self.create_subscription(Image, '/image_raw', self.callback, 10)
        self.bridge = CvBridge()

        self.aruco_dict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_6X6_250)
        self.parameters = cv2.aruco.DetectorParameters_create()

        # 宽松参数(提高弱光、倾斜识别率)
        self.parameters.adaptiveThreshConstant = 3.0
        self.parameters.minMarkerPerimeterRate = 0.008
        self.parameters.maxMarkerPerimeterRate = 1.0
        self.parameters.polygonalApproxAccuracyRate = 0.15
        self.parameters.errorCorrectionRate = 0.8

        self.get_logger().info('🚀 ArUco 检测器已启动(字典:DICT_6X6_250)')

    def callback(self, msg):
        cv_image = self.bridge.imgmsg_to_cv2(msg, 'bgr8')
        gray = cv2.cvtColor(cv_image, cv2.COLOR_BGR2GRAY)

        # 增强对比度
        gray = cv2.equalizeHist(gray)
        clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
        gray = clahe.apply(gray)

        corners, ids, rejected = cv2.aruco.detectMarkers(gray, self.aruco_dict, parameters=self.parameters)

        if ids is not None:
            cv2.aruco.drawDetectedMarkers(cv_image, corners, ids)
            for i, marker_id in enumerate(ids):
                self.get_logger().info(f'🟢 识别成功!ArUco ID: {marker_id[0]}')
                c = corners[i][0]
                cv2.putText(cv_image, f'ID: {marker_id[0]}',
                           (int(c[0][0]), int(c[0][1]) - 10),
                           cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)

        cv2.imshow('ArUco Detection', cv_image)
        cv2.waitKey(1)

def main(args=None):
    rclpy.init(args=args)
    node = UltimateArucoDetector()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()
        rclpy.shutdown()
        cv2.destroyAllWindows()

if __name__ == '__main__':
    main()

4.5 运行与测试

bash 复制代码
# 步骤1:生成 ArUco 码
python3 aruco_create.py
# 将生成的 aruco_id0.png 打印在 A4 纸上(建议不缩放)

# 步骤2:启动摄像头
ros2 run usb_cam usb_cam_node_exe

# 步骤3:运行检测器
python3 aruco_detect_final.py

4.6 识别成功标志

  • 摄像头画面中 ArUco 码被绿色完整边框包围。
  • 边框上方显示 ID: 0
  • 终端持续打印 🟢 识别成功!ArUco ID: 0
    成功显示如下:

    输出日志如下:

五、将虚拟机文件快速传输到 Windows

5.1 问题背景

虚拟机共享文件夹功能可能失效(如 VirtualBox 增强功能损坏),使用 HTTP 服务器 是最稳定、无需额外配置的方法。

5.2 操作步骤

  1. 在 Ubuntu 虚拟机中启动 HTTP 服务器

    bash 复制代码
    cd ~/dev_ws/src/ros2_21_tutorials/learning_cv/learning_cv   # 进入照片所在目录
    python3 -m http.server 8000

    终端会显示 Serving HTTP on 0.0.0.0 port 8000 ...

  2. 查看虚拟机 IP 地址

    bash 复制代码
    hostname -I | awk '{print $1}'

    假设输出为 192.168.1.100

  3. 在 Windows 浏览器中访问

    打开任意浏览器,地址栏输入:
    http://192.168.1.100:8000

    即可看到 Ubuntu 当前目录下的所有文件,点击即可下载。

5.3 优点

  • 无需安装任何软件
  • 支持批量下载(浏览器自带保存功能)
  • 不依赖虚拟机网络配置(NAT 或桥接均可)
  • 速度通常比 U 盘复制更快

六、常见问题与解决方案

问题 可能原因 解决方法
cv2.aruco 模块缺失 OpenCV 版本不含 contrib pip install opencv-contrib-python
摄像头打不开(灰色画面) 设备被占用或权限不足 sudo chmod 666 /dev/video* 或重启虚拟机
cv_bridge 导入错误 NumPy 版本不匹配 pip install "numpy<2"
追踪器按 s 无反应 未正确框选 ROI 或 OpenCV 版本低 确认选区宽>20像素;改用 cv2.TrackerCSRT_create()
ArUco 识别不到手机屏幕上的码 摩尔纹/反光 将标记打印到纸上 或用哑光屏幕

总结

本教程提供了三个完整的 ROS2 + OpenCV 视觉应用,全部代码基于 ROS2 Humble 和 OpenCV 4.5.4 测试通过。关键经验:

  • 人脸识别:直接使用 Haar 级联,注意光照和尺度。
  • 物体跟踪:务必缓存当前帧,使用 CSRT 或 KCF 跟踪器。
  • ArUco 识别:打印物理标记,统一字典,预处理增强对比度。
  • 文件传输:HTTP 服务器是最快捷的跨系统方法。
相关推荐
He少年2 小时前
【AI路径代理与业务接入 — 成功失败感悟】
人工智能·c#
爱炸薯条的小朋友2 小时前
C#的详细应用和讲解池化为什么能提升 OpenCvSharp / Mat 的整体效率
开发语言·opencv·c#
Keano Reurink2 小时前
让AI Agent学会“查资料“:我搭了一套搜索引擎工具链
人工智能·搜索引擎
liguojun20252 小时前
软硬一体智慧场馆系统推荐——助力场馆数字化高效升级
java·大数据·人工智能·物联网·1024程序员节
kyle~2 小时前
机器人感知---工业相机硬触发、时间戳同步( PTP)与 ROS2 驱动时间戳设计
linux·c++·机器人·ros2
阿里云大数据AI技术2 小时前
从图片到声音、视频:MaxCompute MaxFrame 多模态算子模块,让海量多模态数据_跑_起来
大数据·人工智能·阿里云·多模态·maxcompute
云道轩2 小时前
Langflow 1.9 发布:Langflow 助手、流程 DevOps 工具包,以及面向 IDE 和编码代理的 MCP 支持
人工智能·智能体
l1t2 小时前
DeepSeek总结的DuckDB CLAUDE.md
数据库·人工智能
liu-yonggang2 小时前
ROS2 性能优化指南
性能优化·ros2