
我第一次认真做双雷达点云融合是被一个挺尴尬的场景逼出来的一台装了双Livox Mid-360的巡检车单颗雷达拿出来看都很正常可把两颗雷达的点云同时扔进RVIZ以后正前方和正后方就像两个互不承认的世界同一个墙角在两张点云里能差出几十厘米。当时环境是Ubuntu 22.04 ROS2 Humble驱动用的是livox_ros_driver2排查了一整天最后发现问题不在融合代码而在时间同步和外参方向这两个最基础、也最容易被忽略的环节。这篇文章就把我在ROS2 Humble下做双Mid-360点云融合的完整过程拆开来讲从为什么选Humble而不是Foxy到双雷达的驱动配置、外参标定、去畸变和时间同步再到一个可落地的融合节点怎么写最后是我的实际排查记录。无论你是打算给移动底盘加全向感知还是想让点云密度翻倍或者只是被ROS2的QoS、消息同步这些东西绕晕了这篇文章应该能帮你少走不少弯路。1. 为什么是“双Mid-360 Humble”选型背后的真实考量1.1 单雷达的感知盲区不是“转一圈就都能看到”那么简单Mid-360的水平FOV标称是360°听起来装一颗雷达整辆车周围就都覆盖了。但实际用下来这个360°覆盖是分安装姿态的雷达的水平FOV和垂直FOV是固定的窗口水平360°×垂直约70°装到车顶之后这70°的垂直窗口是向上偏还是向下偏完全取决于你支架怎么设计。你把雷达水平安装在车顶车头和车尾方向的近距离确实有覆盖但车身周围会存在大量因为自身结构遮挡形成的盲区尤其是车尾。对巡检机器人或者低速无人车来说车尾盲区意味着倒车、掉头、贴边作业全都是风险某次我在测试场倒车入库车尾方向的一根细柱子全程没出现在点云里等撞上去了RVIZ里还是什么都没有。双雷达不是单纯堆硬件核心目的是用第二颗雷达补足第一颗雷达的视野盲区同时让两颗雷达的重叠区域点云密度翻倍。密度翻倍不是说看起来更酷而是对细长障碍物比如电线杆、栏杆、行人的腿和远距离小目标来说点云密度直接决定了下游分割和聚类算法能不能把这些目标从噪点里认出来。我用的布局是一前一后共线反向安装前雷达朝前、后雷达朝后两颗雷达各管半边天中间重叠区域基本覆盖车顶周围一圈。这个布局最大的好处是逻辑简单标定关系基本可以理解成一个平移加180°旋转后面调试的时候会省很多事。1.2 Foxy和Humble的差别到底影响什么很多教程到现在还停留在Foxy上但如果你要新搭平台我强烈建议直接上Humble。最直接的原因是系统版本Foxy对应Ubuntu 20.04Humble对应Ubuntu 22.04而Foxy的官方维护在2023年就停止了Humble是长期支持版本支持周期到2027年。这意味着bug修复、安全补丁和第三方库的兼容性都会长期跟进你在Humble上写的东西两三年后回来看还能跑这在实际项目里太重要了。除了生命周期Humble在DDS、编译工具链、launch文件和消息过滤器等方面都比Foxy新很多。比如Humble默认对Cyclone DDS的支持更成熟在局域网多设备通信场景下Cyclone DDS的发现机制比Fast DDS稳定这对双雷达这种需要同时连接多台网络设备的情况很关键。还有一点容易被忽略livox_ros_driver2这类第三方驱动的社区验证这两年也明显从Foxy迁移到Humble了很多在Foxy上需要自己改代码的问题在Humble上直接就能编译通过。当然比Humble更新的版本我也试过生态还不太跟得上所以现阶段做点云融合Humble是综合最稳的选择。1.3 Mid-360的硬件特性与安装布局建议Mid-360的参数大家应该都看过点频约20万点/秒量程标称40米10%反射率下重量和功耗在同类混合固态雷达里算很轻最特别的是非重复扫描模式——每一帧的采样位置和上一帧不完全重合视场覆盖率会随着积分时间增长。这个特性的好处是静止场景下点云越积越密坏处是载体一旦转动非重复扫描引入的畸变会比传统旋转式雷达更明显后面去畸变那节我会专门讲。安装布局上除了我前面说的一前一后共线反向也有人用一正一侧斜45°的布局侧向覆盖更均匀但对标定精度要求更高两颗雷达的视野重叠区域小人工标定的误差一点点都会被放大。如果你是第一次做双雷达融合我建议先用共线反向把从标定、时间同步到融合发布整条链路跑通再去挑战斜向布局。我当时犯过的错是把支架设计得太随意两颗雷达的坐标系没有任何刚性约束导致外参标定后稍微颠一下支架就变形点云又错位了。支架一定要刚性固定这是成本最低但效果最明显的标定稳定性保障。2. 从裸机到双雷达出点云环境部署与驱动的硬核细节2.1 Ubuntu 22.04上的Humble安装以及最常见的source坑Humble在Ubuntu 22.04jammy上通过apt就能装官方流程大致是这样先装依赖和ros key再把packages.ros.org的软件源加入apt源列表然后直接安装ros-humble-desktop。desktop包含RVIZ2和大多数常用工具做点云调试够用了不用纠结装desktop还是base。装完之后你会立刻遇到一个所有人都遇到过的坑新开一个终端输入ros2提示command not found。原因很简单ROS2的环境变量没有自动加载。解决办法是把这个source写到~/.bashrc里让每个新终端自动source不然你每次都要手动执行一遍特别容易在开会演示的时候翻车。网上流传的一键安装脚本我也用过确实方便一个命令装完所有依赖。但我建议至少看懂脚本在干三件事配置软件源、安装二进制包、source环境。如果你连这三步都清楚一键脚本可以放心用如果不清楚出了问题你连从哪里排查都不知道。2.2 ROS2和DDS的关系以及双雷达场景下的通信配置ROS2和DDS的关系新手很容易一头雾水。简单说ROS2把DDS当作底层通信中间件话题、服务、动作这些通信机制全部跑在DDS之上。DDS负责发现节点、建立连接、传输数据你可以认为它是一张看不见的通信大网每个ROS2话题都是这张网上的数据流。Humble默认的DDS实现是Fast DDS单机单雷达场景完全没问题。但双雷达场景下工控机、交换机、两颗雷达的网卡都在一个局域网里组播发现经常出幺蛾子——典型症状是ros2 topic list能列出来但echo不到数据或者两个雷达的话题时有时无。我当时的解决方案是切换到Cyclone DDS一条命令就能指定sudo apt install ros-humble-rmw-cyclonedds-cpp export RMW_IMPLEMENTATIONrmw_cyclonedds_cpp后面这个export要写进~/.bashrc否则每次新终端都失效。切换之后双雷达话题的发现和订阅明显稳定很多。Fast DDS和Cyclone DDS它不是谁比谁强的问题而是不同网络拓扑下的适配问题。你在自己电脑上单机调试Fast DDS可能永远不出问题一旦把雷达、工控机、交换机连起来Cyclone DDS的稳健性优势就会显现。2.3 livox_ros_driver2编译注意CMake里的ROS2版本宏Livox官方的ROS2驱动仓库叫livox_ros_driver2直接拉下来编译就能用mkdir -p ~/livox_ws/src cd ~/livox_ws/src git clone https://github.com/Livox-SDK/livox_ros_driver2.git cd ~/livox_ws colcon build --symlink-install source install/setup.bash但这里有个特别容易忽略的细节仓库的CMakeLists.txt里有一块ROS2版本配置默认可能打开的是foxy相关的宏Humble需要改编译参数或手动打开humble对应的选项。如果你在没有修改的情况下直接编译有时候也能过但运行起来会出现点云话题没有时间戳、frame_id乱掉这种怪问题。用--symlink-install这个参数编译是我特别想强调的一个习惯。它会把安装目录建立成符号链接你改launch文件、配置文件以后不用重新build重启节点就生效。做调试的时候一天要改几十次配置文件这个参数能帮你省下大量等编译的时间。2.4 双雷达的config配置与frame_id隔离livox_ros_driver2支持一个节点下挂多颗雷达配置集中在json文件里核心是lidar_configs数组每个雷达一个条目。双雷达的config大概长这样我需要同时指定sn、topic_name、frame_id和端口。sn必须和雷达外壳上的实际编号严格一致这步错了驱动日志会报找不到设备。{ lidar_configs: [ { sn: M003860000001, topic_name: livox/lidar_1, frame_id: lidar_front, lidar_type: 1 }, { sn: M003860000002, topic_name: livox/lidar_2, frame_id: lidar_back, lidar_type: 1 } ] }我强烈建议两颗雷达的frame_id不要都用默认的livox_frame。如果你不做区分TF树里同一个frame_id会同时挂两帧点云RVIZ里看起来就是点云在同一个坐标系下互相穿插根本无法判断是哪个雷达的数据。改成lidar_front和lidar_back之后问题一下清晰了。启动时通过launch参数把这个config传进去ros2 launch livox_ros_driver2 livox_lidar.launch.py --config-file /path/to/dual_lidar.json启动后分别验证两个话题ros2 topic list | grep livox ros2 topic hz /livox/lidar_1 /livox/lidar_2两个话题都在10Hz左右稳定输出驱动这一步就算过了。3. 点云能不能合并先过“时间、坐标、畸变”这三关3.1 非重复扫描的去畸变为什么雷达自己也在“撒谎”这里必须解释清楚一个很多人忽略的问题激光雷达扫描一个完整帧是需要时间的。Mid-360一帧10Hz就是100毫秒在这一百毫秒里雷达内部的光机结构在运动载体本身也在运动。传统旋转式雷达每一帧内点是从一条扫描线上一个点一个点采过来的运动畸变的影响相对周期性好估计而非重复扫描更狠它的扫描点位是伪随机分布的在100毫秒内每个点的测量时刻对应的雷达位姿都不一样如果把这一帧所有点直接当成同一时刻的点云用高速旋转或急转弯时就会出现“拖尾”和“弯曲”现象。打个比方你拿着手机拍360度全景图如果身体边转边拍每一张照片的拍摄时刻不同最后拼出来的全景照片一定是扭曲的。点云也是同样的道理。驱动层默认输出的是原始点位不做运动补偿所以这部分需要你自己在下游处理。工程上最简单的方案是利用Mid-360内置的IMU做粗略补偿订阅雷达自带的/livox/imu话题对帧内每个点的时间戳插值出对应的姿态把点变换到帧起始时刻的坐标系下。如果你的机器人速度不快这个粗略补偿就够了如果要做高速场景那建议直接上FAST-LIO这类激光惯性里程计在线估计帧内运动。3.2 时间同步ApproximateTimeSynchronizer够不够用双雷达融合里两个雷达话题的时间戳不可能完全一致来自不同设备网卡DMA的中断时刻、驱动内部的buffering都会让两帧数据的时间戳差几毫秒到几十毫秒。ROS2的message_filters提供了ApproximateTimeSynchronizer专门解决这种时间戳不完全对齐的问题它在时间轴上搜索相近的消息把它们凑成一组送入回调。typedef message_filters::sync_policies::ApproximateTimePointCloud2, PointCloud2 SyncPolicy; message_filters::SynchronizerSyncPolicy sync(SyncPolicy(20), sub1, sub2);这个策略在低速场景下完全够用因为载体速度低几十毫秒内的位移可能只有几毫米到几厘米相对点云分辨率可以忽略。但如果你打算上高速场景或者雷达之间有明显的视野重叠需要做精细配准那就得考虑更严格的时间对齐比如硬件同步信号或者用IMU做时间外推。我在普通园区低速场景下ApproximateTimeSynchronizer用了很久没出问题所以对大多数移动底盘项目来说先别急着上复杂方案同步器够用了。3.3 外参标定方向写反是所有错位的头号嫌疑外参标定是双雷达融合的重中之重而且方向问题是最容易翻车的点。先明确一个约定我们需要求的是T_front_back也就是在lidar_front坐标系下表示的lidar_back坐标系位姿。融合时把lidar_back坐标系下的点乘上这个变换就能变换到lidar_front坐标系下这是最直观的“把后雷达点云搬进前雷达坐标系”。标定流程我分两步走。第一步是手工粗标定用米尺量出两颗雷达在车体上的安装相对位置估算旋转和平移然后启动RVIZ加载两个点云把后雷达点云手动旋转和平移到大致对齐。第二步是自动精标定采集一帧静止场景下的两片点云比如停在空旷停车场用PCL的NDT做粗配准再用ICP精配准把结果矩阵应用到外参上。整个流程通常能收敛到厘米级精度。我在第一次标定时犯过一个方向错误把T_front_back写成了T_back_front结果融合点云里同一个墙角出现了两个镜像位置像是照了哈哈镜。排查了半天才反应过来外参矩阵的作用方向反了。为了避免这个问题建议把外参发布成静态TF用TF树的可视化工具去检查各个坐标系之间的连线方向比盯着矩阵数字硬核多了。4. 写一个可落地的点云融合节点代码结构与流水线4.1 节点架构同步、变换、拼接、滤波一步都不能少融合节点我按四个阶段组织流水线先用message_filters做时间同步把两个雷达的点云凑成一组再用TF2做坐标变换把后雷达点云变换到前雷达坐标系下然后拼接成一片完整点云最后做体素滤波把密度降到下游算法能接受的水平。整个节点结构不难但每个阶段的细节都值得单独说明。先看代码骨架#include message_filters/subscriber.h #include message_filters/synchronizer.h #include message_filters/sync_policies/approximate_time.h using PointCloud2 sensor_msgs::msg::PointCloud2; class LidarFusionNode : public rclcpp::Node { public: LidarFusionNode() : Node(lidar_fusion_node) { rclcpp::QoS qos(10); qos.best_effort(); sub1_ std::make_sharedmessage_filters::SubscriberPointCloud2( this, /livox/lidar_1, qos); sub2_ std::make_sharedmessage_filters::SubscriberPointCloud2( this, /livox/lidar_2, qos); sync_ std::make_sharedmessage_filters::SynchronizerSyncPolicy( SyncPolicy(20), *sub1_, *sub2_); sync_-registerCallback(LidarFusionNode::callback, this); fused_pub_ create_publisherPointCloud2( /livox/fused_cloud, rclcpp::SensorDataQoS()); } private: using SyncPolicy message_filters::sync_policies::ApproximateTimePointCloud2, PointCloud2; void callback(const PointCloud2::ConstSharedPtr cloud1, const PointCloud2::ConstSharedPtr cloud2) { // 1. sensor_msgs 转 PCL // 2. 用TF2把cloud2变换到lidar_front坐标系 // 3. PCL拼接 // 4. VoxelGrid滤波 // 5. 发布融合点云 } };4.2 核心代码的关键细节QoS、TF方向、点云拼接第一处关键细节是QoS。雷达点云发布端的QoS策略通常是best_effort尽力传输因为传感器数据丢几帧无所谓但延迟要低。如果你用默认的reliable策略去订阅ROS2的通信机制会认为两端QoS不兼容数据根本传不过来。所以订阅点云话题时一定要设置best_effort。这也是很多新手在项目里第一次遇到“话题存在但收不到数据”的常见原因。第二处关键细节是TF2的变换方向。要把后雷达lidar_back的点云变换到前雷达lidar_front坐标系下需要调用lookupTransform(lidar_front, lidar_back, time, timeout)。注意参数顺序第一个是目标坐标系第二个是源坐标系。如果写成lookupTransform(lidar_back, lidar_front, ...)那变换方向就反了融合结果必错。类似这样方向写反的问题我不止一次在别人的代码里看到所以这里特意用代码和文字双重标注。第三处是拼接本身。PCL的concatenate可以把两个PointCloud直接拼成一个但前提是两个点云的字段类型一致。Livox输出的点云通常带反射率、tag、line这些字段融合前最好统一转成sensor_msgs::PointCloud2或者pcl的PointXYZI格式避免字段不匹配导致拼接时报错。// 伪代码梳理核心逻辑 pcl::PointCloudpcl::PointXYZI::Ptr cloud_front(new pcl::PointCloudpcl::PointXYZI); pcl::PointCloudpcl::PointXYZI::Ptr cloud_back(new pcl::PointCloudpcl::PointXYZI); // 从PointCloud2转成PCL格式 pcl::fromROSMsg(*cloud1, *cloud_front); pcl::fromROSMsg(*cloud2, *cloud_back); // TF2变换查找 lidar_front - lidar_back 的变换 geometry_msgs::msg::TransformStamped transform tf_buffer_-lookupTransform(lidar_front, lidar_back, rclcpp::Time(0)); Eigen::Matrix4f transform_matrix tf2::transformToEigen(transform).matrix().castfloat(); // 对cloud_back做刚体变换 pcl::transformPointCloud(*cloud_back, *cloud_back, transform_matrix); // 拼接 pcl::PointCloudpcl::PointXYZI fused; fused *cloud_front; fused *cloud_back; // 体素滤波 pcl::VoxelGridpcl::PointXYZI voxel; voxel.setLeafSize(0.05f, 0.05f, 0.05f); pcl::PointCloudpcl::PointXYZI::Ptr filtered(new pcl::PointCloudpcl::PointXYZI); voxel.setInputCloud(fused.makeShared()); voxel.filter(*filtered);4.3 多线程与Callback Group为什么两个订阅还是会互相阻塞ROS2节点默认是单线程执行回调的。如果你在一个默认节点的on_init里注册了多个订阅回调它们会被塞进同一个Callback Group串行执行。打个比方厨房只有一个灶头你要同时炒两个菜只能炒完一个再炒另一个。双雷达点云回调都不算快但如果第二个回调在等待TF变换或者做体素滤波时阻塞住第一个回调的数据就进不来表现出来就是融合节点CPU不高但掉帧严重。我当时的解法是给两个订阅回调分到不同的Callback Group并且用MultiThreadedExecutor让这两个回调真正并行执行。具体做法是创建两个Callback Group订阅器和同步器分别用不同的group。这样两个雷达数据可以同时在两个线程里被处理节点吞吐量直接上了一个台阶。group1_ create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive); group2_ create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive); rclcpp::SubscriptionOptions options1; options1.callback_group group1_; rclcpp::SubscriptionOptions options2; options2.callback_group group2_; sub1_ std::make_sharedmessage_filters::SubscriberPointCloud2( this, /livox/lidar_1, qos, options1); sub2_ std::make_sharedmessage_filters::SubscriberPointCloud2( this, /livox/lidar_2, qos, options2);用MultiThreadedExecutor需要把节点加入多个线程的executor在launch里或者main函数里设置线程数。我一般设置2到4个线程就够这种点云融合任务不像SLAM那样需要大量并发计算太多线程反而带来上下文切换开销。5. 调试期最容易翻车的几个问题与完整排查链路5.1 故障只出一颗雷达的点云另一颗完全没数据这个问题排查链路很有代表性按顺序来就不慌。第一打开Livox Viewer官方点云可视化工具看它能不能发现两颗雷达。如果Viewer里只能发现一颗那问题基本在硬件或网络层检查网线是否插紧、交换机端口是否正常、工控机网卡IP是否和雷达在同一网段。我之前遇到过一次雷达原厂默认IP是192.168.1.xxx工控机网卡却被配成了192.168.2.xxxViewer自然发现不了雷达。第二如果Viewer能发现两颗雷达但ROS2话题只有一个有数据那问题大概率在config文件。逐项检查sn字段是不是和雷达铭牌一致topic_name是否重复端口是否冲突。很多初学者喜欢复制前一个雷达的配置然后忘了改sn导致驱动一直在找同一台设备。第三如果config没错但驱动日志里有报错打开livox驱动节点的日志看状态码。雷达初始化失败的头两位数字能直接告诉你原因比如网络超时、固件不兼容、SN不匹配。整套排查下来很多次最后都发现是自己把sn里的字母和数字看混了0和O、1和I这类字符在雷达SN里出现频率很高注意用官方的扫码工具或者Livox Viewer复制。5.2 故障两个雷达都有点云融合后出现“重影”和错位现象是两颗雷达单独打开点云都正常融合节点发布后同一个墙角在点云里变成两个位置。先别动代码先把两个原始点云话题同时用RVIZ可视化把Fixed Frame设成lidar_front然后观察lidar_back的点云在哪个位置。如果明显偏离很大说明外参标定偏差太大或者外参方向反了。验证方向最简单的方法让两颗雷达都对准一堵平坦的墙面然后在RVIZ里看墙面点云。如果两面墙在融合后形成“V”字形夹角那几乎肯定外参的旋转部分反了或者lookupTransform参数写反了。还有一种情况是时间戳没同步好点云来自不同时刻的位置这在车辆低速时不太明显但只要你手动晃一下机器人重影就会加剧。排查时把两个雷达的原始话题在不同时刻echo出来对比时间戳如果时间戳差了几百毫秒去检查ApproximateTimeSynchronizer的队列深度和容忍时间窗。顺带说一个我在融合节点里很容易犯的错误发布融合点云时frame_id写错了。我一开始把融合点云的frame_id写成了lidar_back但点云内容已经全部变换到lidar_front导致下游所有使用融合点云的程序外参全部错乱。融合点云的frame_id必须是变换后所在的那个坐标系这一点在代码里写注释标红都不过分。5.3 故障话题订阅不到以及CPU突然飙升订阅不到话题最常见的两个原因分别是domain_id不一致和QoS不兼容。domain_id是ROS2网络隔离的标识发布端和订阅端的domain_id必须一致否则互相发现不了。你在多个机器之间通信时默认domain_id都是0一般不会踩坑但如果你之前为了调试改过~/.bashrc里的ROS_DOMAIN_ID那就会非常隐蔽。QoS不兼容前面已经提过这里再强调一遍Livox驱动发布点云用的是sensor_data策略近似等于best_effort所以订阅端用默认reliable很容易订阅失败。一条命令就能验证ros2 topic echo /livox/lidar_1 --qos-reliability best_effort如果这样能echo到数据而直接用ros2 topic echo收不到那就是QoS的问题。CPU飙升的原因则相对简单。双雷达点频叠加后约40万点/秒如果你在每个回调里都做TF查询、坐标变换、体素滤波再加上RVIZ实时渲染CPU很容易打满。我的优化办法是把体素滤波的leaf size从0.02米放宽到0.05米叶大小从2厘米变5厘米点数量会以三次方级别下降但目标识别精度影响不大。另外平时调试时不要把多个rviz2窗口同时挂着那家伙吃CPU比融合节点还狠。现象最可能原因快速验证手段只出一颗雷达config里sn错误或网络不通Livox Viewer能否发现检查sn融合后点云重影外参方向写反或时间戳不对静止场景看原始点云是否稳定重合话题订阅不到QoS不兼容或domain_id不同echo时指定--qos-reliability best_effortCPU飙升融合后点云太密调大体素滤波leaf size减少rviz2实例6. 实际效果与几个值得长期坚持的操作习惯6.1 实测效果盲区消失密度翻倍但这个结果不是白来的把整个pipeline跑通之后效果其实非常直观。融合前车尾方向在RVIZ里是稀疏的几根点遇到细长障碍物时基本靠猜融合后车尾方向的地面、墙体、立柱都清晰可见。点云密度方面车顶重叠区域能达到单雷达的2倍左右但体素滤波后单帧的点数量并没有爆炸式增长因为滤波把重叠区域的冗余点给合并了实际投入算法计算的点数大概从单雷达的2万到4万变成了融合后的3万到6万根据场景遮挡程度浮动。这些点对下游的障碍物聚类和八叉树地图构建来说是非常充足的输入。当然这个效果有三个前提外参标定准确、时间同步窗口合理、体素滤波参数合适。三者缺一个融合后的点云质量都会大打折扣甚至比单雷达更差。有一次我把体素滤波leaf size改成了0.01米想“保留更多细节”结果融合点云直接把导航算法卡到1Hz整台车走走停停。所以我后来习惯在参数文件里把leaf size单独抽出来作为可配置参数而不是写死在代码里。6.2 几个值得长期坚持的操作习惯第一用ros2 bag记录原始数据。调试双雷达融合时如果只是看着RVIZ窗口里点云形状不对就上手改外参很容易陷入“改一个参数、重启一次、看一眼”的无限循环。我的做法是先用ros2 bag record把双雷达话题完整录下来然后离线回放、离线调参改外参、改滤波、改同步策略都是纯离线完成速度快得多而且可复现。第二外参标定结果要做版本管理。每次标定完把T_front_back写进一个yaml文件文件名带上日期、机器人编号和标定方法比如calib_robo01_20240610_icp.yaml。不然三个月后你根本不知道当前跑的外参是哪一次标定的底盘结构动过以后旧外参还会继续坑你。第三统一用base_link作为枢纽坐标系。双雷达融合不一定非要直接算两个雷达之间的外参更清晰的做法是把两颗雷达的外参分别标定到base_link上融合时都通过base_link中转。这样虽然多了一次坐标变换但逻辑清楚得多以后要加第三颗雷达只要多标一组base_link到新雷达的外参就行不用管其它雷达之间怎么互相变换。第四colcon build一定要用--symlink-install。这个前面提过但这里再说一次是因为在双雷达调试中launch文件和config文件的修改频率极高没有符号链接机制你每次改配置文件都要重新编译几天下来浪费的时间至少以小时计。最后再说一点个人体会做完这个项目回过头来看真正花时间的不是把两片点云“拼”起来而是把时间、坐标系这些看不见的约定对齐。我踩过最大的坑就是一开始急着想做出一个看起来很炫酷的融合效果结果在标定方向写反、QoS不兼容、时间戳不同步这些基础问题上反复折腾反而是先用低速静止场景把pipeline跑通之后复杂问题一个个迎刃而解。如果你也正在做类似的双雷达方案我的建议很朴素先把车停着不动手工量出一个大致外参跑通整条链路再去追求标定精度和运动补偿。这个顺序至少帮我省了一整天的排查时间。