目录
一、ROS 视觉生态总览
ROS 提供了一套完整的从相机驱动接入 → 图像预处理 → 标定 → 2D/3D 感知算法 → 坐标融合的端到端视觉开发栈,借助标准化 sensor_msgs 消息类型(Image、CameraInfo、PointCloud2、CompressedImage),所有视觉组件可以任意组合,算法开发者无需关心硬件差异。整体管线如下:
· sensor_msgs/Image:原始图像(raw 像素数组 + height/width/encoding)
· sensor_msgs/CameraInfo:相机内参(K/D/R/P 矩阵 + 畸变模型)
· sensor_msgs/CompressedImage:JPEG/PNG 压缩图像(节省带宽)
· sensor_msgs/PointCloud2:3D 点云(XYZ/XYZRGB/XYZIRT 等字段)
· sensor_msgs/Image + CameraInfo 配对:两者
header.frame_id 必须一致,标定结果通过 CameraInfo 分发
二、核心组件一:相机驱动适配层
ROS 为各类主流工业/消费级相机提供了开箱即用的驱动节点,所有驱动统一遵循 image_transport + camera_info_manager 规范,对外暴露一致的话题名和参数接口。
usb_cam / libuvc_camera
UVC 协议通用 USB 相机(笔记本摄像头、普通工业 USB 相机),支持 YUYV/MJPEG 格式。
realsense2_camera
Intel RealSense D400/L500 系列深度相机官方 ROS Driver,同步输出 RGB + 深度 + IMU + 点云。
astra_camera
奥比中光 Astra/Orbbec 系列结构光深度相机驱动,支持外参标定话题发布。
camera_aravis / prosilica
GigE Vision / GenICam 协议工业相机(Basler 面阵/线扫、AVT、Allied Vision)。
flir_camera_driver
FLIR(原 Point Grey)Blackfly、Grasshopper、Chameleon 系列 USB3/GigE 工业相机。
daheng_camera / hik_camera
国产大恒图像、海康机器人工业相机驱动,覆盖国内制造业主流品牌。
o3de_camera / zed-ros2-wrapper
ZED/ZED2i/Stereolabs 双目立体深度相机,SDK 内置稠密深度 + 人体骨架 + VIO。
livox_ros_driver2 / ouster-ros
激光雷达相机融合:Livox Mid-70、Ouster OS 系列点云 + 图像像素级对齐(pixel-to-point)。
2.1 image_transport:多传输策略透明切换
ROS 视觉节点间不应直接使用 sensor_msgs/Image 裸订阅,而必须通过 image_transport API,它能自动在以下传输模式间切换:
| 传输插件 | 编码 | 带宽占用 | 延迟 | 适用场景 |
|---|---|---|---|---|
raw |
未压缩 BGR8/Mono8 | 极高(4K 30fps ≈ 1.2GB/s) | 最低(零编解码) | 本机进程间通信、共享内存 |
compressed |
JPEG / PNG | 低(压缩比 5~30x) | 中(CPU 编解码) | 跨机器以太网传输、网络录制 |
theora |
Theora 视频流 | 极低 | 较高(视频帧缓冲) | 低带宽广域网监控回传 |
h264 / h265(社区) |
NVENC / VA-API 硬件编码 | 比 JPEG 再低 2~4x | 中(需硬件支持) | 多路工业相机并发录制 |
ffmpeg(社区) |
H264/H265 + RTSP | 极低 | 中 | 接入现有视频监控系统 |
三、核心组件二:image_pipeline 图像预处理流水线
image_pipeline 是 ROS 官方维护的一组视觉预处理节点包,专门负责相机采集后的常规图像处理,几乎每一个 ROS 视觉系统都会用到。它按以下顺序串联:
camera_calibration 输出标定文件 → 被 camera_info_manager 加载
出厂标定一次得到 YAML 内参文件(K、D、R、P 矩阵 + 畸变模型),驱动启动时自动加载并发布到 /camera/camera_info 话题,后续所有节点订阅它读取内参。
image_proc 节点:去畸变 + 颜色空间转换
订阅 image_raw + camera_info,发布 image_rect(去畸变后图像)、image_rect_color(BGR8 彩色)、image_mono(灰度)。省去每个算法节点重复写 cv::undistort 的代码。
stereo_image_proc:双目立体匹配输出深度
订阅左右目 left/image_raw + right/image_raw 及其 CameraInfo,运行 Block Matching(BM)或 Semi-Global Block Matching(SGBM)算法,输出 /disparity(视差图)→ /points2(重建 3D 点云)。
depth_image_proc:深度图 → 点云 / 注册
订阅 depth/image_rect_raw + 可选 rgb/image_rect_color,输出 /points2 XYZRGB 点云、/registered_depth 深度配准到彩色坐标系。
image_rotate / image_view:辅助节点
image_rotate 配合 IMU 实时校正相机滚动角;image_view 提供 OpenCV GUI 窗口查看任意图像话题,可一键截图保存。
四、核心组件三:相机标定工具链
视觉系统精度 90% 取决于标定质量。ROS 官方提供了成熟的 GUI 标定工具:
4.1 camera_calibration:单目/双目内参标定
- 标定板支持:棋盘格(Chessboard)、圆网格(Circles Grid)、Apriltag 标定板(Aprilgrid)
- 标定模型:Plumb Bob 5 参数畸变(k1,k2,k3,p1,p2)、Rational 8 参数模型(鱼眼镜头)
- 交互流程:启动
cameracalibrator节点后,GUI 实时显示 X/Y/Size/Skew 四维采样进度条,覆盖范围满格后 CALIBRATE 按钮亮起,运行优化后可直接保存 YAML 文件或上传到参数服务器。
4.2 Kalibr:多相机 + IMU 联合标定
当系统涉及多相机(双目、环视四目)、相机与 IMU(VIO、SLAM)时,Kalibr(ETH Zurich 开源)是业界标准:
- 多相机外参标定(cam0 → cam1 → cam2 变换矩阵)
- Camera-IMU 时间偏移标定(td,图像曝光时间与 IMU 时钟的时间差)
- IMU 内参标定(加速度计/陀螺仪零偏、尺度因子、轴不正交误差)
- 卷帘快门(Rolling Shutter)相机标定(逐行曝光时间线)
五、核心组件四:3D 深度相机与点云处理
3D 视觉赋予机器人空间感知能力,ROS 将深度相机与 PCL(Point Cloud Library)深度结合。
5.1 三类深度相机技术对比
| 技术路线 | 代表产品 | 原理 | 有效距离 | 精度 | 适用 |
|---|---|---|---|---|---|
| 主动立体(结构光) | RealSense D415/D435, Orbbec Gemini2 | IR 散斑投影 + 左右双 IR 相机立体匹配 | 0.1 ~ 3m | 1% ~ 2% | 近距离抓取、桌面级 3D 识别 |
| iToF(飞行时间) | RealSense L515, Kinect Azure, Mech-Mind | 调制红外光飞行时间测量相位差 | 0.2 ~ 5m | 0.5% ~ 1% | 中距离物流抓取、体积测量 |
| 被动双目(可见光) | ZED 2i、自定义基线双工业相机 | 自然光 + SGBM/半全局匹配 | 0.5m ~ 20m | 与基线成反比 | 长距离户外、无人机避障 |
5.2 Perception 包(PCL ROS Wrapper)
ROS perception_pcl 提供了完整的 PCL 功能节点:
pcl_ros::VoxelGrid
点云下采样(降体素滤波),典型从百万级 → 万级,显著降低后续算法耗时。
pcl_ros::PassThrough
直通滤波,按 X/Y/Z 轴裁剪感兴趣区域(ROI),移除过远无效点。
pcl_ros::StatisticalOutlierRemoval
统计滤波移除离群散点(深度相机飞点噪声),平滑点云边界。
pcl_ros::EuclideanClusterExtraction
欧氏聚类分割点云中不同物体实例,输出每个物体的独立点云簇(BBox)。
pcl_ros::SACSegmentation
pcl_ros::NormalEstimation
计算点云法向量,为 ICP 配准、抓取检测提供输入。
六、核心组件五:感知算法集成
ROS 几乎集成了计算机视觉领域所有主流算法,开箱即用:
vision_opencv (cv_bridge)
ROS Image ↔ OpenCV cv::Mat 双向转换桥接。是写任何自定义视觉节点的第一行代码。
darknet_ros / yolov8_ros
YOLO 系列目标检测,GPU 推理(TensorRT/ONNX)实时发布 BoundingBox 数组 + 可视化图像。
segment_anything_ros / mask2former
SAM / M2F 语义/实例分割 ROS 封装,输出 2D 分割 mask + 3D 点云实例。
apriltag_ros / aruco_ros
AprilTag / ArUco 二维码 6D 位姿估计,单目即可 3D 定位,广泛用于引导抓取。
find_object_2d
基于 SIFT/SURF/ORB 特征匹配的物体识别与姿态解算,无需训练模板即可识别已知工件。
OpenFace / face_recognition
人脸识别与人脸关键点检测,服务机器人访客识别、情绪识别功能底座。
easy_handeye
手眼标定 GUI 工具,Eye-to-Hand / Eye-in-Hand 两种构型,输出 TF 静态变换。
rtabmap_ros / ORB_SLAM3-ROS
视觉/视觉惯性 SLAM 建图定位节点,发布相机轨迹 + OctoMap / 3D 点云地图。
七、核心组件六:手眼标定与 TF 坐标融合
视觉感知的 2D 坐标最终需要转换到机器人坐标系(机械臂基坐标系、AGV 里程计坐标系)才能驱动机器人执行,这一转换矩阵由手眼标定(Hand-Eye Calibration)求解。ROS 支持两种构型:
求解:base ↔ camera
求解:tool0 ↔ camera
② Tsai-Lenz / Park-Martin 算法求解 AX=XB
③ 验证误差(典型 ≤ 0.5mm / 0.1°)
④ 发布 static_transform_publisher
base_link → tool0 → camera → tag,对比 tag 回 base 的位姿与实际工作台坐标差。平移误差应 < 1mm,旋转误差 < 0.2°。
八、六大典型应用场景
📐 场景一:尺寸测量与外观检测 QC 品质管控
工业质检站中,5MP 面阵相机 + 双远心镜头对注塑件/五金件进行亚像素级尺寸测量和外观缺陷(划痕、缺料、多料)检测。
- Halcon / OpenCV findContours + cv::minAreaRect 做尺寸测量
- 图像畸变校正由 image_proc 完成,节点代码仅 30 行
- OK/NG 结果通过 Service 上报 MES 系统,NG 件触发剔除气缸
- 典型节拍:300ms/件(含拍照 + 推理 + 结果上传)
📦 场景二:3D 视觉无序抓取(Bin Picking)物流 & 仓储
料箱内散乱堆放的工件(汽车零部件、电商包裹)由 3D 结构光相机拍摄点云,GPD/Deep Learning 检测抓取位姿,机械臂抓取。
- Realsense D435i / 梅卡曼德 M-Eye 深度相机发布 PointCloud2
- VoxelGrid → RANSAC 地面分割 → Euclidean 聚类,得到每个工件独立点云
- GPD (Grasp Pose Detection) 生成候选抓取,MoveIt! 验证可达性
- 典型成功率:99.5%+,节拍 6~12 秒/件
🏷️ 场景三:条码 / 二维码识别追溯 产线追溯
汽车零部件、3C 电子产品单件唯一码(DPM 激光雕刻码)读取,绑定工艺参数入库追溯。
zbar_ros/livox_ros条码识别节点,订阅image_rect_color话题- 多相机(6~12 个)环视覆盖产品六面,只要一个相机读到即通过
- 失败自动触发气缸翻转重试 3 次,最终 NG 分流
- 识别记录自动写入 SQL 数据库,满足 IATF16949 可追溯要求
🚗 场景四:自动驾驶多传感器融合感知 Robotaxi / ADAS
自动驾驶车辆上多路相机(前视 800 万 + 4× 环视 200 万 + DMS)与激光雷达、毫米波雷达时间同步、空间融合。
image_proc去畸变、环绕 4 路拼接 BirdEyeView- YOLO-OpenPCDet 级联:2D BBox 投影到 BEV → 3D 聚类补全深度
- Kalibr 标定相机-IMU 外参与时间偏移,保障 VINS-Mono 定位精度
- Camera-LiDAR 联合标定:
autoware.universeCalibration Toolkit
🤖 场景五:视觉引导精确定位 装配 & 机床
机床上下料、精密装配场景中,相机对工件进行 6D 位姿估计,补偿定位夹具 ±5mm 的定位误差,机械臂 ±0.02mm 精准插入。
- AprilTag 6D 定位(
apriltag_ros,精度 ±0.1mm@100mm 工作距) - Eye-to-Hand 标定:
easy_handeye20 样本求解相机-工作台变换 - TF2 级联变换:tag → camera → base → tool0,得到机械臂目标位姿
- MoveIt! Cartesian 直线插入轨迹 + 力控接触检测完成装配
🏥 场景六:医疗影像辅助与手术导航 生命科学
超声设备、内窥镜、OCT 光学相干断层扫描图像通过 ROS 接入 AI 辅助诊断系统。
- 医疗相机 SDK 自定义 Driver 发布 DICOM → ROS Image 格式转换
- 3D Slicer 插件 +
slicer_ros_bridge实现 MRI/CT 三维可视化 - NDI 光学跟踪系统
nditools_ros输出手术器械 6D 位姿 - ROS2 + DDS Security 保障医疗数据传输加密合规(HIPAA/GDPR)
九、完整应用示例:工业质检工作站
以一个典型的「两相机 + 六轴机器人 + PLC」工业外观质检工作站为例,展示 ROS 视觉架构在产线中的完整落地:
输出 Detection2DArray + 缺陷 ROI 图像
绑定缺陷检测结果
MQTT → MES 质检结果入库
远程浏览器抽检
9.1 产线运行时序
- 工件到位:PLC 光电开关 → 发布
/trigger_capture std_msgs/Empty话题 - 双相机曝光同步:hardware_trigger 模式下,PLC 同时发送 GigE Line1 触发信号
- 推理并行:两个 TensorRT GPU 进程并行推理(单图 45ms,总计 < 70ms)
- 结果发布:检测缺陷类型、位置、置信度写入
/qc/result自定义消息 - 决策:SN 码识别成功 × 两个相机都无缺陷 → OK;其他任意情况 → NG
- 执行:OK 放行,NG 延迟 2 秒触发气缸剔除,并将两张原图存入 bag 审计
十、基础代码实战(C++ / Python / Launch)
10.1 C++:cv_bridge 图像订阅 + 物体识别
// C++ ROS2 示例:订阅 rect 彩色图像 → OpenCV 二维码识别 → 发布 6D Pose #include <rclcpp/rclcpp.hpp> #include <image_transport/image_transport.hpp> #include <cv_bridge/cv_bridge.h> #include <opencv2/objdetect/aruco_detector.hpp> #include <geometry_msgs/msg/pose_stamped.hpp> #include <sensor_msgs/msg/camera_info.hpp> #include <tf2_ros/transform_broadcaster.h> class ArucoDetectorNode : public rclcpp::Node { public: ArucoDetectorNode() : Node("aruco_detector") { // 订阅去畸变后的彩色图像和相机内参(必须是 rect 图像) img_sub_ = image_transport::create_subscription( this, "/camera/image_rect_color", std::bind(&ArucoDetectorNode::imageCb, this, std::placeholders::_1), "raw", rmw_qos_profile_sensor_data); cam_info_sub_ = create_subscription<sensor_msgs::msg::CameraInfo>( "/camera/camera_info", 1, std::bind(&ArucoDetectorNode::camInfoCb, this, std::placeholders::_1)); pose_pub_ = create_publisher<geometry_msgs::msg::PoseStamped>("/tag_pose", 10); tf_broadcaster_ = std::make_unique<tf2_ros::TransformBroadcaster>(*this); // ArUco 字典加载:DICT_6X6_250 detector_ = cv::aruco::ArucoDetector::getPredefinedDictionary(cv::aruco::DICT_6X6_250); } private: void camInfoCb(const sensor_msgs::msg::CameraInfo::SharedPtr info) { cameraMatrix_ = cv::Mat(3, 3, CV_64F, (void*)info->k.data()).clone(); distCoeffs_ = cv::Mat(info->d.size(), 1, CV_64F, (void*)info->d.data()).clone(); } void imageCb(const sensor_msgs::msg::Image::ConstSharedPtr &msg) { if (cameraMatrix_.empty()) return; // 还没收到内参,跳过 // cv_bridge 零拷贝转换 ROS Image → OpenCV Mat(bgr8) auto cv_ptr = cv_bridge::toCvShare(msg, sensor_msgs::image_encodings::BGR8); // 检测 ArUco 码角点和 ID std::vector<std::vector<cv::Point2f>> corners, rejected; std::vector<int> ids; detector_.detectMarkers(cv_ptr->image, corners, ids, rejected); if (ids.empty()) return; // PnP 求解:已知码物理边长 tagSize(米),估计 6D 位姿 std::vector<cv::Vec3d> rvecs, tvecs; constexpr double tagSize = 0.05; // 5cm ArUco 码 cv::aruco::estimatePoseSingleMarkers(corners, tagSize, cameraMatrix_, distCoeffs_, rvecs, tvecs); // 发布第一个码的 6D 位姿 + TF geometry_msgs::msg::PoseStamped pose; pose.header = msg->header; // 沿用相机时间戳和 frame_id pose.pose.position.x = tvecs[0](0); pose.pose.position.y = tvecs[0](1); pose.pose.position.z = tvecs[0](2); // Rodrigues 旋转向量 → 四元数 cv::Mat R; cv::Rodrigues(rvecs[0], R); tf2::Matrix3x3 tfR(R.at<double>(0,0), R.at<double>(0,1), R.at<double>(0,2), R.at<double>(1,0), R.at<double>(1,1), R.at<double>(1,2), R.at<double>(2,0), R.at<double>(2,1), R.at<double>(2,2)); tf2::Quaternion q; tfR.getRotation(q); pose.pose.orientation.w = q.w(); pose.pose.orientation.x = q.x(); pose.pose.orientation.y = q.y(); pose.pose.orientation.z = q.z(); pose_pub_->publish(pose); // 同步发布 TF,便于 RViz 可视化:camera → tag_0 变换 geometry_msgs::msg::TransformStamped t; t.header = msg->header; t.child_frame_id = "aruco_tag_" + std::to_string(ids[0]); t.transform.translation.x = pose.pose.position.x; t.transform.translation.y = pose.pose.position.y; t.transform.translation.z = pose.pose.position.z; t.transform.rotation = pose.pose.orientation; tf_broadcaster_->sendTransform(t); } image_transport::Subscriber img_sub_; rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr cam_info_sub_; rclcpp::Publisher<geometry_msgs::msg::PoseStamped>::SharedPtr pose_pub_; std::unique_ptr<tf2_ros::TransformBroadcaster> tf_broadcaster_; cv::aruco::ArucoDetector detector_; cv::Mat cameraMatrix_, distCoeffs_; }; int main(int argc, char** argv) { rclcpp::init(argc, argv); rclcpp::spin(std::make_shared<ArucoDetectorNode>()); rclcpp::shutdown(); }
10.2 Python:深度相机点云下采样 + 地面分割
# Python ROS2 示例:订阅深度相机点云 → 下采样 → RANSAC 地面拟合 → 物体聚类 import rclpy from rclpy.node import Node from sensor_msgs.msg import PointCloud2 import pcl from pcl_ros import pointCloud2_to_array, array_to_pointCloud2 class BinPickingPreprocess(Node): def __init__(self): super().__init__("bin_picking_preprocess") # 订阅 Realsense 原始点云(XYZRGB) self.sub = self.create_subscription( PointCloud2, "/camera/depth/color/points", self.callback, 10) # 发布仅含工件的聚类点云(供抓取检测输入) self.pub_objects = self.create_publisher( PointCloud2, "/workspace/objects_cloud", 10) def callback(self, msg): # Step 1: ROS PointCloud2 → NumPy 数组 → PCL PointCloud pts_np = pointCloud2_to_array(msg) cloud = pcl.PointCloud_PointXYZRGB() cloud.from_array(pts_np) # Step 2: ROI 直通滤波(保留料箱 X=[-0.3,0.3], Y=[-0.2,0.2], Z=[0.1,0.9] 米) passthrough = cloud.make_passthrough_filter() for axis, limits in [('x', (-0.3, 0.3)), ('y', (-0.2, 0.2)), ('z', (0.1, 0.9))]: passthrough.set_filter_field_name(axis) passthrough.set_filter_limits(*limits) cloud = passthrough.filter() # Step 3: VoxelGrid 下采样到 2mm 分辨率(从 30 万 → 约 8 千点) vox = cloud.make_voxel_grid_filter() vox.set_leaf_size(0.002, 0.002, 0.002) cloud = vox.filter() # Step 4: RANSAC 平面拟合(料箱底面),取平面上方的「非地面点」 seg = cloud.make_segmenter() seg.set_model_type(pcl.SACMODEL_PLANE) seg.set_method_type(pcl.SAC_RANSAC) seg.set_distance_threshold(0.005) # 5mm 以内视为平面内点 inliers, coefficients = seg.segment() cloud_objects = cloud.extract(inliers, negative=True) # negative=True 即剔除地面 # Step 5: 欧氏聚类分割不同工件(距离阈值 1cm) tree = cloud_objects.make_kdtree() ec = cloud_objects.make_EuclideanClusterExtraction() ec.set_ClusterTolerance(0.01) ec.set_MinClusterSize(100) # 滤除 < 100 点的小散点 ec.set_MaxClusterSize(50000) ec.set_SearchMethod(tree) clusters = ec.Extract() # 为每个聚类上色后合并发布 colored = pcl.PointCloud_PointXYZRGB() colors = [(255,0,0),(0,255,0),(0,0,255),(255,255,0)] for i, cluster_indices in enumerate(clusters): color = colors[i % len(colors)] for idx in cluster_indices: pt = cloud_objects[idx] pt.rgb = (color[0] << 16) | (color[1] << 8) | color[2] colored.push_back(pt) # 发布:Pcl → NumPy → ROS PointCloud2 out_msg = array_to_pointCloud2(colored.to_array(), msg.header.frame_id) out_msg.header.stamp = msg.header.stamp self.pub_objects.publish(out_msg) def main(): rclpy.init() rclpy.spin(BinPickingPreprocess()) rclpy.shutdown()
10.3 Launch 文件:RealSense D435i + image_proc + RViz 一键启动
# launch/realsense_perception.launch.py:一台 D435i 完整视觉感知启动脚本 from launch import LaunchDescription from launch_ros.actions import Node, ComposableNodeContainer from launch_ros.descriptions import ComposableNode from ament_index_python.packages import get_package_share_directory import os def generate_launch_description(): rs_cfg = os.path.join(get_package_share_directory("realsense2_camera"), "config", "d435i.yaml") # Composable Node:将 Driver + image_proc 放入同一进程减少拷贝 perception_container = ComposableNodeContainer( name="perception_container", namespace="", package="rclcpp_components", executable="component_container", composable_node_descriptions=[ # ① RealSense D435i 驱动:彩色 + 深度 + IMU + 点云 ComposableNode( package="realsense2_camera", plugin="realsense2_camera::RealSenseNodeFactory", name="camera", parameters=[rs_cfg, { "enable_color": True, "enable_depth": True, "enable_infra1": False, "enable_pointcloud": True, "align_depth.enable": True, # 深度配准到彩色 "color_width": 640, "color_height": 480, "color_fps": 30, }], extra_arguments=[{'use_intra_process_comms': True}] ), # ② image_proc 彩色:去畸变 + 颜色转换(rect_color, rect_mono) ComposableNode( package="image_proc", plugin="image_proc::RectifyNode", name="color_rectify", remappings=[ ("image", "/camera/color/image_raw"), ("camera_info", "/camera/color/camera_info"), ("image_rect", "/camera/color/image_rect_color"), ], parameters=[{"interpolation": 1}], extra_arguments=[{'use_intra_process_comms': True}] ), # ③ depth_image_proc:深度图 → XYZRGB 点云 ComposableNode( package="depth_image_proc", plugin="depth_image_proc::PointCloudXyzrgbNode", name="depth_to_xyzrgb", remappings=[ ("depth_registered/image_rect", "/camera/aligned_depth_to_color/image_raw"), ("depth_registered/camera_info", "/camera/aligned_depth_to_color/camera_info"), ("rgb/image_rect_color", "/camera/color/image_rect_color"), ("rgb/camera_info", "/camera/color/camera_info"), ("/points", "/camera/depth/color/points"), ], extra_arguments=[{'use_intra_process_comms': True}] ), # ④ AprilTag 检测节点 ComposableNode( package="apriltag_ros", plugin="apriltag_ros::AprilTagNode", name="apriltag", parameters=[{ "tag_family": "tagStandard41h12", "tag_size": 0.05, "publish_tf": True, }], remappings=[ ("/image_rect", "/camera/color/image_rect_color"), ("/camera_info", "/camera/color/camera_info"), ], ), ] ) # ⑤ RViz 可视化:点云 + 图像 + TF + Marker rviz = Node( package="rviz2", executable="rviz2", arguments=["-d", os.path.join( get_package_share_directory("perception_tutorial"), "rviz", "realsense_perception.rviz")], output="screen" ) return LaunchDescription([perception_container, rviz])
十一、总结
✅ 永远用 image_transport 订阅图像,禁止直接裸订阅 sensor_msgs/Image,跨机器默认 compressed 插件
✅ 标定先行:上线前必须跑 camera_calibration(单目)/ Kalibr(多相机+IMU)/ easy_handeye(手眼)
✅ image_proc 零代码处理:去畸变、配准、色彩转换都交给官方节点,业务节点只订阅 rect 系列话题
✅ 组件化节点设计:预处理节点(下采样/ROI)与算法节点(检测/识别/定位)解耦,各自独立可单独替换
✅ TF 而非手动矩阵:坐标链全部走 TF2,禁止在代码里硬编码外参旋转矩阵
✅ QoS SensorData:图像/点云话题使用 keep_last(1) + best_effort 避免延迟堆积
✅ 回放调试:现场问题 90% 可通过 rosbag 录 30 秒 + 本地回放算法复现,比反复到现场省时百倍