本教程专为 ROS2 学习者设计,补充了市面上大量 ROS1 资料缺失的 ROS2 + OpenCV 实践内容。基于 ROS2 Humble 和 OpenCV 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、原版代码存在问题(踩坑汇总)
- 追踪器初始化帧不一致
回调内临时帧用于初始化追踪器,帧内存引用异常,导致选完框按s无任何追踪反应。 - OpenCV窗口与ROS图像流不同步
鼠标回调频繁重建窗口,画面卡顿、选框失效。 - 选区判定阈值太小
微小拖动也能判定选区,容易误选无效区域。 - 状态逻辑混乱
追踪状态、选框状态互相干扰,按下启动键无响应。 - 无全局缓存当前帧
更新追踪使用实时回调帧,初始化与更新帧不统一,直接追踪失效。
3.3、正确使用流程
- 启动摄像头节点,保证话题
/image_raw正常发布 - 运行追踪python脚本
- 鼠标左键拖动框选需要追踪的物体
- 松开鼠标完成选框,按下键盘
s自动开始追踪 - 物体移动绿色框跟随,丢失目标可重新框选按
s重追 - 按下键盘
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、运行命令
- 启动摄像头
bash
ros2 run usb_cam usb_cam_node_exe
- 新开终端运行追踪脚本
bash
python3 object_tracker_fixed.py
3.6、追踪器选型建议
cv2.TrackerCSRT_create():精度高、慢速目标首选,适合静态识别ArUco、标志物cv2.TrackerKCF_create():速度更快,适合移动较快物体,轻微遮挡也可稳定追踪
3.7、成功运行现象
- 鼠标拖动出现蓝色预选框
- 选区完成按
s出现绿色追踪框+中心红点 - 终端打印开启追踪日志
- 物体移动框体自动跟随,丢失目标文字提示丢失
成功运行后如下:

输出日志:

四、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 操作步骤
-
在 Ubuntu 虚拟机中启动 HTTP 服务器
bashcd ~/dev_ws/src/ros2_21_tutorials/learning_cv/learning_cv # 进入照片所在目录 python3 -m http.server 8000终端会显示
Serving HTTP on 0.0.0.0 port 8000 ... -
查看虚拟机 IP 地址
bashhostname -I | awk '{print $1}'假设输出为
192.168.1.100。 -
在 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 服务器是最快捷的跨系统方法。