news 2026/11/5 20:16:45

松灵小车深度相机与激光雷达联合标定实战:基于Autoware的外参标定全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
松灵小车深度相机与激光雷达联合标定实战:基于Autoware的外参标定全流程

前阵子给一台松灵SCOUT小车做深度相机与激光雷达的联合标定,顺手把整套流程从硬件准备到外参落地理了一遍。说实话,联合标定这个事在Autoware的感知链路里太容易被低估了,很多人觉得装上驱动、跑通Autoware就算完事,可等到做融合避障或者建图的时候,相机图像里的障碍物和雷达点云里的障碍物对不上,才知道外参这东西省不掉。这篇文章就把我在松灵小车上实际用Autoware生态完成联合标定的全过程写出来,包括原理、实操步骤、核心代码思路,以及我在现场踩过的坑,给正在捣鼓传感器融合或者被标定折磨的朋友做个参考。

1. 为什么深度相机和激光雷达需要联合标定

1.1 两个传感器在“各说各话”

激光雷达输出的是一堆以雷达自身为原点的三维点,单位是米;深度相机输出的则是一张张二维图像,外加每个像素可选的深度值。雷达坐标系里一个点坐标是( x, y, z ),到了相机图像里变成像素坐标( u, v ),中间要经过投影变换,而投影变换成立的前提是知道两个坐标系之间的旋转和平移关系。

在实际整车上,absolutely不止有这两个传感器,还有车体底盘、IMU、GPS等。Autoware在做规划、控制、避障的时候,默认所有数据都已经换算到车体坐标系base_link下了。如果相机和雷达没有标定,图像里的障碍物框和雷达点云里的障碍物是两套坐标,融合模块根本没法用。举个直观的例子:小车前方1.5米处有个纸箱,相机看到它在图像左上角某个位置,雷达看到它在车体正前方略偏右1.5米,如果不标定,谁也不知道这两份信息描述的是同一个纸箱。

1.2 Autoware到底管不管标定

Autoware是开源的自动驾驶系统,感知、定位、规划、控制模块都默认传感器已经完成内外参标定。它本身不会帮你去猜传感器装歪了多少度,而是提供工具链去引导整个标定过程。

在Autoware的生态里,联合标定相关的工具常见的有两类:一类是Autoware.AI时代就有的calibration_camera_lidar,基于棋盘格检测做联合标定;另一类是Autoware Universe里的autoware_camera_lidar_calibrator,继续沿用“相机-雷达联合观察标定板”的思路,只是把接口换到了ROS2。这篇主要讲ROS2/Autoware Universe的路线,因为现在新项目用ROS2的比例明显更高,而且松灵原厂也在往ROS2上靠。如果你的项目还在ROS1,思路基本一致,只是启动方式从roslaunch换成rosrun。

1.3 标定到底在求什么

一句话:求外参。外参就是相机坐标系和雷达坐标系之间的变换关系,包含一个3x3的旋转矩阵R和一个3x1的平移向量t。有了R和t,雷达点云里的任意一点都可以转换到相机坐标系下,再结合相机内参投影到图像上。

可以把外参理解为两个人站在不同位置看同一栋楼,如果想确认对方看到的窗户是不是同一扇,必须先知道彼此的位置和脸的朝向。R就是朝向差,t就是位置差。标定的过程就是通过两个传感器共同观察同一个参照物,反推出这个位置差和朝向差。最终结果一般写成4x4齐次变换矩阵,也可以拆成R和t输出到Autoware的tf树中。

2. 标定前的准备:从松灵小车到标定工具

2.1 硬件安装与位置规划

松灵SCOUT系列,比如SCOUT MINI、SCOUT 2.0,车顶是一块完整的安装板,留了不少螺纹孔位,装传感器比较方便。安装时我会先把相机和雷达的固定支架做好,确保装上之后不会因为颠簸移位。标定完成后,传感器哪怕偏了1毫米,在10米处的误差也会被放大很多,所以支架必须锁死。

