☰
激光雷达与相机外参标定实战:基于livox_camera_calib的完整流程与避坑指南
2026/10/7 11:57:29 网站建设 项目流程

先用一句话给这篇文章定个性:它不是官方README的复读,而是我从第一次标定翻车到最终稳定成功之后,沉淀下来的一套完整操作流和排查思路。

我最早在某台巡检机器人上做激光雷达和相机融合。当时觉得自己挺懂,直接拿结构设计图纸上的安装尺寸填了外参,把雷达点云往图像上一投影,结果板子边缘整整偏了半米。后来用Livox官方的livox_camera_calib工具重新标定,才把误差压到几个像素以内。这个工具解决的核心问题很简单:算出激光雷达坐标系和相机坐标系之间的旋转矩阵R和平移向量t,也就是外参。它适用于Livox系列雷达(Mid-360、Horizon、Avia)搭配普通USB相机、工业相机或Realsense的场景,也是后续做点云着色、多传感器目标检测、Cartographer建图并保存地图这些功能的前置步骤。

这篇文章会先讲清楚工具的标定原理,再一步步说标定板制作、数据采集、launch配置和结果验证。最后一部分是我在实际项目中踩过的坑和完整排查链路,这部分在别的教程里很少见到,但恰恰是最能帮你省时间的。

1. livox_camera_calib的标定逻辑:为什么不找点,而是找平面

1.1 外参是什么,为什么不能靠尺子量

外参描述的是两个坐标系之间的刚体变换。雷达坐标系中的任意一个点p_lidar,要投影到相机图像上,需要先通过外参变换到相机坐标系:p_camera = R * p_lidar + t。然后再用相机内参做针孔投影,得到像素坐标。

用尺子量出来的平移t还能勉强凑合,但旋转R很难量准。传感器安装时哪怕偏了1度,在10米外就会造成超过17厘米的横向误差,反映到图像上可能就是几十个像素的偏移。所以外参必须通过标定来求,而不是靠机械加工精度来保证。

1.2 平面约束:两块传感器都能稳定提取的特征

很多人第一次接触标定,第一反应是找两个传感器的共同角点,比如棱角、反光贴。但激光雷达在远距离上的点云很稀疏,想从点云里精确提取一个几何角点非常困难。livox_camera_calib避开这个问题,改用平面来做约束。

标定板在相机图像里是清晰的棋盘格,可以通过OpenCV的角点检测和solvePnP求出棋盘格平面在相机坐标系下的位姿,进而得到该平面的法向量n_c和原点距平面距离d_c。

在雷达点云中,工具利用Livox雷达的反射率特性,把标定板上的点从背景点云中分割出来,然后用这些点拟合一个三维平面,得到雷达坐标系下的法向量n_l和距离d_l。

同一个物理平面,在两个传感器坐标系里分别有不同的表示,但它们通过外参联系在一起。法向量满足n_c = R * n_l,距离满足d_c = d_l + n_l^T * R^T * t。每采集一个位姿的标定板数据,就能得到一组平面约束,把所有帧的约束放进非线性最小二乘里优化,就能解出R和t。

1.3 为什么要摆多个姿态,摆不好会怎样

如果所有帧里标定板都正对着传感器,那平面法向量几乎都指向同一个方向。这种情况下,约束方程之间高度相关,优化问题在数学上退化,旋转的几个自由度根本约束不住。最后出来的外参可能在你采集的那一个特殊方向上误差很小,换个方向就偏得离谱。

打个比方:你闭着一只眼睛,只看一个固定方向的光斑,是无法判断光源和你的空间距离的,只有左右摇头、远近走动,才能从光斑移动中反推出位置关系。标定板姿态就是这个道理。

所以录制数据时,标定板一定要覆盖正对、左右偏转、上下俯仰、画面四角斜放这些不同姿态,法向量方向尽量分散。我一般采集12到15个位姿,每个位姿保持2到3秒,低于8个位姿时外参稳定性会明显下降。

2. 标定前的准备:标定板、相机内参和话题类型

2.1 标定板制作最容易翻车的三个细节

工具示例一般用9x6的内角点棋盘格,方格边长108mm。打印时记住三点:

  1. 不要用光面铜版纸,反光会让角点检测出现高光斑块,导致检测失败
  2. 棋盘格必须贴在完全平整的硬板上,推荐5mm以上的亚克力板或铝塑板,不要用软木板
  3. 板子四周留几厘米白边,方便点云分割时把板子区域和背景区分开

