尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ROS2激光雷达与相机融合实战:从标定到点云投影与导航落地

ROS2激光雷达与相机融合实战:从标定到点云投影与导航落地 简介本资源是面向机器人方向本科生与研究生的ROS2多传感器融合实践项目聚焦激光雷达与相机数据协同感知这一核心问题适用于毕业设计、课程设计及期末大作业等工程实践场景。压缩包共27个文件含9个xacro模型文件用于机器人URDF描述与传感器位姿定义、11个identifier配置标识文件支撑模块化功能识别、4个Python节点脚本实现坐标变换、图像-点云对齐与融合逻辑、1个README.md说明文档含环境依赖、编译命令与运行流程及1张系统运行效果截图整体仅64KB轻量易部署。已有96人学习下载资源结构清晰src目录下已组织好功能分层代码配套截图直观展示融合可视化效果读者可直接复现校准→采集→预处理→融合全流程并基于现有框架快速拓展SLAM或目标检测应用。 前阵子整理手上的一套传感器融合工程打包的时候顺手起了个名字叫ROS2激光雷达与相机融合.zip里面既有标定脚本、时间同步配置也有完整的点云投影节点和一套导航避障的验证Demo。很多朋友看到这个包名来问我具体怎么落地正好借这个机会把整个项目的思路、环境搭建、标定流程、核心代码和踩坑记录完整梳理一遍。做移动机器人、自动驾驶小车、巡检机器人或者任何需要既知道物体在哪又知道它长什么样的场景这篇内容应该都能帮上忙。1. 为什么非要把雷达和相机凑在一起先别急着装环境、跑代码。融合这件事我见过太多人一上来就抄开源项目结果代码跑通了换到自己机器上就废。根本原因是没有想清楚雷达和相机放在一起到底要解决什么痛点1.1 两种传感器各自的短板正好互补激光雷达的优势是直接输出3D位置信息精度高不受光照干扰室外大太阳底下照样能测出几十米外的障碍物距离。但它的缺点也明显点云是稀疏的没有颜色和纹理本质上就是一串有距离的坐标点你很难靠它判断这是一个消防栓还是一棵树。相机正好相反。图像分辨率高颜色纹理丰富语义信息极其充分一眼就能看出画面里是行人、车辆还是锥桶。但单目相机没有深度双目虽然有测距范围和精度又受基线限制晚上或者逆光的时候基本歇菜。把两者放一起本质上是让雷达负责测距和几何相机负责语义和纹理然后在时间和空间上把两路数据对齐得到一张带语义的三维视角结果。这是所有融合方案的基本逻辑。1.2 决定融合路线前先分清三类需求我在实际项目里发现不同场景对融合的诉求完全不一样技术路线也会随之分叉。大体可以分为三类空间对齐型把雷达点云投影到相机图像上给图像上的物体标注距离或者给点云着色。这是最基础、也是本文重点做的方向。典型应用是目标检测测距、火情侦查、巡检点位定位。决策互补型雷达做主要障碍物检测和SLAM相机做辅助语义识别、红绿灯识别、人形判断。两者在感知层面独立工作在决策层合并结果。这是目前L2/L3级辅助驾驶的主流做法。深度增强型用雷达点云作为稀疏深度真值监督或者引导相机深度估计最终得到稠密深度图。这个偏算法研究方向对工程要求高一般项目不用碰。我做这套工程时用的是第一种路线但代码结构上预留了第二种的扩展位。强烈建议你也先把第一步走扎实空间对齐做不好后面谈什么融合都是空中楼阁。1.3 为什么坚持用ROS2而不是ROS1这个话题几乎每次都会被问到。说句实在话雷达相机融合这个需求ROS1时代就有人做了网上老教程一堆。但我的观点很明确新项目直接上ROS2不要犹豫。原因也不复杂。ROS2的分布式通信基于DDS节点之间不依赖中心master一个节点挂了不会拖垮整个系统这对实车、实机调试非常重要另外ROS2的参数服务器、生命周期管理、动态配置这些机制在传感器标定和多传感器同步场景下能省非常多事更关键的是融合涉及时间戳同步ROS2里message_filters对时间同步的支持比ROS1干净得多后面会细说。系统版本我选的是 Ubuntu 22.04 ROS2 Humble这也是目前最稳的组合。用Humble不追最新因为社区资料多、功能包兼容性好踩坑了能搜到答案。工业项目最怕最新版依赖冲突稳定压倒一切。2. 把这套环境从零拉起来安装与准备2.1 Ubuntu 22.04 上的 ROS2 Humble 安装很多人卡在ROS2安装这一步其实没多难。如果你在Ubuntu 22.04上最简单的是用鱼香ROS的脚本一键安装这个脚本社区口碑不错我帮朋友装机器时也用它wget http://fishros.com/install -O fishros . fishros按提示选择ros2 humble桌面版安装即可。它会自动帮你配置软件源、装依赖、初始化rosdep环境变量。安装完成后验证一下ros2 run demo_nodes_cpp talker ros2 run demo_nodes_cpp listener两个终端分别跑以上命令能看到互相收发hello world说明基础环境OK。但实操中我建议你再手动确认三件事~/.bashrc里是否有source /opt/ros/humble/setup.bash没有就手动加rosdep是否初始化成功。这一步很多人会漏后面编译功能包时各种依赖解析失败全是它引起的工作空间结构是否正确。项目根目录下必须有src文件夹用colcon build编译别再用ROS1时代的catkin_make习惯。2.2 常用功能包和依赖融合工程里我用到的核心依赖包如下建议用sudo apt install安装版本稳sudo apt install ros-humble-pcl-ros sudo apt install ros-humble-pcl-conversions sudo apt install ros-humble-cv-bridge sudo apt install ros-humble-image-geometry sudo apt install ros-humble-message-filters sudo apt install ros-humble-tf2-geometry-msgs sudo apt install ros-humble-rviz2这里面几个包的作用要心里有数pcl_conversions处理sensor_msgs/msg/PointCloud2和 PCL 点云类型之间的互相转换cv_bridge把sensor_msgs/msg/Image转成 OpenCV 的Mat否则没法做像素级操作image_geometry专门用来管理相机内参提供PinholeCameraModel做投影时不用自己手写矩阵乘法准确率还高message_filters做时间同步保证点云和图像是同一时刻采集的。另外我强烈建议自己也编译一个ros-humble-laser-geometry它能把2D激光雷达的数据转成点云。如果你用的是2D雷达做融合这个包是刚需。2.3 三种获取雷达相机数据的方式代码写好了总得有数据。我总结下来有三种来源从易到难跑公开数据集KITTI、nuScenes 这些数据集都有现成的雷达点云和图像转成ROS2 bag直接用。好处是数据质量高、有真值适合先验证算法流程。用Gazebo仿真在Gazebo里给机器人模型挂上雷达和相机插件可以模拟各种场景和光照。参数基本接近真实调试代码效率极高。真实传感器Velodyne16线雷达配Realsense深度相机是常见组合。真实数据最可靠但标定工作量大而且采集环境要选好否则数据里全是噪声。我第一次跑通融合节点用的是仿真数据确认逻辑无误后才搬上真机。这条路径推荐给所有初学者能省下一半的排错时间。开头的核心目标读者可以清楚了不管你是学校实验室做课题还是在公司搞机器人产品预研只要手里有台能跑ROS2的设备这套工程都能直接作为起点。3. 标定和同步融合前最容易翻车的两个环节很多人跑融合代码最头疼的就是明明代码逻辑没问题激光点投影到图像上就是对不上。问题基本出在两个地方空间没有对齐时间没有对齐。这也是整个项目里最需要耐心的部分。3.1 联合标定让两个坐标系对齐空间对齐靠的是标定得到的相机与雷达之间的外参矩阵。这个矩阵描述的是同一个物体在雷达坐标系下的坐标换算到相机坐标系下需要做什么旋转和平移。常用的标定工具是 Autoware 的lidar_camera_calibration或者 Intel 的lidar_camera_calib。标定思路如下把一个棋盘格标定板放到雷达和相机都能看到的公共区域同时采集雷达点云和相机图像在图像中提取棋盘格角点的像素坐标在点云中提取棋盘格平面上的点拟合平面并获得角点的3D坐标用PnP算法求解雷达坐标系到相机坐标系的旋转矩阵R和平移向量t多采几组不同距离、不同角度的数据最小化重投影误差得到最终外参。这一过程中的核心工程细节是雷达点云里的棋盘格平面提取。这里分享一个很实用的经验不要试图直接找棋盘格的角点在点云里长什么样而是先做平面分割RANSAC把标定板平面拟合出来然后用平面边界估算三维角点。这样比直接提取角点稳定得多。采集标定数据时要注意三点一是标定板要尽量垂直于雷达扫描平面否则点云在板面上分布不均二是在不同距离和角度下多采覆盖整个公共视场三是保证标定板表面没有反光或者吸光材料否则点云上的面可能缺一块。标定完成后你会得到一个ba_calib或类似文件里面包含了相机内参焦距、主点、畸变系数和外参雷达到相机的变换矩阵。这个文件后续会配到项目的 launch 文件里以参数形式加载。3.2 动态TF让机器人动起来也不错位标定得到的是一个静态变换但实际机器人在运动时雷达坐标系和相机坐标系都挂在底盘坐标系下它们之间的相对关系不随机器人运动改变。所以我们需要把这个静态变换发布到TF树里让一切坐标变换有据可查。在ROS2里有两种方式写一个静态坐标发布节点把R和t封装成tf2_msgs/msg/TFMessage周期性发布直接用static_transform_publisher命令行工具。推荐第二种方式简单直接。例如标定得到的变换是雷达在相机坐标系下沿x轴偏移0.07米y轴偏移0.15米z轴偏移0.1米绕y轴旋转180度则命令如下ros2 run tf2_ros static_transform_publisher --x 0.07 --y 0.15 --z 0.1 --yaw 3.14159 --pitch 0 --roll 0 --frame-id lidar_link --child-frame-id camera_link注意这里的--frame-id和--child-frame-id的方向别搞反。我见过不少人在这一步把父子坐标系弄反导致后面投影结果全部偏移怎么调都调不回来。调试技巧发布完TF后在Rviz2里打开TF显示手动播放一帧点云和图像拖动视角看雷达坐标系和相机坐标系的箭头是否在合理位置。两个轴方向正确了再往下走。底层逻辑其实不复杂。坐标系变换的整个过程是点云里每一点都是lidar_link坐标系下的坐标我们想把它表示在camera_link坐标系下需要从TF树里查询这两个坐标系之间的变换。这个查询由tf2_ros::Buffer完成它内部会维护一棵完整的坐标树支持任意时刻的变换查询。3.3 时间同步用message_filters对齐话题空间对齐解决点在线的问题时间对齐解决线和线的问题。雷达和相机的采样频率不同例如雷达10Hz相机30Hz如果不做同步每次拿到图像时对应的点云可能是几十毫秒前的机器人运动越快误差越明显。ROS2里的message_filters提供了同步机制最常用的是ApproximateTime和ExactTime。ExactTime要求两个消息的时间戳完全一致适合频率相同且触发同步的情况ApproximateTime在允许的时间窗口内寻找最接近的时间戳配对适合频率不同、时间戳各不相同的传感器。实际项目中我一般直接上ApproximateTime。雷达和相机各自从不同驱动发布消息时间戳完全对齐的概率几乎为零硬用ExactTime只会让回调永远不触发。同步逻辑的伪代码如下#include message_filters/subscriber.h #include message_filters/sync_policies/approximate_time.h #include message_filters/synchronizer.h message_filters::Subscribersensor_msgs::msg::PointCloud2 cloud_sub(this, /points_raw, 10); message_filters::Subscribersensor_msgs::msg::Image image_sub(this, /camera/color/image_raw, 10); typedef message_filters::sync_policies::ApproximateTime sensor_msgs::msg::PointCloud2, sensor_msgs::msg::Image SyncPolicy; message_filters::SynchronizerSyncPolicy sync( SyncPolicy(10), cloud_sub, image_sub); sync.registerCallback(YourNode::syncCallback, this);这个模式避免了手动管理多路消息缓存是最标准、最不容易出bug的同步方式。4. 核心节点实现把点云投影到图像上环境、标定、同步都准备好了下面到重点环节写一个融合节点把雷达点云投影到图像上。这个节点是整套工程的发动机务必吃透。4.1 投影公式拆解与坐标系变换先把投影的数学公式讲明白。假设雷达测到障碍物上某一点在lidar_link坐标系下的坐标为(x_l, y_l, z_l)。要把这个点画到像素坐标(u, v)需要经过三步变换第一步通过TF查询得到的变换矩阵T_l_to_c把点从雷达坐标系变换到相机坐标系[x_c, y_c, z_c, 1]^T T_l_to_c * [x_l, y_l, z_l, 1]^T这个T_l_to_c是4x4齐次变换矩阵内含旋转和平移。第二步用相机内参把3D相机坐标投影到2D像素坐标。针孔相机模型下u f_x * x_c / z_c c_x v f_y * y_c / z_c c_y其中f_x、f_y是焦距单位像素c_x、c_y是主点坐标这些都来自相机内参标定结果。第三步判断这个像素坐标是否在图像范围内(0 u width, 0 v height)同时要过滤掉z_c 0的点。这些点位于相机后方或者相机平面上投影出来没有物理意义不处理会出现大量飞点。整个投影过程其实就这么简单难的是代码里把TF查询和同步回调组织好。4.2 C节点的完整结构与关键代码下面给出一个可运行的融合节点骨架。这个节点订阅同步后的点云和图像在回调里完成投影然后用OpenCV把每个投影点画在图像上颜色由点云距离决定。#include rclcpp/rclcpp.hpp #include message_filters/subscriber.h #include message_filters/synchronizer.h #include message_filters/sync_policies/approximate_time.h #include sensor_msgs/msg/point_cloud2.hpp #include sensor_msgs/msg/image.hpp #include cv_bridge/cv_bridge.h #include image_geometry/pinhole_camera_model.h #include pcl_conversions/pcl_conversions.h #include pcl/point_cloud.h #include pcl/point_types.h #include pcl/filters/voxel_grid.h #include tf2_ros/buffer.h #include tf2_ros/transform_listener.h #include geometry_msgs/msg/transform_stamped.h #include opencv2/opencv.hpp class LidarCameraFusion : public rclcpp::Node { public: LidarCameraFusion() : Node(lidar_camera_fusion) { // 参数相机内参 fx_ this-declare_parameterdouble(fx, 617.3); fy_ this-declare_parameterdouble(fy, 615.2); cx_ this-declare_parameterdouble(cx, 324.6); cy_ this-declare_parameterdouble(cy, 245.3); // TF tf_buffer_ std::make_uniquetf2_ros::Buffer(this-get_clock()); tf_listener_ std::make_sharedtf2_ros::TransformListener(*tf_buffer_); // 订阅 cloud_sub_.subscribe(this, /points_raw, rclcpp::SensorDataQoS()); image_sub_.subscribe(this, /camera/color/image_raw, rclcpp::SensorDataQoS()); // 同步 sync_.reset(new message_filters::SynchronizerSyncPolicy( SyncPolicy(10), cloud_sub_, image_sub_)); sync_-registerCallback(LidarCameraFusion::callback, this); // 发布 overlay_pub_ this-create_publishersensor_msgs::msg::Image( /fusion/overlay, 10); } private: using SyncPolicy message_filters::sync_policies::ApproximateTime sensor_msgs::msg::PointCloud2, sensor_msgs::msg::Image; void callback(const sensor_msgs::msg::PointCloud2::SharedPtr cloud_msg, const sensor_msgs::msg::Image::SharedPtr image_msg) { // 1. 图像转OpenCV cv::Mat img cv_bridge::toCvShare(image_msg, bgr8)-image.clone(); if (img.empty()) { RCLCPP_WARN(this-get_logger(), Empty image); return; } // 2. 点云消息转PCL pcl::PointCloudpcl::PointXYZ::Ptr cloud(new pcl::PointCloudpcl::PointXYZ()); pcl::fromROSMsg(*cloud_msg, *cloud); if (cloud-empty()) return; // 3. 查询雷达-相机变换 geometry_msgs::msg::TransformStamped tf; try { tf tf_buffer_-lookupTransform(camera_link, lidar_link, rclcpp::Time(0)); } catch (tf2::TransformException ex) { RCLCPP_WARN(this-get_logger(), TF error: %s, ex.what()); return; } // 4. 构造4x4变换矩阵 Eigen::Isometry3d T Eigen::Isometry3d::Identity(); Eigen::Quaterniond q(tf.transform.rotation.w, tf.transform.rotation.x, tf.transform.rotation.y, tf.transform.rotation.z); T.rotate(q); T.pretranslate(Eigen::Vector3d(tf.transform.translation.x, tf.transform.translation.y, tf.transform.translation.z)); // 5. 遍历点云做投影 for (const auto pt : cloud-points) { if (!std::isfinite(pt.x) || !std::isfinite(pt.y) || !std::isfinite(pt.z)) continue; Eigen::Vector4d p_lidar(pt.x, pt.y, pt.z, 1.0); Eigen::Vector4d p_cam T.matrix() * p_lidar; if (p_cam.z() 0.1) continue; double u fx_ * p_cam.x() / p_cam.z() cx_; double v fy_ * p_cam.y() / p_cam.z() cy_; if (u 0 || u img.cols || v 0 || v img.rows) continue; // 距离越远颜色越偏红越近越偏绿 double dist std::sqrt(p_cam.x() * p_cam.x() p_cam.y() * p_cam.y() p_cam.z() * p_cam.z()); int r static_castint(255 * std::min(1.0, dist / 20.0)); int g static_castint(255 * std::max(0.0, 1.0 - dist / 20.0)); cv::circle(img, cv::Point2i(static_castint(u), static_castint(v)), 2, cv::Scalar(0, g, r), -1); } // 6. 发布叠加结果 sensor_msgs::msg::Image out_msg *cv_bridge::CvImage(image_msg-header, bgr8, img).toImageMsg(); overlay_pub_-publish(out_msg); } double fx_, fy_, cx_, cy_; std::unique_ptrtf2_ros::Buffer tf_buffer_; std::shared_ptrtf2_ros::TransformListener tf_listener_; message_filters::Subscribersensor_msgs::msg::PointCloud2 cloud_sub_; message_filters::Subscribersensor_msgs::msg::Image image_sub_; std::shared_ptrmessage_filters::SynchronizerSyncPolicy sync_; rclcpp::Publishersensor_msgs::msg::Image::SharedPtr overlay_pub_; }; int main(int argc, char **argv) { rclcpp::init(argc, argv); rclcpp::spin(std::make_sharedLidarCameraFusion()); rclcpp::shutdown(); return 0; }这段代码里几个关键点值得多说两句TF查询的时间参数。lookupTransform的最后一个参数用了rclcpp::Time(0)意思是取最近一帧可用的变换。这样做能拿到最新TF但严格来说应该在回调里传入消息时间戳保证拿到的是与消息同一时刻的变换。这个取舍我一般建议先用Time(0)跑通了再改成时间戳。点云预处理的重要性。示例里我没加降采样但实际工程一定要加。一帧16线雷达大约3万个点直接画到图像上CPU消耗不小。用pcl::VoxelGrid把点云降采样到每立方厘米或者更粗的网格既能减少计算量又能让投影结果更均匀不会出现一堆点挤在同一个像素上。4.3 验证结果用Rviz2和图像叠加一眼看效果代码写完后通过colcon build编译然后写一个launch文件把数据源、TF、融合节点串起来启动。怎么验证两个途径途径一Rviz2 叠加显示。在Rviz2里添加PointCloud2显示话题选/points_rawFixed Frame设为lidar_link再添加一个Camera显示话题选/camera/color/image_raw。此时你可以拖动视角让点云从图像相机的角度去看如果标定参数正确点云的轮廓应该正好贴合图像中的物体轮廓。这是最直观的验证方式。途径二看融合节点输出的/fusion/overlay话题。在Rviz2里添加Image显示话题选/fusion/overlay。此时你可以看到图像上被画上了一层彩色圆点颜色代表距离。点应该均匀落在障碍物表面比如前面有一辆车点云应该勾勒出车的轮廓车的远近不同颜色深浅不同。如果投影结果整体偏移不要着急调代码优先怀疑标定外参和TF方向。我在实际调试时有个土办法把一个锥桶放在雷达和相机前方5米处看投影点是否落在图像中锥桶的位置。不中就把锥桶挪到3米、7米根据投影点的偏移方向逆推外参的调整方向。这个方法比反复标定十次都管用。5. 融合结果怎么落到SLAM和导航上很多朋友做完点云投影觉得图看着挺炫然后呢这里我重点说说融合结果往SLAM和导航上落地时的数据流设计和几个实际收益点。5.1 数据流怎么设计把融合节点放到整个机器人系统里看数据流通常这样走底层传感器驱动雷达驱动、相机驱动分别发布PointCloud2和Image融合节点订阅两路数据输出带距离点标注的叠加图像下游感知模块比如目标检测节点接收叠加图像把检测结果和距离信息绑定输出障碍物类别距离的结构化消息地图与导航模块用这些结构化消息做动态避障和路径规划。这个架构里融合节点最好设计成一个纯计算节点不负责业务决策。它只做数据对齐投影把结果以标准消息发出去。这样后续无论是接YOLO做目标识别还是接导航做避障都不需要重新改融合逻辑只改下游消费者就行。5.2 在导航中做障碍物融合检测雷达点云在导航里最大的优势是测距准但有个致命弱点无法区分这个障碍物是动态人还是静态墙。传统nav2的做法是直接把雷达点云作为障碍物层但是点云稀疏远处小物体容易漏检尤其是细杆、铁丝网这类雷达反射截面小的目标。融合相机之后可以做一个语义障碍物层相机图像经过目标检测如YOLO识别出人、车、锥桶等语义物体然后通过投影把每个检测框对应的点云距离提取出来。这样导航系统就知道前方5米有人在移动而不是前方某个位置有不可描述的点。这对机器人让行、绕障决策非常有价值。我在项目里做的是把融合节点输出的距离标注图像再接一个yolov8_ros2节点检测图像里的行人然后取行人检测框中心点对应的点云距离发布为semantic_obstacle消息。nav2那边订阅这个消息在全局代价地图和局部代价地图上都加一层动态障碍物。实测下来行人绕障的成功率比纯雷达时提高了非常多。5.3 在SLAM中提升精度与回环能力雷达SLAM比如cartographer在建图时主要靠点云配准但在特征稀疏的环境长走廊、大面积空地容易漂移。相机图像有丰富的纹理信息可以作为额外的约束来源。常见的做法是把视觉特征点匹配结果融合进雷达SLAM的图优化框架比如雷达里程计给一个粗略位姿视觉里程计再修正一次漂移。这套方案工程量大但收益也大尤其是建大场景地图时精度提升非常明显。不过要给你一个诚实的建议如果项目现阶段只是跑通基本导航不要一上来就做视觉惯性雷达紧耦合。先把雷达建图做好再用融合节点在同一个坐标框架下做视觉辅助回环检测。耦合程度一步一步加深出问题好排查。我自己的路线就是先有了可靠的融合叠加结果再去尝试视觉辅助每一步都有独立的验证节点不会黑盒化。6. 实际调试中踩过的坑这部分是我最想分享的因为网上教程基本都会把成功路径讲得很顺但不会告诉你失败路径有多曲折。下面这些坑我都是真金白银踩出来的希望能帮你避雷。6.1 标定参数看起来没问题投影为什么还是偏这是最常见的问题没有之一。表现为标定工具显示重投影误差很小比如0.5个像素以内但把点云投影到图像上整体就是往左偏或往右偏近处偏得少远处偏得多。排错路径是这样先排查图像话题的内参是否一致。有些相机驱动会输出裁剪过的图像比如自动去畸变后裁剪了边缘但标定文件里的内参还是原始分辨率下的。内参和图像不匹配投影必偏。解决方法是重新标定内参或者在内参里加裁剪偏移。再排查TF的父子关系方向。我遇到过把雷达到相机的变换写成了相机到雷达的逆变换导致远处偏得更厉害。Rviz2里用TF显示拖动视角看坐标轴方向是否正确。最后考虑时间同步引入的动态误差。如果机器人运动速度快即使外参没问题ApproximateTime匹配到的点云和图像有时间差会导致投影点整体滞后。优化方向是提高同步容差精度或者改用更高频率的消息类型。6.2 点云投影到图像上出现空洞和飞点空洞是指图像上有些区域没有点云覆盖飞点是指某些点明显偏离物体轮廓。原因不同处理方式也不同。空洞的根源是点云本身稀疏或者大量点被z_c 0.1过滤掉了。解决思路检查雷达和相机的公共视场是否重叠如果雷达装得太偏重叠区域小空洞自然多用ApproximateTime同步时如果点云和图像时间戳差异太大运动模糊会导致投影点分散空洞一片。此时应检查同步窗口大小对点云适当膨胀投影半径画圆点而不是画单像素视觉上能缓解。飞点的根源通常是点云噪声尤其是目标边缘和透明物体表面。解决思路投影前一定要做点云降噪比如统计滤波或半径滤波在投影循环里过滤掉NaN和无穷值示例代码里我加了std::isfinite判断对雷达点做距离阈值过滤超出有效测量范围的点直接丢弃。6.3 时间戳不同步导致图动点不动现象是机器人旋转时点云和图像里同一个物体就是错开的停下来才勉强对上。这不是外参问题是时间同步的窗口太大点云和图像不是同一时刻的数据。我的处理经验是三步走先看雷达和相机各自的发布频率。用ros2 topic hz /points_raw和ros2 topic hz /camera/color/image_raw确认检查message_filters的队列大小和同步窗口参数。队列太小容易丢帧同步窗口太大容易匹配到老帧如果机器人运动很快可以试试把相机帧率降到和雷达一致或者把雷达帧率提上去让时间差尽可能小。6.4 CPU占用太高的优化思路点云投影是纯CPU计算一帧几十万点不做优化分分钟跑满一个核。我的优化优先级是降采样VoxelGrid 把点云降下来这是立竿见影的手段并行化用OpenMP或TBB把点云遍历循环并行化多核直接吃满预分配不要在循环里反复创建cv::Point和Eigen::Vector4d减少临时对象构造只投影ROI如果相机视场有限可以在投影前先按照相机视场角裁剪点云缩小候选点集合。实测中降采样到1万点以下、开启OpenMP之后单节点CPU占用能从80%降到15%左右实时性完全够用。最后再分享一个小经验整套系统跑通后一定要把每次标定参数、launch文件、实测截图做好归档。因为传感器的安装位置只要动过一次哪怕碰了一下支架外参就全部失效要重新标定。没有归档记录三个月后你自己都会忘记当时用的哪组参数。这套流程走完雷达和相机基本就不再是两个孤立的传感器而是一台能看出物体种类的测距仪后续扩展语义地图、动态避障、视觉辅助回环都只是在同一套数据管道上做文章。本文还有配套的精品资源点击获取
返回列表