安装位置的数据最好记录一下:相机装在车头哪个位置,雷达装在哪个位置,大致的高度、前后距离、朝向。这些粗值很有用,后续标定工具做优化时如果初值离真实值太远,容易陷入局部最优,给一个靠谱的初值能省很多事。

2.2 雷达IP修改与网络调试

如果是速腾、Livox这类网络型雷达,上电后第一件事是检查电脑能不能收到它的点云。Livox MID-360默认IP一般是192.168.1.1xx之类,电脑网卡要配到同一网段,比如192.168.1.50。这里有个松灵小车特别容易踩的坑:底盘和感知系统会用不同网口,一个口连底盘控制器,一个口连雷达和相机。连雷达的那个网卡如果没配静态IP,系统重启后IP变化,雷达点云就会消失。

用Livox Viewer2修改雷达IP的步骤大致是这样:先把电脑网卡IP改成与雷达同一网段的静态地址,打开Livox Viewer2扫描设备,在设备列表里点设置,把IP改成想要的地址,保存后重启雷达。改完IP后,再把主机网卡和雷达的IP关系在路由表里固定好,避免拔插网线后失联。

2.3 ROS2与Autoware标定工具的安装

我这里用的环境是Ubuntu 22.04 + ROS2 Humble + Autoware Universe。如果还没装Autoware,建议直接用官方镜像,省去源码编译的很多麻烦。先确保ROS2基础环境能跑起来,然后安装相机与雷达驱动。

RealSense相机驱动可以直接用系统源里的包:

sudo apt install ros-humble-realsense2-camera

Livox驱动从官方仓库拉下来编译:

git clone https://github.com/Livox-SDK/livox_ros_driver2.git cd livox_ros_driver2 colcon build source install/setup.bash

标定工具autoware_camera_lidar_calibrator也建议单独拉一个工作空间编译:

git clone https://github.com/autowarefoundation/autoware_camera_lidar_calibrator.git cd autoware_camera_lidar_calibrator rosdep install -y --from-paths . --ignore-src colcon build

编译过程中常见的问题是缺依赖,用rosdep装一遍基本能解决。如果有包没被rosdep覆盖,手工apt装也行。

2.4 驱动启动与topic梳理

启动相机:

ros2 launch realsense2_camera rs_launch.py depth_module.profile:=1280x720x30

启动雷达:

ros2 launch livox_ros_driver2 rviz_MID360.launch.py

启动后用ros2 topic list确认关键话题都存在:

  • /camera/color/image_raw:RGB图像
  • /camera/color/camera_info:相机内参
  • /livox/lidar:雷达点云

还要用ros2 run tf2_tools view_frames看tf树,确认有base_link、camera_link、livox_frame这几个关键坐标系。如果没有,说明驱动里的静态tf没有发出来,需要自己补一个粗略的外参作为初始值。

3. 数据采集:让两个传感器“看”到同一块板子

3.1 标定板选择与摆放技巧

棋盘格标定板是联合标定里最常用的参照物。尺寸建议尽量大,A0或1.2米乘0.9米,格子数量8x6或9x6都可以。关键是要用哑光磨砂材质,不要用亮面覆膜,否则激光一打上去会产生大量杂点。

摆放板子的时候,要让相机和雷达都能同时看到。雷达点云是稀疏的,板子太远、角度太斜,扫到的点数不够,平面拟合质量就差;相机视野一般比雷达窄,板子太偏会跑到图像外面。我一般把板子放在0.8米到3米范围内,左右各偏大概20到30度,俯仰角度也稍微变一变。每个位置停几秒,让两个传感器都吃到足够的帧。

录制bag的命令:

ros2 bag record /camera/color/image_raw /camera/color/camera_info /livox/lidar /tf /tf_static

这里不需要专门录深度相机的depth topic,联合标定主要用RGB图像和雷达点云。深度相机的深度值在后续融合中会用到,但标定过程中更依赖RGB图像里的棋盘格角点。