我第一次图省事,把棋盘格贴在软木板上,结果木板拿起来会弯曲,图像侧角点检测没问题,但点云侧拟合出来的平面法向量和图像侧算出来的法向量不一致,优化器只能在两者之间取一个折中,最终外参怎么调都对不齐。后来换硬板,问题当场消失。

另外多说一句,雷达侧识别标定板靠的是棋盘格黑白格在905nm红外波段反射率的差异。不要把棋盘格贴在反光不锈钢板上,否则白格在点云里的反射率会变得异常,影响平面分割。

2.2 相机内参必须提前搞定,而且要用锁焦后的参数

livox_camera_calib只标外参,不标内参,所以相机内参的准确性直接决定外参上限。如果内参本身有偏差,外参再准也无法把点云投到正确像素上。

内参获取有几个途径:

  • 用OpenCV棋盘格标定程序,拍25张以上不同角度的标定板图像,覆盖画面中心和边缘
  • 用Kalibr标定鱼眼镜头或大畸变镜头
  • Realsense等深度相机可以用出厂内参,但最好还是自己重标一遍,因为镜头批次和温度都会影响出厂参数

有一个细节很多人忽略:标定内参之前,要先把相机的自动对焦、自动曝光、自动白平衡全部关掉,手动锁焦锁曝光。如果相机自动对焦,焦距一直在变,内参就不可能稳定。

2.3 雷达点云话题格式先对齐,不然后面全是坑

Livox的驱动经历过两个阶段:早期livox_ros_driver发布sensor_msgs/PointCloud2,后期livox_ros_driver2默认发布自定义的livox_ros_driver2/CustomMsg。livox_camera_calib按PointCloud2设计,如果话题类型不匹配,节点会直接不工作。

判断话题类型很简单:

rostopic info /livox/lidar

如果显示的类型不是sensor_msgs/PointCloud2,需要在雷达驱动launch里找消息格式相关参数(不同版本名字不一样,常见是msg_type或point_format),把输出切换成PointCloud2。也可以用一个小转换节点把CustomMsg转成PointCloud2。这一步建议在录数据之前就解决,不要录完bag再回来折腾。

3. 采集数据:标定成败的80%取决于这一步

3.1 录制前先做传感器静态检查

很多人拿到工具就急着录,结果后面反复重录。我建议录制前花五分钟做三件事:

  • 确认雷达和相机之间的连接件锁紧,不会在采集过程中晃动
  • 在rviz里同时订阅点云和图像,把标定板放到画面中央,确认两个传感器都能清晰看到
  • 用手轻轻按压安装连接件,观察点云相对图像有没有偏移,如果有明显晃动,先解决机械问题

Mid-360虽然水平FOV是360度,但实际标定时最好把标定板放在雷达正前方60度扇区内,这里的点云密度最高,平面拟合更稳定。

3.2 标定板姿态序列:照这个顺序来

采集时最好有两个人配合,一个人移动板子,一个人观察rviz里点云和图像是否同步。实在没人配合,就把板子固定在三脚架上,移动三脚架也比手举着稳得多。

我用的姿态序列供参考:

  1. 板子正对传感器,距离2.5米左右,停留3秒
  2. 板子向左偏约45度,停留3秒
  3. 板子向右偏约45度,停留3秒
  4. 板子上仰,让传感器俯视板面,停留3秒
  5. 板子下俯,让传感器仰视板面,停留3秒
  6. 板子放到画面左上角,停留3秒
  7. 板子放到画面右下角,停留3秒
  8. 再补2到3组远近交替的位姿

距离控制在2米到5米之间。太近时板子在图像里占不满,角点检测容易失败;太远时雷达落在板子上的点数太少,平面拟合噪声大。

3.3 录制bag的参考命令

以ROS1 noetic为例,开三个终端:

# 终端1:启动雷达驱动 roslaunch livox_ros_driver livox_lidar.launch # 终端2:启动相机驱动,确保发布camera_info roslaunch usb_cam usb_cam.launch # 终端3:录制bag,注意把camera_info一并录进去 rosbag record -O calib_0803.bag \ /livox/lidar \ /usb_cam/image_raw \ /usb_cam/camera_info

Topic名以你实际环境为准。我遇到过有人只录了/image_raw没录/camera_info,后面想排查内参问题就得重新回现场录,非常痛苦。所以相机内参消息一定要录进去。

整个录制过程1到3分钟就够,换姿态时不要急,宁可每个位姿多停一会儿。

4. 跑通标定流程:编译、launch参数和结果解读

