
1. 为什么联合标定是激光雷达与相机融合的第一道坎做过多传感器融合的人都有一个共识激光雷达和相机各自跑得再好只要外参标定不准融合结果就是灾难。点云投到图像上偏个十几像素目标检测的框就会飘到天上反过来图像里的语义信息映射到点云上错位后续的聚类、跟踪全都会跟着崩。我见过太多人在这上面栽跟头明明算法没问题最后发现是标定参数差了那么一点点。这次要聊的是Livox激光雷达和USB相机的联合标定而且是在ROS2环境下、不用专门做目标标定的方式来完成。所谓“无目标标定”指的是不需要制作特定的标定板或者标定物直接利用场景中的自然特征来完成外参求解。这种方式的好处很明显省去了制作和摆放标定板的麻烦尤其适合Livox这种非重复扫描的固态激光雷达——它的点云分布和传统机械式雷达完全不同用棋盘格标定反而容易出问题。Livox系列雷达不管是Mid-360、Avia还是Mid-40它们的扫描方式都是非重复式的点云在空间中的分布会随着积分时间变化。这意味着你在某一帧里看到的点下一帧可能就换了一批位置。传统基于平面特征的标定方法对这类雷达并不友好因为平面提取本身就不稳定。而无目标标定更多依赖边缘、角点这类几何特征配合图像的边缘信息做匹配反而更鲁棒。这篇文章适合谁看如果你正在做ROS2下的多传感器融合项目手里有Livox雷达和普通USB相机想快速把外参标出来跑通后续的感知或建图流程那这篇内容就是为你准备的。我会从环境准备、数据采集、标定原理、实操步骤到问题排查一步步拆开讲尽量把每个环节的“为什么”说清楚让你不只是抄作业而是真正理解背后的逻辑。2. 环境准备与依赖梳理2.1 ROS2版本选择与Livox驱动安装ROS2的版本选择上我建议直接用Humble。Foxy虽然也支持Livox但很多新的功能包已经不再维护Foxy分支了Humble的生态更完整社区支持也更好。Ubuntu 22.04搭配Humble是目前最稳的组合安装方式用鱼香ROS的一键安装脚本就行省去配源的麻烦。Livox的ROS2驱动在GitHub上有官方仓库编译之前先确认依赖装齐了sudo apt install -y ros-humble-pcl-ros ros-humble-pcl-conversions \ ros-humble-cv-bridge ros-humble-image-transport \ ros-humble-camera-info-manager libpcl-dev libeigen3-devLivox SDK2是必须的驱动依赖它来跟雷达通信。编译顺序是先装SDK2再编译livox_ros_driver2。这里有个坑SDK2的安装路径默认是/usr/local如果之前装过旧版SDK记得先卸干净否则编译驱动时会报链接错误。USB相机这边如果是普通的UVC相机ROS2下有usb_cam和v4l2_camera两个常用包。我个人更推荐v4l2_camera它对参数的控制更细而且支持动态改分辨率、帧率这些标定的时候方便调整。安装命令sudo apt install ros-humble-v4l2-camera2.2 标定工具链的选型考量无目标标定的工具链核心是lidar_camera_calibration这类包但很多现成的包都是针对Velodyne那种重复扫描雷达设计的。Livox的点云格式和帧结构不一样直接拿来用会出问题。我的做法是基于direct_visual_lidar_calibration的思路自己搭一套适配Livox的流程。这个思路的核心是利用图像中的边缘特征和点云中的深度不连续区域做匹配通过优化重投影误差来求解外参。不需要标定板但需要场景中有足够的纹理和几何结构。走廊、办公室、实验室这类环境就很合适纯白墙的房间不行因为提取不到边缘。需要的依赖包括OpenCV 4.x用于图像处理PCL 1.12点云处理Ceres Solver非线性优化Eigen3矩阵运算这些在Ubuntu 22.04下都可以通过apt直接装版本兼容性没问题。2.3 硬件连接与时间同步检查Livox雷达通过网线连接默认IP是192.168.1.1XX网段。Mid-360的默认IP是192.168.1.1XX具体看型号。电脑端需要把网卡配到同一网段比如192.168.1.50子网掩码255.255.255.0。配完之后ping一下雷达的IP通了再往下走。USB相机直接插上就行用v4l2-ctl --list-devices确认设备号。时间同步这块如果只是做离线标定不要求硬同步但采集数据时尽量让雷达和相机同时启动减少时间偏移。如果要在线融合那就得考虑PTP或者GPS授时了那是另一个话题。注意Livox雷达的IP配置在livox_ros_driver2/config/MID360_config.json里里面的host_net_info和lidar_configs都要改对否则驱动起不来。3. 无目标标定的核心原理拆解3.1 为什么不用标定板也能标传统标定板方法的核心是已知标定板上的角点在世界坐标系中的位置通过图像检测到这些角点的像素坐标再通过点云找到对应角点的三维坐标构建2D-3D的对应关系求解PnP问题得到外参。这个流程很成熟但前提是标定板要能被雷达和相机同时清晰观测到。Livox雷达的问题在于它的点云密度分布不均匀标定板上的角点在点云里可能根本找不到对应的点。而且非重复扫描意味着同一块标定板在不同帧里的点云形态不一样提取角点非常不稳定。无目标标定换了个思路不依赖已知结构的标定物而是从场景本身提取特征。具体来说图像里用Canny或者Sobel提取边缘点云里用深度不连续或者法向量变化提取边缘点然后让这两组边缘特征在投影后对齐。优化的目标就是让图像边缘和点云投影边缘的重合度最高。这个思路的数学本质是寻找一个外参矩阵T使得点云中的边缘点投影到图像平面后落在图像边缘上的概率最大。可以用互信息、归一化互信息或者边缘距离场来构建损失函数然后用Ceres做非线性优化。3.2 Livox点云的特殊处理Livox的点云和传统雷达有个很大的区别它的点云不是按方位角均匀分布的而是按时间顺序排列的。一帧点云里点的密度在空间上是不均匀的中心区域密边缘区域稀。这就导致直接做边缘提取时稀疏区域的边缘会断断续续。我的处理方式是先对点云做体素滤波把密度均匀化然后再计算每个点的法向量用法向量突变来提取边缘点。具体来说如果一个点的法向量和它邻域内其他点的法向量夹角超过阈值就认为它是边缘点。这个阈值一般设在30度到45度之间具体看场景。另外Livox的点云强度信息也可以利用。某些材质的边缘在强度图上表现很明显可以辅助边缘提取。不过这个不是必须的视场景而定。3.3 外参初值与优化策略无目标标定对初值比较敏感如果初值太离谱优化很容易陷入局部最优。我的做法是分两步走先手动给一个粗略初值然后用多分辨率策略做优化。手动初值怎么给如果雷达和相机是刚性连接可以大致测量它们之间的相对位置和角度。比如相机在雷达正前方10厘米光轴和雷达坐标系X轴平行那初值就是平移(0.1, 0, 0)旋转为单位矩阵。这个精度足够了不需要很准。优化的时候先用降采样的图像和点云做粗优化步长设大一点快速收敛到大致正确的区域。然后再用原始分辨率做精优化步长调小提高精度。这个过程类似图像配准里的金字塔策略能有效避免局部最优。损失函数的选择上我试过互信息和边缘距离场两种。互信息对光照变化更鲁棒但计算量大边缘距离场收敛更快但对边缘提取的质量依赖高。实际用下来边缘距离场在室内场景下效果更好因为室内边缘清晰提取稳定。4. 实操全流程从数据采集到外参输出4.1 数据采集的规范与技巧数据采集这一步看着简单但很多标定失败的原因都出在这里。我总结了几条硬性要求第一场景要有足够的几何特征。走廊、楼梯间、有家具的办公室都行但纯白墙的房间不行。场景里最好有不同深度的物体比如近处有个箱子远处有面墙这样点云的深度变化丰富边缘提取更可靠。第二采集时雷达和相机要静止。虽然理论上可以边走边采但运动会导致时间戳对不齐增加标定误差。静止采集每个位置停留几秒让雷达积累足够的点云。第三采集多个视角。一般建议至少采集5到8组数据每组对应一个不同的位姿。位姿变化要覆盖三个旋转轴和三个平移轴不能只在一个方向上变。比如你可以先正对场景然后左转、右转、上仰、下俯再前后移动。第四记录数据时同时保存图像和点云。ROS2下可以用rosbag录但要注意bag的体积。Livox的点云数据量很大录几分钟就上G了。我的做法是只录关键帧每组数据录2到3秒够用就行。# 录制bag的示例命令 ros2 bag record /livox/lidar /image_raw /camera_info -o calib_data_01录完之后用ros2 bag info确认一下话题和帧数确保数据完整。4.2 点云与图像的预处理原始数据不能直接拿来标定得先做预处理。点云这边先做体素滤波降采样体素大小设0.02米左右既能保留细节又能减少计算量。然后计算法向量用PCL的NormalEstimation邻域半径设0.05米。图像这边先做去畸变。USB相机的畸变参数需要提前标定好用ROS2的camera_calibration包就行拍十几张棋盘格照片跑一下就能得到内参和畸变系数。去畸变之后再做直方图均衡化增强边缘对比度。边缘提取用Canny阈值根据图像亮度动态调整。我一般用cv::Canny的自动阈值模式或者手动设成低阈值50、高阈值150。提取完边缘后做一次形态学膨胀把断掉的边缘连起来。点云边缘提取的代码大概长这样import pcl from pcl import NormalEstimation # 体素滤波 vg cloud.make_voxel_grid_filter() vg.set_leaf_size(0.02, 0.02, 0.02) cloud_filtered vg.filter() # 法向量估计 ne cloud_filtered.make_NormalEstimation() ne.set_RadiusSearch(0.05) cloud_normals ne.compute() # 边缘点提取法向量夹角超过阈值 edge_points [] for i in range(cloud_normals.size()): normal_i cloud_normals[i][3:6] neighbors get_neighbors(cloud_normals, i, radius0.05) for j in neighbors: normal_j cloud_normals[j][3:6] angle compute_angle(normal_i, normal_j) if angle np.radians(35): edge_points.append(cloud_filtered[i]) break这段代码是示意性的实际用的时候要注意PCL的Python绑定版本有些接口不太一样。4.3 外参求解的完整实现外参求解的核心是一个非线性优化问题。我定义损失函数为点云边缘点投影到图像后到最近图像边缘点的距离之和。用Ceres Solver来优化这个损失函数变量是外参的6个自由度3个旋转3个平移。旋转用四元数表示避免万向锁。平移就是普通的3维向量。优化的时候先固定内参只优化外参。如果内参也不准可以一起优化但那样变量太多容易过拟合。// Ceres优化外参的简化示例 struct ReprojectionError { ReprojectionError(cv::Point2f img_edge_point, pcl::PointXYZ lidar_point) : img_pt_(img_edge_point), lidar_pt_(lidar_point) {} template typename T bool operator()(const T* const q, const T* const t, T* residual) const { // 四元数转旋转矩阵 Eigen::QuaternionT quat(q[0], q[1], q[2], q[3]); Eigen::MatrixT, 3, 3 R quat.toRotationMatrix(); // 点云变换 Eigen::MatrixT, 3, 1 pt(T(lidar_pt_.x), T(lidar_pt_.y), T(lidar_pt_.z)); Eigen::MatrixT, 3, 1 pt_cam R * pt Eigen::MatrixT, 3, 1(t[0], t[1], t[2]); // 投影到图像 T u pt_cam[0] / pt_cam[2]; T v pt_cam[1] / pt_cam[2]; // 残差投影点到图像边缘点的距离 residual[0] u - T(img_pt_.x); residual[1] v - T(img_pt_.y); return true; } cv::Point2f img_pt_; pcl::PointXYZ lidar_pt_; };优化的时候先用粗分辨率跑200次迭代再用精分辨率跑500次。每次迭代后检查残差变化如果连续几次变化小于阈值就提前退出。优化完得到外参矩阵用cv::projectPoints把点云投影到图像上肉眼检查对齐效果。如果边缘基本重合说明标定成功如果偏差很大要么是初值太离谱要么是数据有问题。4.4 标定结果的验证与评估标定完不能直接用得验证。我的验证方法是用标定得到的外参把点云投影到图像上然后看几个关键指标。第一个指标是边缘重合度。计算投影后的点云边缘和图像边缘的平均距离如果小于3个像素就算合格。如果大于5个像素说明标定有问题得重新来。第二个指标是重投影误差的分布。把误差画成直方图如果大部分误差集中在0附近说明标定稳定如果误差分布很散说明有些点匹配错了需要剔除离群点重新优化。第三个指标是不同视角的一致性。用同一组外参去投影不同视角的数据如果每个视角的误差都差不多说明外参是全局最优的如果某个视角误差特别大说明那组数据有问题或者外参只在那组数据上过拟合了。我一般会留一两组数据不做标定专门用来验证。这样能真实反映外参的泛化能力。5. 常见问题与排查技巧实录5.1 点云和图像对不齐的典型原因标定完发现点云投到图像上偏了这是最常见的问题。原因通常有这几个一是时间戳没对齐。如果雷达和相机的数据不是同一时刻的场景里的物体动了投影就会偏。解决办法是采集时保持静止或者用硬件触发做同步。二是内参不准。相机的内参和畸变系数如果标定得不好投影误差会很大。建议用camera_calibration多标几遍取误差最小的那组。三是外参初值太离谱。如果初值偏差超过30度或者半米优化很容易陷入局部最优。这时候手动调一下初值或者用多个初值分别优化取最好的结果。四是点云边缘提取有问题。Livox的点云在远处很稀疏边缘提取会断。可以限制点云的距离范围比如只取10米以内的点远处的点直接扔掉。5.2 Livox驱动常见报错与解决Livox驱动编译或者运行的时候经常会遇到几个报错。我整理了一个速查表报错信息原因解决办法Failed to init livox sdkSDK2没装或者路径不对重装SDK2确认/usr/local/lib下有liblivox_sdk2.soNo lidar foundIP配置不对检查MID360_config.json里的IP和电脑网段是否一致Point cloud format error点云格式不匹配确认驱动版本和雷达型号对应Mid-360用livox_ros_driver2Timestamp jump时间同步问题检查PTP配置或者改用软件时间戳rviz2不显示点云QoS配置不对把rviz2的QoS设为Best EffortLivox默认是Best Effort提示Livox的点云话题默认是/livox/lidar但有些版本是/livox/lidar_1用ros2 topic list确认一下。5.3 标定精度提升的独家技巧几个我踩坑之后总结的技巧能明显提升标定精度第一采集数据时让场景里有明显的深度边缘。比如在墙角放个纸箱纸箱的边缘在点云和图像里都很清晰匹配起来更准。第二图像边缘提取时用亚像素精度。OpenCV的cornerSubPix可以把边缘定位到亚像素级别虽然计算量增加但精度提升很明显。第三点云边缘提取时用法向量和曲率双重判据。光看法向量夹角有时候会误判加上曲率阈值能过滤掉很多噪声。第四优化时加鲁棒核函数。Ceres的HuberLoss能有效抑制离群点的影响让优化更稳定。第五多组数据联合优化。不要一组一组单独标把所有数据放在一起优化让外参在所有数据上同时最优。这样得到的参数泛化能力更强。5.4 从标定到融合的衔接要点标定完只是第一步后面还要把外参用到实际的融合流程里。这里有几个衔接要点外参的存储格式要统一。我一般存成4x4的变换矩阵平移在前旋转在后或者用ROS2的tf2发布成静态变换。用tf2的好处是其他节点可以直接查询不用手动传参。坐标系的定义要明确。Livox的坐标系一般是X向前、Y向左、Z向上相机的坐标系是X向右、Y向下、Z向前。标定得到的外参是从雷达坐标系到相机坐标系的变换用的时候别搞反了。融合的时候要注意时间同步。如果做在线融合雷达和相机的数据流要时间对齐可以用message_filters做近似时间同步。如果做离线融合那就简单了直接按时间戳匹配就行。最后标定参数不是一劳永逸的。如果雷达或者相机被碰过外参就变了得重新标。我一般会在机器人上做个标记每次启动前检查一下标记有没有偏移有偏移就重新标定。6. 实操心得与后续扩展这套无目标标定流程我前后跑了十几遍从最开始的一头雾水到后来能稳定出结果中间踩的坑不少。最大的体会是数据质量决定标定上限优化算法只是逼近这个上限。如果采集的数据本身就有问题再好的优化也救不回来。所以我现在花在数据采集上的时间比花在调参上的时间多得多。另一个心得是不要迷信自动标定。自动标定出来的结果一定要肉眼验证。我习惯把点云投影到图像上放大看边缘对齐情况。有时候数值指标很好但肉眼一看就发现某个区域偏了这种情况往往是局部过拟合得重新调。后续如果想进一步提升可以考虑几个方向一是加入IMU做联合标定利用运动信息约束外参二是用深度学习提取特征替代传统的边缘提取在纹理弱的场景下更鲁棒三是做在线标定让机器人在运行过程中自动修正外参适应长期运行中的漂移。最后分享一个小技巧标定的时候把雷达和相机固定在一个刚性支架上支架上贴几个反光标记点。这样每次重新标定时可以用标记点快速检查外参有没有变省去重新采集数据的麻烦。这个法子我在多个项目里用过很实用。