3.2 时间同步检查

录制之前最好先检查一下两个传感器的时间戳有没有对齐。简单方法是用rqt或者写个小脚本订阅两个话题,打印最近一条消息的header.stamp,看看时间差。如果时间差超过50毫秒,标定板在两帧之间只要有轻微移动,点云和图像里的板子位置就对不上,标定结果一定偏。

解决时间同步的办法有几个:一是让两个驱动都用硬件时间戳,RealSense和Livox都支持PTP或时钟同步;二是录制时用--clock选项让bag统一时钟源;三是采集时尽量保持机器人静止,这样即使消息有几毫秒到几十毫秒的时间差,板子位置变化也不大。实际最稳妥的做法是静态采集,机器人完全不动,用手移动板子到不同位置。

4. 核心实战:从点云与图像中求解外参

4.1 原理:棋盘格法向量如何约束外参

联合标定最核心的思想是:在某一帧中,相机看到了棋盘格,雷达也看到了棋盘格。相机图像通过角点检测和PnP求解,可以算出棋盘格平面在相机坐标系下的位姿,进而得到棋盘格平面的法向量n_cam。雷达点云通过裁剪和平面拟合,可以算出同一平面在雷达坐标系下的法向量n_lidar。

因为是同一块板,旋转矩阵R要把n_lidar旋到n_cam:

R * n_lidar = n_cam

一个平面法向量只能提供两个角度约束,所以要多放几个位置,采集多帧数据,把所有帧的方程联立起来,用最小二乘或者奇异值分解求解最优R。求得R之后,再根据棋盘格中心点在不同传感器下的坐标差来求t。

用生活类比来解释:你站在A点伸出手指指向远处一个旗杆,另一个人站在B点也指向同一个旗杆,两个人的指向方向不同,但都对着同一个目标。多换几个旗杆位置,拿到足够多的指向数据,就能反推出A和B之间的相对位置和朝向。

4.2 用工具一把梭:autoware_camera_lidar_calibrator实操

工具用起来比手写算法省心很多。先把相机、雷达驱动跑起来,再启动标定工具:

ros2 launch autoware_camera_lidar_calibrator calibrator.launch.xml \ camera:=/camera/color/image_raw \ lidar:=/livox/lidar

打开的界面里左边是图像,右边是点云。操作逻辑是逐帧处理:在图像窗口里确认棋盘格角点检测是否成功,然后调整点云裁剪框,把棋盘格区域的点选中,点击添加这一帧样本。重复这个动作,采集10到20帧不同位置的数据,然后点击标定按钮,工具会跑优化并输出外参结果。

官方工具的优势在于它把角点检测、平面拟合、非线性优化都封装好了,并且会在界面上显示重投影误差。这个误差值是判断标定质量的关键指标,越低越好。如果误差一直降不下来,先别急着继续采帧,回头检查是不是时间戳不对或者标定板太小。

4.3 自己写脚本提取标定板平面并拟合法向量

如果不想依赖现成工具,或者想更深入理解原理,自己写脚本也是可行的。两个核心步骤:一是从雷达点云中提取标定板平面并求法向量,二是从图像中求解棋盘格位姿得到法向量。

点云部分我用Open3D来做平面拟合。首先从bag里读出一帧点云,然后用ROI裁剪出标定板可能出现的位置,再用RANSAC拟合平面。核心代码如下:

import numpy as np import open3d as o3d # 假设pcd是已转换到雷达坐标系的点云 pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(points) # 裁剪ROI,只保留可能的标定板区域 bbox = o3d.geometry.AxisAlignedBoundingBox( min_bound=np.array([-1.0, -1.0, -1.0]), max_bound=np.array([3.0, 3.0, 3.0]) ) pcd_cropped = pcd.crop(bbox) # RANSAC平面拟合 plane_model, inliers = pcd_cropped.segment_plane( distance_threshold=0.02, ransac_n=3, num_iterations=1000 ) a, b, c, d = plane_model normal = np.array([a, b, c]) normal = normal / np.linalg.norm(normal) centroid = np.asarray(pcd_cropped.select_by_index(inliers).get_center())