4.1 创建catkin工作空间并编译

Livox官方仓库的编译流程很常规:

mkdir -p ~/livox_ws/src cd ~/livox_ws/src git clone https://github.com/Livox-SDK/livox_camera_calib.git cd ~/livox_ws catkin_make

如果编译缺依赖:

sudo apt install ros-noetic-pcl-ros ros-noetic-camera-calibration-parsers

优化部分依赖Ceres库,如果编译时提示找不到Ceres,先安装ros-noetic-libceres或从源码编译Ceres。旧版本Ceres可能导致部分代码无法编译,建议装系统源里的较新版本。

4.2 launch文件里的关键参数怎么填

打开launch文件后,重点检查这几项:

参数说明建议
image_topic相机图像话题/usb_cam/image_raw
cloud_topic雷达点云话题/livox/lidar
camera_info_topic相机内参消息/usb_cam/camera_info
board_col / board_row棋盘格内角点数9x6或你的实际值
square_size方格边长,单位米0.108
初始外参优化初值用卷尺量出来的粗略值

square_size这个参数单位是米,很多人会顺手填成108,导致平面板尺寸完全错误。棋盘格如果是打印的,最好用游标卡尺量一下实际方格边长,打印缩放会产生微小误差。

初始外参尽量给一个接近真实的值。用卷尺量一下雷达和相机光心之间的平移,旋转可以先按安装方向给一个大概的偏航角。不要全填零,否则非线性优化很容易掉进局部最优。

4.3 启动节点并回放bag

配置完成后:

# 终端A:启动标定节点 source ~/livox_ws/devel/setup.bash roslaunch livox_camera_calib calib.launch # 终端B:回放录制的数据 rosbag play calib_0803.bag --clock

正常情况下,终端会持续输出检测到棋盘格、点云平面拟合成功的信息,并显示当前优化迭代的误差。如果运行半天没有任何输出,优先检查launch里的话题名是否和bag里一致。

需要注意--clock的作用是发布/clock消息,配合use_sim_time让节点时间轴跟随bag走,但它并不会改变消息头里的时间戳。真正的同步依据还是头时间戳,录制时雷达和相机的时间戳偏差才是关键。

4.4 结果怎么看,怎么保存

优化完成后,终端会打印旋转矩阵和平移向量。看到结果后先别急着关窗口,输出有以下几种可能:

  • 输出一个4x4变换矩阵,这就是最终外参
  • 输出R和t分开,需要自己拼成齐次矩阵
  • 有些版本还会弹出一个可视化窗口,显示点云平面投影到图像的效果

拿到外参后,我建议立刻写一个静态TF发布脚本或者yaml文件,不要只记在终端里。yaml格式大致如下:

rotation_matrix: [0.999, -0.015, 0.012, 0.015, 0.999, 0.004, -0.012, -0.004, 0.999] translation_vector: [0.098, -0.045, 0.021]

后续工程直接加载这个yaml就行。

5. 避坑指南:我在实操中踩过的七个坑

5.1 棋盘格角点检测反复失败

现象是终端一直提示找不到棋盘格。排查顺序:

  1. 先单独播放bag里的图像,看标定板是否完整在画面内,有没有被裁边
  2. 检查光照,棋盘格反光会在角点附近形成高光斑块,导致检测失败
  3. 检查board_col和board_row是不是内角点数,不是格子数。9x6是内角点9列6行,对应格子是10x7
  4. 确认相机话题是去畸变后的图像,如果livox_camera_calib内部直接用原始图,大畸变镜头会检测失败

我遇到过最无语的情况是摄像头自动白平衡导致棋盘格颜色偏色,角点检测会偶发失败,锁掉自动调节后一次通过。

5.2 点云平面提取不到或拟合不稳定

这个坑的表现是节点提示点云数量不足、平面拟合失败。原因通常是:

  • 标定板离太远,落在板上的点数太少
  • 点云范围裁剪参数设得太小,把板子点也滤掉了
  • 标定板边缘产生飞点,被错误算进平面点集

解决办法是先在rviz里用Region Selection量出标定板的空间范围,然后裁剪参数设置得比板子范围略大一点。同时观察板子在点云里有没有明显空洞,如果有,可以试试调整反射率阈值,或换一个更大的标定板。

5.3 点云话题类型不匹配导致节点不工作

如果你回放bag时节点完全没有反应,用rostopic info确认话题类型。我见过不少人在livox_ros_driver2环境下直接把 /livox/lidar 填进launch,但节点收不到数据。

