做过多传感器融合的人应该都有这种感觉:激光雷达和相机各说各话,点云里清清楚楚的一堵墙,图像里也明明就在那里,可一旦要把两个结果对齐,偏差能大到让融合算法直接“摇头”。这个问题的根治办法,就是用工具把两个传感器之间的空间关系彻底标定清楚。我最近把 OpenCalib 这套开源标定工具箱完整跑了一遍,从数据准备到结果可视化,走通了一条激光雷达与相机联合标定的可行路径。这篇就按我实际操作时的顺序,把每一步的原理、配置、坑点和判断标准都写出来,给正准备做联合标定或者已经被标定折磨过的人一个参考。
联合标定这件事,很多人把它想得很复杂,一上来就翻算法论文。但实际操作下来,真正决定成败的往往不是优化求解那几十行代码,而是数据采得够不够好、初值给得准不准、结果出来之后你会不会验证。这几个环节我都会展开讲。
1. 标定前先想清楚:联合标定到底在解决什么问题
1.1 坐标系、外参和“对齐”的本质
所谓激光雷达与相机联合标定,本质上是求两个传感器坐标系之间的刚体变换关系。激光雷达看到的目标在雷达坐标系里有一个三维坐标,相机看到的同一个目标在像素坐标系里有一个二维坐标。要让算法能把这两类数据当作同一个对象处理,就必须把雷达的三维点投影到图像上,或者把图像信息反投影到三维空间。
这个变换关系的数学表示很简单:P_cam = R * P_lidar + t。其中 P_lidar 是激光雷达坐标系下的点,R 是一个 3x3 的旋转矩阵,t 是一个 3x1 的平移向量,P_cam 是同一个点在相机坐标系下的坐标。这一步做完之后,再套用相机内参 K 和畸变模型,就能把三维点映射到像素坐标上。整套流程里,R 和 t 合称外参,也就是我们要标定的东西。
你可以把两个传感器理解成两个人站在同一个房间里,一个拿着测距仪,一个拿着望远镜。测距仪告诉你目标在东北方向 5 米处,望远镜告诉你目标在画面偏右的位置。如果这两个人之间没商量好彼此的站位和朝向,他们的描述永远对不上。外参标定,就是给他们统一一套说话的方式。
这里有一个容易被忽略的点:旋转矩阵 R 和平移向量 t 的单位和坐标轴方向会直接影响后续所有融合算法的输入。比如 t 的单位必须是米,如果你的安装量测是用厘米记的,忘了换算,投影结果就会整体偏移几十个像素,排查起来非常痛苦。我自己的习惯是,写配置文件之前先在纸上画出两个传感器的坐标系朝向,再顺手把量测值换算成米,这个习惯帮我避免了好几次低级错误。
1.2 OpenCalib 的标定方案好在哪
开源界做联合标定的工具不算少,但 OpenCalib 在易用性和自动化程度上做了很好的平衡。它不只是一套激光雷达与相机的标定代码,而是一个覆盖相机内参、相机间外参、激光雷达与相机外参、多激光雷达外参的完整工具箱。每个模块相对独立,你只需要用到其中的 lidar2camera 部分,不强制安装全套。
和某些需要人工在图像和点云里反复点选对应点的工具相比,OpenCalib 对标定板特征做自动提取。它使用的是圆形标定板,图像端通过椭圆检测提取圆心,点云端通过拟合平面和分割圆形区域估计中心,两边特征一一对应之后,再用PnP或者优化方法求解外参。检测环节一旦自动化,人为操作带来的随机误差就少了一大截,标定结果也稳定得多。
另一个让我觉得舒服的特点是,它保留了完整的中间结果输出。标定过程里到底提取到了多少特征点、点云平面拟合得怎么样、投影误差是多少,都能看到。对于调试来说,这比一个黑盒工具,直接丢给你一个最终外参文件要有用得多。你能够知道问题出在数据端还是求解端,这一点在实操里价值极高。
2. 数据准备:标定成败的决定性环节
2.1 标定板:尺寸、图案与制作要点
OpenCalib 的 lidar2camera 模块走的是标定板路线,所以选一块合适的板子是第一件事。板面图案不是常见的棋盘格,而是圆形阵列,通常叫 circle grid。圆心的提取精度在图像和点云两端都能做得比较高,而且圆形的几何对称性对透视变形不太敏感,这是它比棋盘格更适合多传感器标定的原因。
标定板的尺寸影响巨大。激光雷达在远距离上的点云密度会迅速下降,板子太小的话,几米之外就只剩寥寥几个点,平面拟合和圆心估计都会失效。我建议板子边长不要小于 600 毫米,有条件的话做到 1 米左右更好。圆形阵列的间距也要提前量准,比如列间距和行间距都是 150 毫米,这个值是后续求解平移尺度的重要参考。打印误差会直接进到标定结果里,所以有条件就尽量用高精度喷绘,别用普通家用打印机出图。
板子的材质同样不能马虎。最好是 5 毫米以上的铝塑板或者亚克力板,表面要哑光处理。雷达点云对高反射率物体很敏感,如果板面反光,点云上会出现一些异常亮斑或飞点,干扰平面分割。我自己第一次用的就是普通相纸覆膜,结果在点云视图里那个板面斑斑驳驳的,圆心提取根本不稳定。后来换了哑光铝塑板,效果立刻正常了。另外板子边缘要平整,背面如果有支架,尽量不影响前表面,否则点云拟合平面时会出现倾斜。
2.2 现场采集的姿势规范和样本量要求
数据采集是整个标定流程里最耗时,也最影响最终效果的一环。核心目标是让标定板在两个传感器的共同视野里,尽可能多地覆盖不同位置、不同角度、不同距离。这样做不是因为程序有什么特殊要求,而是因为外参求解的本质是从多个视角的对应关系里反推刚体变换。位姿越丰富,约束条件越充分,最终求得的解越不容易陷入局部极小。
实际操作时,我建议按这样的姿势序列采集:正对传感器,放在视野中央;向左偏移、向右偏移;向上仰、向下俯;板面左右倾斜、前后倾斜;距离分别拉远到 3 米、5 米、8 米左右。每个姿势保持静止 2 到 3 秒,不要边动边采集。激光雷达的一帧点云是一个扫描周期内逐步累加的结果,板子如果一直在晃,点云里的板面会是扭曲的,平面拟合直接报废。
样本量至少要有 15 到 20 组有效姿态,我通常会采到 30 组以上。比较稳妥的做法是给每个姿态编个号,采集的时候记录一下大概位置,方便后面筛选。现场还得避开强光直射和玻璃反光区域。另外,人员不要出现在标定板附近,雷达会把人体也扫进去,影响平面分割。
采集完成之后,别急着跑标定。先做一次快速预览,把点云和图像按时间戳大致对齐,确认每一组数据里板面都完整出现在雷达点云和图像中。有些姿态在图像里看着挺好,但板子正好落在雷达扫描的盲区边缘,点云里只有半块板子,这类数据就要果断丢掉。
2.3 数据同步与格式检查
多数方案在数据准备阶段会忽略时间同步问题,但它在联合标定里很关键。如果相机和雷达没有硬件同步,至少要在软件层做时间戳对齐。采集时我习惯用 rosbag 记录,两个传感器的话题时间戳统一走 ROS 时钟,后续处理时按时间差最小原则把图像和点云配对。如果用的是离线数据文件,也要先检查一对图像和点云是否真的对应同一时刻的同一场景。
一个简单的验证方法是:找一帧点云投射到图像上,看板子是否在同一个位置。如果发现点云里的目标轮廓和图像里的目标轮廓有明显错位,而外参明明已经给得很好了,那大概率就是时间对齐出了问题。我在一次采集里就遇到过这种情况,当时还以为是外参算法有 bug,最后发现是相机和雷达的帧率不匹配,配对时选错了帧。所以别嫌麻烦,数据检查这一步非常值得。
3. OpenCalib 实操全流程:从编译到出外参
3.1 环境依赖与编译
先把基础环境准备好。OpenCalib 官方比较推荐在 Ubuntu 18.04 或者 20.04 上用 ROS Melodic 或 Noetic 环境,核心依赖包括 OpenCV、PCL、Eigen、yaml-cpp,以及 Ceres 或者 Sophus 这类非线性优化库。如果你已经有跑过激光雷达的 ROS 环境,大部分依赖应该已经就位了。
我的环境是 Ubuntu 20.04 + ROS Noetic,装完依赖之后,把代码放进工作空间的 src 目录,然后编译。命令很简单:
mkdir -p opencalib_ws/src cd opencalib_ws/src git clone <OpenCalib仓库地址> cd .. catkin_make编译过程中常见的坑是 OpenCV 和 PCL 的版本冲突。Noetic 自带的 OpenCV 4 和 PCL 1.10 在大部分情况下是兼容的,但如果你之前手动编译过其他版本的 PCL,很容易在链接阶段报一些莫名其妙的符号错误。我的建议是尽量用系统自带的库版本,不要贪新鲜升级。真的遇到依赖冲突,用 Docker 把环境隔离起来反而是最快的出路。我见过不少人在编译上卡了一整天,最后换成 Docker 镜像十分钟就过了。
3.2 配置文件逐个拆解
OpenCalib 里每个标定任务都对应一份 YAML 配置文件,里面有几个关键字段直接决定标定能否跑通。首先是相机内参,包括 fx、fy、cx、cy 和畸变系数。内参必须来自独立的相机标定结果,这一步不能用激光雷达去反推。官方推荐的做法是用棋盘格 + OpenCV 的标定流程,至少采集 10 到 15 张不同姿态的棋盘格图像,把重投影标定误差控制在 0.5 像素以内再继续。
然后是两个传感器的话题名或者数据路径。用 rosbag 跑的话要填 topic 名称,并且确保图像话题是原始图像,不要选已经被压缩编码过的话题,否则 OpenCV 读入会出问题。接着是标定板的几何参数,包括圆形阵列的行数、列数、圆心间距,以及板子实际物理尺寸。这个区域容易出错的地方是行数列数方向定义,不同工具里往往不一致。你必须打开点云和图像确认,配置里的行方向对应的是图像里的哪个方向,不然检测到的对应点关系会整体错位。
外参初值也要填进去。旋转部分可以用欧拉角表示,再转换成矩阵;平移部分就是激光雷达相对于相机的三维位移,单位是米。初值允许有误差,OpenCalib 的优化过程会修正它,但初值和真实值如果差太多,很容易陷入局部最优。实际操作里初值其实不用精确测量,拿尺子量个大概,旋转角度估个方向,比如雷达在相机右侧零点几米、稍向下倾斜一点,这类量级的信息就够了。
3.3 跑标定时的中间结果怎么读
一切就绪后启动标定程序,屏幕或者日志里会持续输出当前检测到的特征数量、点云平面拟合的残差、当前迭代的误差指标。很多人看到这些输出就忽略了,但它们是判断数据质量的第一手指标。如果某一帧特征点数量过少,或者平面拟合残差异常大,说明这一帧数据可能存在问题,不用等最终结果出来,直接标记剔除。
优化求解过程一般会显示迭代次数和误差下降曲线。正常情况下误差会逐步下降并趋于平稳。如果误差忽大忽小,甚至到了最后几轮还在明显跳动,多半是数据里混入了错误的对应点,或者初值方向给反了。我遇到过一次旋转初值方向填反的情况,优化过程错误提示不明显,但最终投影结果图像和点云的边缘整体错开了很大一段距离,最后回查配置文件才发现问题。
标定完成后,结果会输出到外参文件,一般包含一个 4x4 的齐次变换矩阵或者分拆的 R 和 t。保存好这个结果,同时也建议把对应的配置文件备份,因为外参是和坐标定义强相关的,一旦换了坐标系约定,这份外参就不再有效了。
4. 结果可视化与精度验证:外参对不对,一眼就知道
4.1 点云投影叠加图像的三种可视化做法
拿到外参之后,第一件事就是可视化验证。最简单粗暴的方法,是把雷达点云按外参投影到图像上,按深度着色之后叠在一起看。边缘轮廓贴合得好不好,一眼就能看出来。这个操作不需要复杂的软件,OpenCV 加几行代码就能完成。
我自己常用的流程是:读一帧图像,读一帧点云,对每个有效点做坐标系变换,跳过投影后落在图像范围之外的点,然后把剩下的点按距离着色画到图像上。伪代码大致是这个意思:
import cv2 import numpy as np # 假设 points 是 N x 3 的雷达点云,K 是相机内参,R, t 是外参 def project_lidar_to_image(points, K, R, t): cam_pts = (R @ points.T + t.reshape(3, 1)).T # 过滤掉相机后方的点 mask = cam_pts[:, 2] > 0 cam_pts = cam_pts[mask] proj = (K @ cam_pts.T).T u = proj[:, 0] / proj[:, 2] v = proj[:, 1] / proj[:, 2] return u, v, cam_pts[:, 2], mask如果用的是 ROS 环境,更省事的做法是在 RViz 里同时显示图像和 PointCloud2,然后通过发布外参对应的 TF 变换,把点云显示到相机坐标系下。设置一个半透明的图像面板,叠加效果非常直观。还有一种做法是用 PCL 的可视化窗口加载点云,再在同一个窗口里显示图像纹理,但对配置要求高一些,日常调试没必要上这么重的方案。
判断可视化结果的好坏,不能只盯着标定板看。我习惯找场景里的明显结构物来做交叉验证,比如墙角、柱子、路缘石、灯杆这类线条清晰的目标。如果这些结构在图像和点云里都精确贴合,说明外参在视野的多个区域都可信,而不只是在标定板附近自圆其说。
4.2 重投影误差怎么看
可视化是定性判断,重投影误差是定量判断。所谓重投影误差,就是把标定板上的特征点先从点云侧提取出来,再投影到图像上,和图像侧检测到的对应像素位置做差。这个差值越小,说明外参越准。
评判标准上,我个人的经验是:均值误差在 1 到 2 像素之间属于非常好;2 到 5 像素属于可接受,多数融合算法都能正常运作;超过 10 像素基本不可用。但要注意,重投影误差取决于特征点的空间分布,如果所有点都集中在画面中央,误差小也不能说明边缘区域标定得好。所以量化验证时最好把标定板放在画面的不同位置,分别统计误差。
还有一个不起眼但很实用的检验方法,就是用一组没有参与标定的数据来做交叉验证。如果参与优化拟合的帧误差都很小,但新数据一测试就明显错位,说明标定结果过拟合了,模型被少数特定姿态“带偏”。遇到这种情况,回炉补采数据比调算法参数有用得多。
4.3 基于可视化结果微调外参的实操技巧
可视化还有一个应用容易被忽略,就是辅助判断外参偏差的方向。如果投影出来的点云整体往右偏,通常说明平移向量或旋转角度在某些轴上有系统偏差。你可以微调外参后在可视化里不断验证,虽然这种方法不够严谨,但能帮你快速定位问题来源。
比如,点云整体偏右,试着把 t 向对应方向调整几个厘米,看看是否改善;如果整体绕某个轴旋转,试着微调欧拉角。很多时候最终优化结果不好,未必是算法的问题,而是初值或者坐标系定义本身就歪了,可视化验证能帮你快速建立直觉。这也是为什么我会建议无论如何都要先把外参文件加载到可视化环境里看一眼,再决定下一步做什么。
5. 高频问题与避坑记录
5.1 常见问题速查表
我把实际操作中遇到过的典型问题整理成了表格,方便对照排查。
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 图像里检测不到圆圈格 | 标定板尺寸太小、距离太远、图像模糊 | 增大标定板、靠近拍摄、检查相机对焦 |
| 点云里找不到板平面 | 板面反光、雷达视角被遮挡、板子倾斜过大 | 换用哑光板、调整姿态、让板面尽量正对雷达 |
| 特征对应关系混乱 | 圆形阵列的行列方向定义不一致 | 打开可视化确认行列方向,修改配置里的行列数定义 |
| 重投影误差整体偏大 | 相机内参不准或外参初值偏差大 | 重新做相机内参标定、回采数据提高初值精度 |
| 误差在优化过程中不收敛 | 数据里有错误对应点、时间未对齐、板子在动 | 筛选数据帧、检查时间戳、保证每个姿态静止 |
| 投影结果边缘区域偏差明显 | 样本姿态覆盖范围不够 | 增加画面边缘、远距离、大角度姿态 |
这张表里的每一条我都实际踩过。尤其是行列方向定义那个问题,看起来只是配置里的一行数字,一旦错了,整个点云会在点对应关系上发生错位,导致优化结果完全失真,而且错误提示并不明显,只能靠可视化排查。建议在开始跑优化之前,先输出一次特征提取和匹配的可视化结果确认。
5.2 几个隐藏很深的坑
第一个容易忽略的坑是打印比例。标定板的圆形阵列间距,必须以实际打印出来的尺寸为准,而不是设计文档里的尺寸。打印过程可能会按默认设置缩放,导致实际间距比设计值小几个百分点,这个误差最终会直接进入平移向量的尺度,造成数厘米甚至更大的偏差。拿到板子后,第一件事用尺子量一下实际间距,再填进配置文件。
第二个坑是室外采集时光照变化对图像检测的影响。太阳角度一变,标定板上的圆圈可能在图像里出现过曝或者阴影遮挡,检测到的圆心位置会偏移。如果必须在室外采集,尽量选阴天或者背光环境,保证板面光照均匀。我在一次下午采集中连续翻车,就是因为板面一半被阳光直射,一半在阴影里,圆心的提取精度明显下降。
第三个坑藏在点云预处理环节。有些激光雷达驱动会输出带有强度值的同时,也输出一些离群噪声点,这些点如果不做滤波处理,会让平面拟合的残差变大。但要注意,滤波半径不能设得太大,否则板面上的有效点也会被一起滤掉。我的经验是先做一次简单的直通滤波,把范围限制在标定板可能出现的大致区域,然后再做平面拟合,稳定性和精度都能兼顾。
最后一个坑是“标定完成”不等于“标定永久有效”。传感器支架可能在车辆运行中被磕碰,相机镜头可能因为震动产生微小位移。外参标定是一次性的,但传感器状态不是永久的。如果融合系统里出现追踪不稳定、投影偏移渐进变大的现象,多半不是因为算法退化,而是外参已经失效。定期用同样的流程在固定场景里做一次复标定,能把这个问题控制在早期。
还有一点要特别提醒,保存外参文件时,最好把相机内参、外参、标定板参数、采集时间记录下来,放到同一个目录里。这样后续做数据分析或者问题回溯时,能完整还原当时的标定条件。别问我为什么知道——当你手里有五个不同时期的标定结果,却分不清哪个对应哪次采集时,就明白了。
写到这里,关于 OpenCalib 做激光雷达与相机联合标定这件事,我能分享的实操经验基本都覆盖了。如果只留一句话给后来者,我的体会是:标定算法已经足够成熟,输出结果好不好,绝大多数时候取决于你是否愿意在数据采集和结果验证上多花一点耐心。把采集流程标准化,把每一次标定的配置和结果记录下来,你会慢慢发现,标定从一件玄学变成一件很踏实的事。