图像部分用OpenCV的棋盘格角点检测加solvePnP:

import cv2 import numpy as np # 棋盘格参数 pattern_size = (8, 6) square_size = 0.108 # 格子边长,单位米 # 角点检测 ret, corners = cv2.findChessboardCorners(gray, pattern_size) if ret: # 生成棋盘格三维点坐标 objp = np.zeros((np.prod(pattern_size), 3), np.float32) objp[:, :2] = np.indices(pattern_size).T.reshape(-1, 2) * square_size # 相机内参 mtx = np.load("camera_matrix.npy") dist = np.load("dist_coeffs.npy") # 求解棋盘格位姿 ret, rvec, tvec = cv2.solvePnP(objp, corners, mtx, dist) # 棋盘格平面法向量:第三列旋转列向量 R, _ = cv2.Rodrigues(rvec) normal = R[:, 2]

拿到每一帧的n_lidar和n_cam之后,把所有帧的法向量拼起来,构建方程组,用最小二乘求R:

# 堆叠所有点的法向量 N_l = np.hstack(n_lidar_list) # 3xN N_c = np.hstack(n_cam_list) # 3xN # 用SVD求解 R * N_l = N_c A = N_l @ N_l.T B = N_c @ N_l.T U, _, Vt = np.linalg.svd(B.T @ A) R = U @ Vt # 确保行列式为1,避免反射变换 if np.linalg.det(R) < 0: Vt[-1, :] *= -1 R = U @ Vt

平移t的求解要复杂一些,一般利用多个位置的棋盘格中心点,建立线性方程求解。实际生产环境不建议完全自己写,因为坑很多,比如点云异常点、角点误检、法向量方向不一致等。我建议先用工具跑通,再自己写代码去复现结果,这样即使写错了也能对照。

4.4 外参验证:从图像重投影误差看标定质量

标定完成不等于万事大吉,必须验证。最直观的方法是重投影验证:把雷达点云按照标定结果投影到图像上,看看激光点是否落在图像中的标定板区域。

# R和t是标定结果 points_cam = (R @ points_lidar.T).T + t.reshape(1, 3) uv = (mtx @ points_cam.T).T uv = uv[:, :2] / uv[:, 2:] # 画在图像上 for u, v in uv: if 0 <= u < img.shape[1] and 0 <= v < img.shape[0]: img = cv2.circle(img, (int(u), int(v)), 2, (0, 0, 255), -1)

在Rviz里也可以直接启动相机图像和点云叠加显示,把点云topic的Fixed Frame设为camera_link,然后调整点云的颜色和大小,肉眼观察边缘是否对齐。这个检查一定要做,建议近处、远处各放一次标定板,如果近处对准远处偏,说明旋转矩阵可能不够准;如果远处对准近处偏,可能是平移向量有问题。

5. 松灵小车落地时的常见坑

5.1 时间戳不同步导致标定板位置漂移

这个坑我印象最深。第一次标定的时候,相机和雷达消息时间差大约200毫秒,采集过程中板子稍微动了一下,结果标出来的外参平移量偏差了好几厘米。后来我重新采集,全程把小车停在原地,只动板子,同时用工具打印两路话题的时间差,确认都在20毫秒内,结果一次通过。

操作建议:采集前一定先看时间戳差值。如果差值较大,优先从驱动配置上解决;如果解决不了,就采用完全静态的采集方式,把外部抖动降到最低。

5.2 点云中找不到标定板

雷达点云稀疏,尤其在小角度、远距离情况下,标定板上的点寥寥无几。常见原因包括标定板太小、反光太强、距离太远。黑色棋盘格会吸收大量激光能量,点云里的板子看起来缺一块是正常的,这时候可以借助反射率信息,把强度过低或者过高的点过滤掉,再用RANSAC拟合平面。

