ROS2双目相机标定与测距全流程详解:从原理到实践
本文将带你全面了解双目视觉测距原理,并通过ROS2平台实现相机标定和距离测量,让普通读者也能掌握这项关键技术。
一、为什么需要双目相机标定?
双目视觉模仿人类双眼感知深度的原理,通过视差计算实现测距。但相机镜头存在畸变,两个相机的位置也不可能完全平行。因此,我们需要相机标定来解决三个核心问题:
- 镜头畸变校正:消除鱼眼效应、径向畸变等光学缺陷
- 相对位置标定:确定两个相机间的精确空间关系
- 内外参数获取:建立像素坐标与世界坐标的映射关系
未经标定的双目系统就像未校准的尺子,测量结果不可靠。标定后,测距精度可达毫米级,为机器人导航、三维重建等应用奠定基础。
二、操作流程详解
1、硬件准备:选购合适的双目相机
推荐选择基线固定(如60mm)的工业级USB双目相机:
选购要点:
- 全局快门优于卷帘快门(减少运动模糊)
- 同步触发功能(确保左右图像同时捕获)
- 固定基线设计(标定参数稳定)
2、环境搭建:ROS2基础环境
参考:在RK3588上部署ROS2与ORB-SLAM3实现Gazebo小车自主导航-环境搭建过程
安装标定工具包:
# 替换<humble>为你的ROS2版本
sudo apt install ros-<ros_distro>-camera-calibration
3、棋盘格标定板制作
使用在线生成器创建棋盘格:calib.io
制作要点:
- 选择9x7格(角点数量=8x6)
- 方格尺寸建议20-30mm(A4纸打印)
- 使用哑光纸避免反光
- 保持标定板平整
用A4纸打印
4. 相机数据采集与预处理
4.1 验证相机输出格式
ffmpeg -f v4l2 -video_size 1280x480 -i /dev/video2 -vframes 1 out.bmp
分辨率解析:
- 1280x480 = 左(640x480) + 右(640x480)
- 类似格式:2560x720 = 左(1280x720)+右(1280x720)
4.2 启动ROS2相机节点
# 创建配置文件:
cat> params_1.yaml <<-'EOF'
/**:
ros__parameters:
video_device: "/dev/video2"
framerate: 25.0
io_method: "mmap"
frame_id: "camera"
pixel_format: "mjpeg2rgb"
av_device_format: "YUV422P"
image_width: 1280
image_height: 480
camera_name: "test_camera"
EOF
#启动节点:
ros2 run usb_cam usb_cam_node_exe --ros-args --params-file ./params_1.yaml
4.3 图像分割节点开发
cat> split_node.py <<-'EOF'
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image, CameraInfo
from cv_bridge import CvBridge
import cv2
class StereoImageSplitter(Node):
def __init__(self):
super().__init__('stereo_image_splitter')
# 声明参数
self.declare_parameter('input_topic', '/image_raw')
self.declare_parameter('output_left', 'left/image_raw')
self.declare_parameter('output_right', 'right/image_raw')
self.declare_parameter('split_width', 640)
self.declare_parameter('publish_camera_info', True)
# 获取参数
input_topic = self.get_parameter('input_topic').value
output_left = self.get_parameter('output_left').value
output_right = self.get_parameter('output_right').value
self.split_width = self.get_parameter('split_width').value
publish_info = self.get_parameter('publish_camera_info').value
# 创建订阅和发布
self.subscription = self.create_subscription(
Image,
input_topic,
self.callback,
25)
self.left_pub = self.create_publisher(Image, output_left, 25)
self.right_pub = self.create_publisher(Image, output_right, 25)
if publish_info:
self.left_info_pub = self.create_publisher(CameraInfo, 'left/camera_info', 25)
self.right_info_pub = self.create_publisher(CameraInfo, 'right/camera_info', 25)
self.create_timer(0.1, self.publish_camera_info)
self.bridge = CvBridge()
self.get_logger().info(f'Stereo splitter ready. Splitting at {
self.split_width}px')
def publish_camera_info(self):
"""发布相机标定信息(简化版)"""
# 创建相机信息消息
left_info = CameraInfo()
left_info.header.stamp = self.get_clock().now().to_msg()
left_info.header.frame_id = "left_camera"
left_info.width = self.split_width
left_info.height = 480 # 根据实际调整
right_info = CameraInfo()
right_info.header.stamp = left_info.header.stamp
right_info.header.frame_id = "right_camera"
right_info.width = self.split_width
right_info.height = 480
# 发布
self.left_info_pub.publish(left_info)
self.right_info_pub.publish(right_info)
def callback(self, msg):
try:
# 转换为OpenCV格式