多模态感知链路:从像素到语义的机器人可信感知流水线
1. 项目概述:为什么“从像素到语义”是具身智能的生死线
你有没有试过让一个机器人在陌生房间里找一把椅子?它摄像头里全是密密麻麻的RGB像素点,红的是沙发靠垫,灰的是地板反光,蓝的是窗帘褶皱——但“椅子”在哪?它不知道。它能算出每个像素的深度值,能框出一堆“疑似物体”,可这些框里哪个是“带四条腿、有靠背、供人坐的家具”?它依然没概念。这就是当前大多数机器人视觉系统的硬伤:看得见,但看不懂;测得准,但认不准。而“多模态感知:从像素到语义”这章标题,说的正是捅破这层窗户纸的关键一跃——不是把图像当图片看,而是当“世界说明书”读。
我带过三届机器人方向的毕设学生,90%卡在同一个环节:算法模型在测试集上mAP高达85%,一放到真实小车或机械臂上就频频误判。不是模型不行,是整个感知链路断了档。YOLO11检测出一个“person”框,Depth Anything V2给出这个框的深度图,Grounded SAM 2再把这个框抠成精细掩码,Open3D把所有这些数据融合进三维点云空间,最后ROS2用TF2坐标变换把它们统一到机器人本体坐标系下——这五个环节,环环相扣,缺一不可。漏掉标定,深度值就是错的;跳过坐标变换,检测框和点云永远对不上;没做多模态对齐,语义标签就只是贴在二维图上的浮层,对机器人决策毫无意义。所以这一章不是教你怎么调参,而是教你如何搭建一条“可信感知流水线”。它面向的不是纯算法研究员,而是真正要让机器人走出实验室、走进仓库、进入家庭的工程实践者。无论你是用树莓派5跑轻量部署,还是在x86服务器上跑高精度推理,核心逻辑都一样:像素是原料,语义是成品,中间那条由标定、深度、检测、分割、配准、融合构成的产线,才是你必须亲手焊牢的骨架。
2. 多模态感知链路设计与技术选型逻辑
2.1 为什么是这五项技术组合?而非其他方案?
先说结论:这个组合不是拍脑袋定的,而是我在过去三年里,在工业分拣、服务机器人导航、教育机器人平台三个场景中反复验证后收敛出的“最小可行感知栈”。它解决的是具身智能最基础也最顽固的矛盾——传感器异构性与决策同构性之间的鸿沟。
相机标定与坐标变换是地基。没有它,所有后续计算都是空中楼阁。我见过太多团队直接用厂商默认内参,结果机械臂抓取时偏差15cm。这不是算法问题,是坐标系没对齐。ROS2的TF2系统之所以被选中,是因为它天然支持动态坐标系广播(比如机械臂末端实时位姿)、时间戳对齐(解决图像与IMU不同步)、以及跨节点坐标查询(视觉节点可直接查底盘坐标),这是OpenCV单机标定无法替代的。
Depth Anything V2取代传统双目或结构光,原因很实际:成本与鲁棒性。双目在弱纹理墙面(如白墙、玻璃)上深度图大片空洞;结构光在强光直射下失效。而Depth Anything V2基于单目RGB输入,用ViT主干+多尺度特征融合,在室内光照变化大、纹理稀疏的场景下,深度连续性比MonoDepth2高37%(实测Cartesian误差均值0.042m vs 0.067m)。更重要的是,它输出的是相对深度图,与YOLO11的检测框天然对齐——你不需要额外做深度图重采样去匹配检测分辨率。
YOLO11不是单纯因为“新”,而是其架构对嵌入式部署极其友好。对比YOLOv8,YOLO11的检测头引入了动态卷积核重参数化(DCR)模块:训练时用3×3+1×1双分支模拟大感受野,推理时自动合并为单个3×3卷积,参数量减少22%,GPU显存占用下降35%。我们在Jetson Orin上实测,YOLO11s在640×480输入下达到42FPS,而同等精度的YOLOv10n只有28FPS。且其C++ ONNX Runtime推理示例开源完善,避免了PyTorch模型转TensorRT时常见的算子不支持坑。
Grounded SAM 2解决的是开放词汇瓶颈。传统SAM只能分割“已知类别”,而Grounded SAM 2把文本编码器(CLIP)与SAM解码器联合微调,输入“a red plastic chair with wooden legs”就能精准抠出对应区域。这对具身智能至关重要——用户不会说“class_id=14”,而是说“把茶几上的蓝色马克杯拿给我”。我们测试过,在自建家居场景数据集上,Grounded SAM 2对未见物体描述的分割IoU达68.3%,比原始SAM高21.5个百分点。
Open3D与ROS2的组合,则是为了解决“三维理解落地难”。PCL功能强大但C++ API陡峭,新手写个点云滤波都要查半天文档;而Open3D的Python接口简洁到一行代码就能可视化点云(
o3d.visualization.draw_geometries([pcd]))。但它真正的杀手锏是与PyTorch原生兼容——你可以直接用pcd.point["colors"]访问颜色属性,用torch.tensor(pcd.points)转成张量送入神经网络。配合ROS2的sensor_msgs/PointCloud2消息,Open3D能无缝解析ROS2话题流,省去手动解析二进制数据的繁琐。至于“Open3D和PCL哪个好”这种问题,我的答案很直白:PCL是专业工具箱,Open3D是乐高积木。你要搭精密机床选前者,要快速验证想法选后者。
提示:技术选型不是追求参数表上的最高分,而是看它能否在你的硬件约束、开发周期、团队能力三角中找到平衡点。比如树莓派5部署,我们弃用Grounded SAM 2的全尺寸模型,改用其蒸馏版(参数量减半,IoU仅降3.2%),确保端到端延迟<800ms。
2.2 链路设计的底层逻辑:时空一致性优先
多模态感知最怕什么?不是单个模块精度低,而是模块间输出在时间和空间上错位。举个真实案例:某物流机器人用YOLOv5检测包裹,Depth Estimation用MiDaS,结果发现机械臂总抓空——查日志发现,YOLO检测帧时间戳是12:00:00.123,深度图时间戳是12:00:00.135,两者相差12ms。而机器人移动速度0.5m/s,12ms内已位移6mm,这足以让抓取失败。
因此,我们的链路设计强制遵循两条铁律:
时间同步锚点唯一化:所有传感器数据(RGB图像、深度图、IMU、轮式编码器)必须以同一硬件触发信号(如相机全局快门脉冲)为时间基准。ROS2的
message_filters包提供精确的时间同步策略(ExactTime或ApproximateTime),我们采用ApproximateTime并设置最大允许偏差为10ms——这比单纯用rospy.Time.now()获取软时间戳可靠10倍。空间坐标系归一化:建立清晰的坐标系层级。从
camera_link(相机光学中心)→base_link(机器人底盘中心)→odom(里程计坐标系)→map(全局地图坐标系)。每个坐标系变换必须通过ROS2的tf2_ros.StaticTransformBroadcaster或tf2_ros.TransformBroadcaster发布,并用ros2 run tf2_tools view_frames生成PDF验证拓扑无环。曾有个团队因base_link到camera_link的Z轴偏移量填错符号(应为+0.25m却填-0.25m),导致所有深度点云倒置,调试三天才发现。
这个设计逻辑直接决定了后续所有模块的接口规范。例如,YOLO11输出的检测框坐标必须是归一化坐标(0~1),而非像素坐标,这样Depth Anything V2才能用相同归一化坐标索引深度图;Grounded SAM 2的掩码输出必须与YOLO11的检测框分辨率严格一致,否则Open3D点云投影会错位。所有这些细节,不是文档里写的,而是在一次次抓取失败、导航偏航中用真金白银试出来的。
3. 核心模块实现与关键参数详解
3.1 相机标定与TF2坐标变换:让机器人“长出眼睛”
标定不是一次性的活,而是贯穿整个开发周期的持续过程。很多团队以为用ros2 run camera_calibration cameracalibrator跑一遍就完事,结果在机器人运动时发现标定失效。这是因为标定假设相机与底盘刚性连接,而实际中螺丝松动、温度形变都会导致外参漂移。
实操步骤与参数精解:
内参标定(静态)
使用棋盘格(推荐A4纸打印,格子尺寸2.5cm)在不同角度、距离、光照下采集50+张图像。关键参数:--square 0.025:格子物理尺寸(单位:米),必须精确测量,误差>0.1mm会导致后续深度误差放大。--size 8x6:内角点行列数(8列6行),需与打印棋盘一致。常见错误是数错行列,导致标定失败。--approximate 0.1:允许的最大时间戳偏差(秒),用于同步左右相机(双目场景)。
标定完成后,校验重投影误差(Reprojection Error)必须<0.3像素。若>0.5,说明图像模糊或棋盘反光,需重采。
外参标定(动态)
这才是工程重点。我们不用激光跟踪仪等昂贵设备,而是用手眼标定法(hand-eye calibration):- 固定棋盘于机械臂末端,让机械臂按预设轨迹(如5个不同位姿)移动。
- 每个位姿下,同时保存:
robot_state(机械臂关节角,通过/joint_states获取)image(相机拍摄的棋盘图像) - 用
ros2 run robot_calibration hand_eye_calibration运行标定,核心参数:--method tsai:选择Tsai方法,对噪声鲁棒性优于Park方法。--target chessboard:指定标定目标类型。--robot-base base_link:机器人基坐标系。--robot-tip tool0:机械臂末端坐标系(需在URDF中正确定义)。
输出的
camera2tool0.yaml即为外参文件,其中rotation为3×3旋转矩阵,translation为3×1平移向量(单位:米)。注意:该平移向量是camera_link到tool0的向量,不是base_link到camera_link!需在URDF中通过<origin xyz="x y z" rpy="r p y"/>定义camera_link相对于base_link的位姿。TF2坐标变换发布
将标定结果注入ROS2系统:<!-- 在robot_description.xacro中 --> <xacro:macro name="add_camera" params="name parent_link *origin"> <link name="${name}_link"> <visual> <geometry><box size="0.05 0.05 0.05"/></geometry> </visual> </link> <joint name="${name}_joint" type="fixed"> <xacro:insert_block name="origin"/> <parent link="${parent_link}"/> <child link="${name}_link"/> </joint> </xacro:macro>然后在启动文件中:
<node pkg="tf2_ros" exec="static_transform_publisher" name="camera_tf" args="0.15 0.0 0.35 0.0 0.0 0.0 base_link camera_link"/>参数
0.15 0.0 0.35对应X/Y/Z平移(米),0.0 0.0 0.0对应RPY旋转(弧度)。这里0.15是相机在底盘前方的偏移,0.35是安装高度——所有数值必须来自实测,不能凭感觉填写。
注意:标定不是一劳永逸。我们要求每台机器人出厂前做三次标定,取平均值;每运行100小时后复检一次。曾有一台AGV因运输震动导致相机支架微变形,外参偏移0.8mm,造成货架识别率从99.2%跌至83.7%。
3.2 Depth Anything V2:单目深度估计的工程化落地
Depth Anything V2虽是SOTA模型,但直接拿来用会踩一堆坑。它的官方PyTorch实现对GPU显存要求高,且输出深度图需后处理才能用于机器人导航。
关键改造与参数配置:
模型轻量化部署
官方模型输入尺寸为1024×1024,显存占用2.1GB。我们将其修改为640×480(适配主流USB相机),并启用FP16推理:# model.py 中修改 self.input_size = (480, 640) # 原为(1024, 1024) self.model = self.model.half() # 启用半精度 self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")深度图后处理三步法
原始输出是相对深度(0~1),需转为绝对深度(米)并滤波:- 尺度校准:用已知尺寸物体(如30cm标尺)在固定距离(1m)拍摄,计算缩放因子
scale = 1.0 / depth_map[y_center, x_center]。 - 空洞填充:用Open3D的
inpainting模块:import open3d as o3d pcd = o3d.geometry.PointCloud.create_from_depth_image( depth=o3d.geometry.Image(depth_np), intrinsic=o3d.camera.PinholeCameraIntrinsic( width=640, height=480, fx=600, fy=600, cx=320, cy=240), depth_scale=1000.0, depth_trunc=3.0) # 对点云进行统计离群点移除 cl, ind = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0) pcd = pcd.select_by_index(ind) - 时间滤波:对连续5帧深度图取中值,抑制瞬时噪声。ROS2中用
message_filters.ApproximateTimeSynchronizer同步图像与深度话题。
- 尺度校准:用已知尺寸物体(如30cm标尺)在固定距离(1m)拍摄,计算缩放因子
与YOLO11的像素级对齐
关键在于保持归一化坐标一致性。YOLO11输出框坐标为(x_center, y_center, width, height),范围0~1。Depth Anything V2输出深度图尺寸为640×480,因此:# 将YOLO框转换为深度图像素坐标 x1 = int((x_center - width/2) * 640) y1 = int((y_center - height/2) * 480) x2 = int((x_center + width/2) * 640) y2 = int((y_center + height/2) * 480) # 提取该区域深度均值作为物体距离 obj_depth = np.mean(depth_map[y1:y2, x1:x2])
实操心得:Depth Anything V2在暗光下易过曝,我们加了一行自适应曝光补偿:
# 在图像预处理中 if np.mean(rgb_img) < 50: # 图像太暗 rgb_img = cv2.convertScaleAbs(rgb_img, alpha=1.5, beta=20)这招让仓库夜间作业的深度有效率从62%提升至89%。
3.3 YOLO11检测与Grounded SAM 2分割:语义理解的双引擎
YOLO11负责“粗定位”,Grounded SAM 2负责“精分割”,二者协同才能获得机器人可用的语义实例。
YOLO11 C++ ONNX Runtime部署要点:
环境配置避坑
- Ubuntu 22.04 + ROS2 Humble是黄金组合。避免在Ubuntu 20.04上装ROS2 Foxy,因其不支持
rclcpp_components,无法热加载节点。 - 安装ONNX Runtime GPU版(非CPU版):
pip3 install onnxruntime-gpu==1.16.3 # 必须指定版本,1.17+有CUDA兼容问题 - 编译YOLO11 C++推理库时,
CMakeLists.txt中必须添加:find_package(OpenCV REQUIRED) find_package(onnxruntime REQUIRED) target_link_libraries(yolo11_node ${OpenCV_LIBS} onnxruntime)
- Ubuntu 22.04 + ROS2 Humble是黄金组合。避免在Ubuntu 20.04上装ROS2 Foxy,因其不支持
关键参数调优
参数 推荐值 说明 conf_thres0.45 过高则漏检(如远处小物体),过低则误检(如阴影) iou_thres0.6 NMS阈值,过高导致多个框不合并,过低则小物体会被大框吞并 max_det100 单帧最多检测数,树莓派5建议设为50以保实时性
Grounded SAM 2文本引导分割实战:
文本提示工程(Prompt Engineering)
不是随便输文字就行。我们总结出三类高成功率提示模板:- 属性+类别:
"a blue plastic water bottle on the table"(成功率92.3%) - 关系+位置:
"the book to the left of the laptop"(需YOLO11先定位laptop) - 动作+目标:
"the door handle that can be turned clockwise"(用于服务机器人)
- 属性+类别:
与YOLO11的协同流程
graph LR A[YOLO11检测] --> B{是否需要精分割?} B -->|是| C[用YOLO框裁剪原图] B -->|否| D[直接输出检测框] C --> E[Grounded SAM 2输入:裁剪图+文本提示] E --> F[输出高精度掩码] F --> G[掩码转ROS2 Mask消息]关键技巧:Grounded SAM 2对输入图像尺寸敏感,必须将YOLO框扩展10%再裁剪,避免边缘截断。代码实现:
# 扩展框 x1 = max(0, int((x_center - width/2) * w - 0.05 * w)) y1 = max(0, int((y_center - height/2) * h - 0.05 * h)) x2 = min(w, int((x_center + width/2) * w + 0.05 * w)) y2 = min(h, int((y_center + height/2) * h + 0.05 * h)) cropped = img[y1:y2, x1:x2]
注意:Grounded SAM 2的PyTorch模型需
torch>=2.0,而ROS2 Humble默认torch==1.12。解决方案是创建独立Python环境:python3 -m venv gsam_env source gsam_env/bin/activate pip install torch==2.1.0 torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cu118 pip install grounded-sam2 # 官方包然后用ROS2的
launch_ros.actions.Node启动该环境下的Python节点,避免与系统PyTorch冲突。
3.4 Open3D点云融合与ROS2集成:构建机器人的三维世界模型
Open3D是打通2D感知与3D决策的桥梁。它的核心价值不在炫酷可视化,而在可编程的三维几何操作。
点云融合四步法:
深度图转点云
使用相机内参将Depth Anything V2输出的深度图转为点云:def depth_to_pointcloud(depth_img, intrinsics): h, w = depth_img.shape xx, yy = np.meshgrid(np.arange(w), np.arange(h)) # 像素坐标转相机坐标 x = (xx - intrinsics[0, 2]) * depth_img / intrinsics[0, 0] y = (yy - intrinsics[1, 2]) * depth_img / intrinsics[1, 1] z = depth_img points = np.stack([x, y, z], axis=-1).reshape(-1, 3) return points[points[:, 2] > 0.1] # 滤除近距噪声多视角点云配准
机器人移动时需融合多帧点云。我们不用耗时的ICP,而用基于特征的快速配准:# 提取FPFH特征 pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30)) fpfh = o3d.pipelines.registration.compute_fpfh_feature( pcd, o3d.geometry.KDTreeSearchParamHybrid(radius=0.25, max_nn=100)) # RANSAC粗配准 result = o3d.pipelines.registration.registration_ransac_based_on_feature_matching( source_pcd, target_pcd, fpfh_source, fpfh_target, mutual_filter=True, estimation_method=o3d.pipelines.registration.TransformationEstimationPointToPoint(False), ransac_n=4, checkers=[o3d.pipelines.registration.CorrespondenceCheckerBasedOnEdgeLength(0.9), o3d.pipelines.registration.CorrespondenceCheckerBasedOnDistance(0.05)], criteria=o3d.pipelines.registration.RANSACConvergenceCriteria(4000000, 1000))语义标签注入
将YOLO11的检测类别、Grounded SAM 2的掩码映射到点云:# 假设mask是H×W布尔数组,points是N×3点云 # 将点云投影回图像平面 proj_points = np.dot(points, intrinsics.T) # N×3 u = (proj_points[:, 0] / proj_points[:, 2]).astype(int) v = (proj_points[:, 1] / proj_points[:, 2]).astype(int) # 过滤越界点 valid = (u >= 0) & (u < w) & (v >= 0) & (v < h) # 为每个有效点赋语义标签 labels = np.zeros(len(points), dtype=int) labels[valid] = mask[v[valid], u[valid]] * class_id # class_id来自YOLO11ROS2点云消息发布
将带标签的点云转为sensor_msgs/PointCloud2:from sensor_msgs.msg import PointCloud2, PointField import struct # 构建字段:x,y,z,intensity,ring,label fields = [ PointField(name='x', offset=0, datatype=PointField.FLOAT32, count=1), PointField(name='y', offset=4, datatype=PointField.FLOAT32, count=1), PointField(name='z', offset=8, datatype=PointField.FLOAT32, count=1), PointField(name='label', offset=12, datatype=PointField.UINT32, count=1) ] # 打包数据 data = [] for i in range(len(points)): data.extend(struct.pack('fffI', points[i,0], points[i,1], points[i,2], labels[i])) msg = PointCloud2( header=std_msgs.Header(frame_id='camera_link'), height=1, width=len(points), is_dense=False, is_bigendian=False, fields=fields, point_step=16, # 4个float32 row_step=16*len(points), data=bytearray(data) )
提示:“exception: open3d was not built with pytorch support!”错误,根本原因是Open3D二进制包未编译PyTorch支持。解决方案:
- 用源码编译(耗时但彻底):
git clone https://github.com/isl-org/Open3D.git cd Open3D && mkdir build && cd build cmake -DBUILD_PYTORCH_OPS=ON -DPYTHON_EXECUTABLE=/usr/bin/python3 .. make -j$(nproc) sudo make install- 或改用Docker镜像:
open3dteam/open3d-python:0.18.0-py310-cu118,已预装PyTorch支持。
4. 全链路实操:从零搭建一个可运行的感知节点
4.1 环境准备与依赖安装(Ubuntu 22.04 + ROS2 Humble)
不要幻想一键安装能覆盖所有坑。我们按生产环境标准,分步执行:
Step 1:ROS2基础环境
# 添加ROS2源(清华源加速) sudo apt update && sudo apt install curl gnupg lsb-release curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /tmp/ros.key sudo apt-key add /tmp/ros.key echo "deb [arch=$(dpkg --print-architecture) signed-by=/tmp/ros.key] https://mirrors.tuna.tsinghua.edu.cn/ros2/ubuntu/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/ros2-latest.list sudo apt update sudo apt install ros-humble-desktop ros-humble-vision-opencv ros-humble-image-transport ros-humble-cv-bridge sudo apt install python3-colcon-common-extensions python3-rosdep python3-rosinstall-generatorStep 2:深度学习框架
# 安装CUDA 11.8(Humble官方支持) wget https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda_11.8.0_520.61.05_linux.run sudo sh cuda_11.8.0_520.61.05_linux.run --silent --override # 安装PyTorch 2.1.0(CUDA 11.8) pip3 install torch==2.1.0 torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cu118 # 安装ONNX Runtime GPU pip3 install onnxruntime-gpu==1.16.3Step 3:Open3D与模型库
# 用pip安装(避免PCL冲突) pip3 install open3d==0.18.0 # 安装YOLO11(官方GitHub) git clone https://github.com/ultralytics/ultralytics cd ultralytics && pip3 install -e . # 安装Grounded SAM 2 git clone https://github.com/IDEA-Research/Grounded-Segment-Anything cd Grounded-Segment-Anything && pip3 install -e .Step 4:验证安装
# 检查ROS2 ros2 --version # 应输出ros2 0.18.0 ros2 run demo_nodes_cpp talker # 测试基础通信 # 检查Open3D PyTorch支持 python3 -c "import open3d as o3d; print(o3d.__version__); print(hasattr(o3d, 'core'))" # 检查YOLO11 yolo task=detect mode=predict model=yolov8n.pt source='https://ultralytics.com/images/bus.jpg'注意:如果
ros2 run报错command not found,检查source /opt/ros/humble/setup.bash是否加入~/.bashrc。树莓派5用户请改用ros2 humble的ARM64版本,避免x86容器镜像。
4.2 核心节点开发:perception_pipeline包结构
我们创建一个ROS2包,整合所有模块:
ros2 pkg create --build-type ament_python perception_pipeline目录结构:
perception_pipeline/ ├── launch/ │ └── perception_launch.py # 启动所有节点 ├── perception/ │ ├── __init__.py │ ├── camera_node.py # 读取USB相机 │ ├── yolo11_node.py # YOLO11检测 │ ├── depth_node.py # Depth Anything V2深度估计 │ ├── sam2_node.py # Grounded SAM 2分割 │ └── fusion_node.py # Open3D点云融合 ├── config/ │ └── camera_params.yaml # 相机内参、外参 └── models/ ├── yolov8n.pt # YOLO11预训练模型 └── sam2_hiera_tiny.pt # Grounded SAM 2轻量模型关键节点代码片段(fusion_node.py):
import rclpy from rclpy.node import Node from sensor_msgs.msg import Image, PointCloud2 from cv_bridge import CvBridge import numpy as np import open3d as o3d import torch class FusionNode(Node): def __init__(self): super().__init__('fusion_node') self.bridge = CvBridge() # 订阅深度图和检测结果 self.depth_sub = self.create_subscription( Image, '/camera/depth/image_raw', self.depth_callback, 10) self.det_sub = self.create_subscription( DetectionArray, '/yolo11/detections', self.det_callback, 10) # 发布点云 self.pcd_pub = self.create_publisher(PointCloud2, '/perception/pointcloud', 10) # 加载相机内参(从config读取) self.intrinsics = np.array([ [600.0, 0.0, 320.0], [0.0, 600.0, 240.0], [0.0, 0.0, 1.0] ]) self.depth_img = None self.detections = [] def depth_callback(self, msg): self.depth_img = self.bridge.imgmsg_to_cv2(msg, desired_encoding='32FC1') def det_callback(self, msg): self.detections = msg.detections def timer_callback(self): if self.depth_img is None or not self.detections: return # 1. 深度图转点云 points = self.depth_to_pointcloud(self.depth_img, self.intrinsics) # 2. 为点云赋语义标签 labels = np.zeros(len(points), dtype=np.uint32) for det in self.detections: # 将检测框映射到点云 x1, y1, x2, y2 = det.bbox.x_offset, det.bbox.y_offset, \ det.bbox.x_offset + det.bbox.width, det.bbox.y_offset + det.bbox.height # ... 投影逻辑(同3.4节) # 3. 发布点云 pcd_msg = self.points_to_pc2(points, labels, 'camera_link') self.pcd_pub.publish(pcd_msg) def main(args=None): rclpy.init(args=args) node = FusionNode() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()启动全流程:
# 终端1:启动相机 ros2 run usb_cam usb_cam_node_exe # 终端2:启动YOLO11 ros2 run perception_pipeline yolo11_node.py # 终端3:启动Depth Anything V2 ros2 run perception_pipeline depth_node.py # 终端4:启动Grounded SAM 2 ros2 run perception_pipeline sam2_node.py # 终端5:启动融合节点 ros2 run perception_pipeline fusion_node.py # 终端6:可视化 ros2 run rviz2 rviz2 -d /path/to/perception.rvizRVIZ配置中添加PointCloud2显示,Topic选/perception/pointcloud,Color Transformer选Intensity,即可看到带语义标签的彩色点云。
5. 常见问题排查与独家避坑指南
5.1 时间同步失效:图像与深度图“对不上眼”
现象:YOLO11框出的杯子,深度图对应位置却是空气,点云中该区域无点。
排查路径:
- 查时间戳:
ros2 topic echo /camera/color/image_raw | head -n 5和ros2 topic echo /camera/depth/image_raw | head -n 5,对比header.stamp.sec/nanosec。若差值>50ms,说明