我实际用下来的经验是:标定板尺寸至少在1米乘0.8米以上,位置放在雷达正前方1米到2.5米之间,角度不要太极端。如果用的是Livox MID-360这类非重复扫描雷达,点云相对密集,效果会好一些;如果是16线雷达,就要更注意板子的放置角度。

5.3 外参反复标不准,甚至偏了几十厘米

如果每次标定结果差异很大,不要急着怀疑算法,先查几个基础环节:

先确认相机内参是否准确。相机内参不准,外参标定一定不准。RealSense出厂自带的内参一般够用,但如果你换了镜头或者镜头被动过,建议先用棋盘格重新标定相机内参。

再确认传感器安装是否松动。松灵小车在越野场景下震动很厉害,安装板上稍微有一点松动,标定结果就跟着变。紧固完螺丝,重启系统,重新采集一遍数据再看结果。

还要确认初值是否给得靠谱。Autoware工具和自写算法都需要一个初始外参。我的做法是先量出传感器在车上的大致安装位置,输入作为初值,再让工具去优化。初值离谱的话,优化很容易掉进局部最优。

5.4 常见问题速查表

问题现象可能原因排查与解决
点云和图像里板子位置对不上时间戳不同步检查消息时间戳,开启硬件同步,或静态采集
点云中板子点数太少标定板太小/太远/反光换大板子,放近一点,用反射率过滤
标定误差一直很大相机内参不准单独标定相机内参,确认camera_info无误
每次标定结果都不一样安装松动或初值不稳紧固支架,固定初值,重新采集
雷达点云在Rviz里时有时无本机IP与雷达IP不在同一网段检查网卡静态IP,用Livox Viewer2重新配对
外参写入tf后Rviz显示异常tf树缺少静态变换检查tf树,补充base_link到各传感器坐标系的变换
室内标定效果好,到室外就偏地面反光、多路径干扰换到空旷无强反光区域重新验证

6. 标定质量对避障与建图的实际影响

6.1 外参不准,融合感知就是空中楼阁

Autoware里做目标检测,通常是相机出2D框,雷达出3D点云,然后在感知融合模块里把两类结果关联起来。外参标定不准,相机输出“前方2米有障碍物”和雷达输出“前方2.2米有障碍物”就没法统一,避障模块轻则忽远忽近,重则直接把一个障碍物看成两个。建图也一样,雷达建图本身依赖激光里程计,和相机没有直接关系,但如果后续要做激光相机融合建图,或者要给点云上色,外参不对就会看到彩色点云有重影,有人管这个叫“建图飘”,其实很大程度是标定没做好。

松灵小车做园区巡检、物流配送这类任务时,传感器安装高度低,周围环境复杂,外参不准带来的误差会被进一步放大。近处障碍物可能只差几厘米,到了5米外的障碍物可能差出二三十厘米,这在Autoware的规划模块里是不可接受的。

6.2 上电后快速复查外参的习惯

标定完成之后,建议把复查流程固化下来。我现在的习惯是每次上电后先启动相机和雷达,然后在车正前方1.5米处放一块标定板,打开Rviz看看点云叠加图像是否对齐。如果明显偏移,先用tf2_echo查一下当前外参有没有被覆盖,再决定要不要重新标定。

上电顺序也有讲究。相机驱动先起,雷达驱动再起,最后再启动Autoware相关节点。如果Autoware先启动,某些静态tf可能会被驱动或者底盘节点重复发布,导致标定结果被覆盖,出现“明明标好了,跑起来又歪了”的情况。

6.3 后续扩展建议

标定好的外参可以直接用在Autoware的感知管线里,也可以在ROS2里结合Cartographer做激光雷达建图。建图的精度提升需要好的外参,也需要好的里程计,松灵小车的轮式里程计在平坦路面上够用,遇到打滑就要靠IMU或者雷达里程计来纠正。