解决办法:

  • 雷达驱动配置里切到PointCloud2输出
  • 自己写一个CustomMsg转PointCloud2的转化节点
  • 换个使用sensor_msgs的驱动版本

这个话题类型问题和外参无关,但它是排在所有问题前面的"拦路虎"。

5.4 时间戳不同步导致同步丢帧

livox_camera_calib内部用的是近似时间同步,允许一定时间差。但如果雷达和相机同一时刻的时间戳相差太大,同步器会丢帧,最终表现为明明录了很多数据,节点实际处理的却很少。

排查方法是看两个话题消息头时间戳的差值:

rostopic echo /livox/lidar/header/stamp -n1 rostopic echo /usb_cam/image_raw/header/stamp -n1

如果差值超过几十毫秒,考虑在launch里加固定时间补偿,或者换用支持时间戳对齐的相机驱动。我在一个项目里用普通USB摄像头,时间戳偏差稳定在120ms左右,补了一个固定偏移后同步立刻恢复正常。

5.5 标定结果和实际安装位置明显不符

如果优化完的外参和你传感器实际的位置差异很大,九成是初始值问题。非线性优化怕初始值离全局最优太远,局部最优会让结果看起来"合理"但实际完全错误。

我的做法是:

  1. 先用卷尺量平移,精度到厘米
  2. 旋转初值按安装方向估算,比如俯仰和横滚给0,偏航按雷达相对相机的朝向给
  3. 把初始外参写进launch,用rviz做投影预览,确认大致方向对了再跑优化

还有一种情况是点云平面法向量方向不统一,有的朝传感器,有的朝反面,导致优化错乱。解决办法是检查点云拟合出来的法向量是否指向传感器一侧,不是就取反。

5.6 多雷达点云没有统一坐标系就标定

某些大设备上装了多台Livox,点云会聚合成一个话题。如果聚合时的坐标系不是同一台雷达坐标系,平面拟合会混入来自不同坐标系下的点,优化出来当然不准。

用时先确认聚合话题的点是在统一坐标系下的。如果不确定,可以只看单台雷达的话题跑标定,标完再验证整体。

5.7 ROS2环境怎么办

livox_camera_calib官方是基于ROS1的,在ROS2环境里想用它,最稳妥的做法是用Docker开一个noetic容器,把bag喂进去跑离线标定。容器里只跑这个标定节点,不需要把其他算法也搬进去。

如果你不想用ROS1,可以去看看官方后续维护的livox_camera_lidar_calibration有没有ROS2版本,但那是另一套流程。建议不要在项目冲刺阶段临时换标定工具,先按最稳的方式把外参拿到手再说。

6. 标定完之后,怎么验证外参是真的准

6.1 点云投影图像的直观检查

拿到外参,第一件事就是把点云投影到图像上。最简单的方法是通过静态TF发布外参,然后rviz里同时订阅图像和点云。观察标定板边缘,点云轮廓和图像里的棋盘格边缘应该基本重合,误差不超过2到3个像素。

如果发现整体偏移但方向一致,多半是平移t还有误差;如果点云相对图像有旋转趋势,旋转R还有误差;如果某个区域贴合、另一个区域偏移,可能是标定板在采集时发生了形变,需要重新检查板子。

6.2 量化重投影误差

视觉检查之外,可以做一个小验证程序:对某一帧图像,用检测到的棋盘格角点作为真值,同时将雷达点云里标定板的四角投影到图像,计算平均像素误差。我的经验是误差小于3个像素说明外参质量很好,3到5个像素属于可用范围,超过10个像素基本要重新标定。

这个验证程序不复杂,写一次之后每次标定都能复用。也可以直接把重投影代码和读取yaml外参的逻辑放在一起,方便批量验证多组bag。

6.3 把外参接进你的建图或融合工程

标定结果最常见的落地方式是发布TF:

rosrun tf2_ros static_transform_publisher x y z yaw pitch roll map camera_link

也可以把外参写进你自己的SLAM配置,比如后续做Cartographer建图并保存地图时,点云变换到相机坐标系后的纹理渲染就依赖这套外参。

我最后再多说一句经验之谈:外参标定不是一劳永逸的。传感器受过撞击、安装板重新拆卸、极端温度变化后,外参都可能发生微小的漂移。我现在会在工程里加一个启动自检流程,每次开机用标定板测一次重投影误差,超过阈值就提醒重新标定。这样做看起来费了一点事,但能够避免很多融合算法里来源不明的"飘",比出了问题再排查要高效太多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询