另外,如果你用的是RealSense这类深度相机,联合标定完成后,深度相机自带的点云和雷达点云也可以做进一步融合。这时候外参质量直接影响两个点云融合后的密度和精度,建议在融合前用上文的重投影方式再验证一遍,确认误差在可接受范围内。

我个人在实际操作中的体会是:联合标定本身的数学原理并不复杂,真正耗时间的全在细节上,尤其是时间同步、传感器安装牢固度、初始值合理性这三件事。把它们搞定,标定基本一次过。最后再分享一个小技巧:标定完成后,把标定板放在车正前方1.5米处,在Rviz里把点云叠加到图像上,透明度调到50%看边缘对齐,这个人工目检虽然是“土办法”,但往往比看RMS误差数字更可靠。我现在已经把这一步加进松灵小车的日常上电检查流程里了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/5 19:16:51

从辅助补全到持续执行:Codex Cloud 智能体实践与避坑指南

最近这一周我一直在折腾 Codex Cloud&#xff0c;说实话&#xff0c;第一次看它在云端沙箱里自己把仓库 clone 下来、装依赖、改代码、跑测试、再把结果推回分支的时候&#xff0c;我是有点恍惚的。过去两年我们习惯的 AI 编程&#xff0c;是“辅助补全”——你在 IDE 里敲代码…

作者头像 李华
网站建设 2026/10/23 18:56:52

固态硬盘主控识别与开卡实战指南

1. 为什么你手里的固态硬盘“认不出自己”&#xff1f;主控型号不是标签&#xff0c;而是钥匙 你拆开一块二手SSD&#xff0c;外壳写着“480GB”&#xff0c;PCB上丝印却标着“520GB”&#xff0c;BIOS里能看见盘符&#xff0c;进PE却显示“未初始化”或干脆不识别&#xff1b;…

作者头像 李华
网站建设 2026/10/23 2:18:20

CycleGAN+ReID跨视角特征对齐实战指南

简介&#xff1a;本资源是一套完整的基于生成对抗网络&#xff08;GAN&#xff09;的行人重识别毕业设计实现方案&#xff0c;面向人工智能方向本科生及深度学习初学者&#xff0c;聚焦跨摄像头场景下的行人身份匹配问题&#xff0c;适用于课程设计、毕设参考与算法复现实践。压…

作者头像 李华
网站建设 2026/11/4 23:10:04

游戏引擎基础架构:数学库、内存池与渲染命令流设计

1. 项目概述&#xff1a;这不是教科书&#xff0c;是引擎工程师的日常切片“游戏引擎架构深度解析&#xff08;一&#xff09;&#xff1a;引擎基础架构”——这标题里藏着的不是PPT里的分层图&#xff0c;而是每天凌晨三点还在调试内存泄漏时咬牙记下的日志&#xff0c;是第一…

作者头像 李华
网站建设 2026/11/5 19:24:33

航空级提示词,让AI不再废话

最近被Karpathy的一句话戳到了&#xff1a;他说现在的LLM太"啰嗦"&#xff0c;一个能一句话回答的问题&#xff0c;模型非要写满三段。我当时也在给业务团队调AI问答质量&#xff0c;天天被业务方吐槽"这AI说话怎么一股职场PPT味"&#xff0c;听到这个观点…

作者头像 李华
网站建设 2026/10/26 22:48:18

Unity3D旋转地球从零搭建:贴图、材质、灯光与旋转脚本避坑详解

Unity3D里搓一个旋转的地球&#xff0c;听起来确实是个入门级练手项目&#xff1a;新建场景、拖个球体、贴一张世界地图、写几行旋转脚本&#xff0c;好像十分钟就能交差。但如果你真的照着这个流程跑一遍&#xff0c;十有八九会卡在半路——球是黑的、贴图是紫的、或者按了Pla…

作者头像 李华