先看一个具体画面。
点云里每个点自带 (x, y, z):
点云说「前方 12 m 处有个箱子」
同一个箱子在照片上是某个像素 (u, v):
图像说「画面右侧那一小片像素是箱子」
要让这两句话对上,需要两组数字:
相机自己怎么把光线变成像素 → 内参
相机相对 LiDAR 在哪、朝哪 → 外参
把这两组数字写成定义:
内参 (intrinsics) = 传感器【自身】的映射关系
自己的坐标系 → 自己的输出
外参 (extrinsics) = 传感器【之间 / 与世界】的空间关系
自己的坐标系 → 别人的坐标系
判断方法:
后面的诊断、标定顺序、验收标准全都建立在这个分类上:内参错了表现为 "中心对得上、边缘对不上",旋转外参错了表现为"整幅图均匀错位"(见第七、八节)。
相机的内参描述的是一件事:一条从相机原点出发的光线,落在像平面上的哪个像素? 这条映射由 4 个参数加一组畸变系数确定:
⌊ fx 0 cx ⌋
K = | 0 fy cy | + 畸变系数 (k1 k2 p1 p2 k3 …)
⌊ 0 0 1 ⌋
| 参数 | 物理含义 |
|---|---|
fx = 焦距(mm) / 像元尺寸(μm) × 1000 | 水平方向的"像素焦距" |
fy | 垂直方向(通常 ≈ fx) |
cx, cy | 主点,光轴与像平面的交点。理想在图像中心,实际偏几十像素 |
| 畸变系数 | 镜头的非线性误差(径向 + 切向) |
视场角:FOV_h = 2·atan(W / 2fx)。fx=933、W=1920 → 91.6°。
这条式子的直觉是:fx 越大,同样画幅对应的视场角越小,这正是"长焦看窄、广角看宽"的数学形式。
相机和 LiDAR 装在车上的不同位置、朝向也不同。要把 LiDAR 系的点云画到相机图像上, 必须先做一次坐标变换,一个 3×3 旋转加一个 3×1 平移,合起来写成 4×4 矩阵:
⌊ R t ⌋ R: 3×3 旋转
T = | | t: 3×1 平移
⌊ 0 0 0 1 ⌋
用 T_A_B 记号(把 B 系的点表示成 A 系,即 p_A = T_A_B · p_B):
T_cam_lidar : 点云 → 相机 // 投影用这个
T_lidar_cam : 相机 → 点云 // 反投影、给点云上色用这个
只写"外参"而不写方向,这个说法就是不完整的,必须补上从哪到哪。
相机必须由用户标定内参,但这不是所有传感器的通例。逐类看,有的传感器把内参 藏在固件里、用户永远碰不到,有的则根本没有这一层:
| 传感器 | 有内参吗 | 为什么 |
|---|---|---|
| 相机 | ✅ 必须有,且必须自己标 | 输出是图像(2D 像素),要从像素反推 3D,必须知道针孔+畸变模型 |
| LiDAR | ⚠️ 有,但厂商固件里用掉了 | 输出直接就是 XYZ,用户层面只需要外参 |
| IMU | ✅ 有(bias、scale、轴间非正交) | 输出是原始加速度/角速度,要校正 |
| 深度相机 | ✅ 有两套(见第五节) | 内部是深度相机 + 彩色相机,各有一套内参 |
| 编码器 | ✅ 有(零位、齿轮比) | 输出是脉冲计数 |
LiDAR 的内参确实存在,就藏在 ring 字段背后:
每条激光线的:
• 垂直角偏差 // 装配误差,理想是 2° 间隔,实际每条都偏一点
• 水平角偏差
• 测距偏差/尺度因子
• 各激光器的强度差异
这些就是 LiDAR 的内参。厂商在出厂时标定好,烧在固件里, 所以标定文件里拿到的 XYZ 已经是补偿过的结果。
用户层面通常不需要管,除非:要做逐线标定(用 ring 字段!)、
传感器摔过 / 温度漂移严重、或者要做多 LiDAR 融合需要每台的逐线参数。
对比:相机的内参暴露给用户,因为厂商给的标称值精度不够(尤其畸变), 必须自己标或至少验证。
看一个深度相机(RealSense)的参数构成:
RealSense D435:
├── 深度相机内参 K_depth ← 内参
├── 彩色相机内参 K_color ← 内参
├── 深度→彩色 的变换 T_color_depth ← ???
└── 设备→世界 的变换 T_world_dev ← 外参
同一个 T_color_depth,换个角度看会得到相反的答案:
从【单个传感器】的角度看 → 它是【外参】(深度相机相对于彩色相机的位置)
从【整个设备】的角度看 → 它是【内参】(设备【内部】两个部件的相对关系,出厂固定)
"内参/外参"不是一个绝对属性,而是「相对于所选参考系」而言的。
同一个 4×4 矩阵,取决于把"设备"的边界画在哪里,它可能是内参也可能是外参。
这解释了为什么工程上经常看到"相机内参"里混着相机之间的变换,因为对于多传感器模组,"内"的边界是整个模组。
先看一个场景:车以 10 m/s 行驶,相机和 LiDAR 的时间戳相差 33 ms。 这 33 ms 里车已经走了 33 cm,投影到图像上就是上百像素的整体错位 (第七节实算)。这类误差既不属于内参,也不属于外参。
"内参 + 外参"这个分类漏了一类,而且漏掉的这一类危害最大:
① 内参 自身映射 (K、畸变、bias…)
② 外参 空间关系 (R、t)
③ 时间参数 时间关系 (传感器之间的时间偏移、曝光时刻)
时间偏移严格说既不是内参也不是外参,但它必须在标定时一起估计 (Kalibr 就是这么做的:camera-IMU 的时间偏移和内外参一起优化)。
把四类误差各注入一点,看它们分别把投影点推偏多少像素。这四类是:
外参的旋转部分、外参的平移部分、内参 fx、以及时间偏移。
实算用一组真实的标定值:fx=933.4667、fy=934.6754、
cx=896.4692、cy=507.3557,图像 1920×1080。
下表每一格表示"该深度、该画面位置处的像素偏移量":
深度Z | X/Z=-0.6 | X/Z=-0.3 | X/Z=+0.0 | X/Z=+0.3 | X/Z=+0.6
3m | 11.02px | 8.86px | 8.15px | 8.90px | 11.14px
5m | 11.02px | 8.86px | 8.15px | 8.90px | 11.14px
10m | 11.02px | 8.86px | 8.15px | 8.90px | 11.14px
25m | 11.02px | 8.86px | 8.15px | 8.90px | 11.14px
注意:和距离完全无关(3 m 和 25 m 都是 11.02 px),
但越靠画面边缘越大(中心 8.15 → 边缘 11.14)。中心值正好等于 fx·θ = 933 × 0.00873 = 8.15。
深度Z | X/Z=-0.6 | X/Z=-0.3 | X/Z=+0.0 | X/Z=+0.3 | X/Z=+0.6
3m | 3.11px | 3.11px | 3.11px | 3.11px | 3.11px
5m | 1.87px | 1.87px | 1.87px | 1.87px | 1.87px
10m | 0.93px | 0.93px | 0.93px | 0.93px | 0.93px
25m | 0.37px | 0.37px | 0.37px | 0.37px | 0.37px
正好相反:和画面位置无关,和距离成 1/Z(3 m 处 3.11 px → 25 m 处 0.37 px)。
深度Z | X/Z=-0.6 | X/Z=-0.3 | X/Z=+0.0 | X/Z=+0.3 | X/Z=+0.6
3m | 5.60px | 2.80px | 0.00px | 2.80px | 5.60px
25m | 5.60px | 2.80px | 0.00px | 2.80px | 5.60px
和距离无关;画面中心是 0,向边缘线性增长。
时间戳相差 33 ms,车在这段时间里走了 33 cm,效果等同于一个 33 cm 的平移误差, 区别只在它随速度变化、而不随安装变化。换算到像素:
深度Z | X/Z=-0.6 | X/Z=-0.3 | X/Z=+0.0 | X/Z=+0.3 | X/Z=+0.6
3m | 102.68px | 102.68px | 102.68px | 102.68px | 102.68px
5m | 61.61px | 61.61px | 61.61px | 61.61px | 61.61px
10m | 30.80px | 30.80px | 30.80px | 30.80px | 30.80px
25m | 12.32px | 12.32px | 12.32px | 12.32px | 12.32px
3 m 处 102.68 px,比 1 cm 标定误差大 33 倍。
第七节四组数据的分布形态两两正交,互不混淆,于是可以整理成一棵判据树:
旋转误差 只取决于【画面位置】,与距离无关
平移误差 只取决于【距离】,与画面位置无关
内参误差 与距离无关,且【中心为 0】
时间误差 形态像平移误差(1/Z),但【量级随速度变化】
错位现象 结论
─────────────────────────────────────────────────────
中心对齐、边缘错位 → 【内参】问题(fx/fy/主点/畸变)
全图均匀错位(中心也错) → 【旋转外参】问题
近处错位大、远处小 → 【平移外参】问题(或时间偏移)
错位量随运动速度变化 → 【时间同步】问题
错位量在 3D 空间随距离增大 → 旋转误差(3D 位移 = Z·θ)
这套判据可以在 30 秒内把问题归到某一类参数上,而不是盲目地重标一遍。
这个理由不成立。从第七节的数据看:
Z·θ),不是像素偏移但"看远处细长物体"仍然是正确的验收方法,理由不是误差大小,是可检测性:
远处的电线杆 → 在图像上只有几像素宽
→ 点云偏 8px 就【完全跑到杆子外面】→ 一眼看出
近处的墙 → 在图像上占几百像素
→ 偏 8px 相对它的尺寸微不足道 → 看不出来
结论:远处细长物体放大了错误的可见性,而不是放大了误差本身。
左边是 LiDAR 坐标系下的 3D 点云(可以拖动旋转),右边是实时投影结果。 调参数时注意观察 3D 场景不动、只有图像在变,以及每个参数影响图像的方式不一样。
| 操作 | 观察什么 |
|---|---|
| 只调 fx | 画面里的一切同步放大/缩小,这就是"焦距"的含义。注意 FOV 在变 |
| 关掉"朝向约定" | 整个投影瞬间崩掉,点全部飞到画面外。这就是忘记 LiDAR→Camera 约定时的样子 |
| 把 pitch 调到 ±15° | 场景整体在图像上平移。注意平移量在画面各处一致(旋转误差的特征) |
| 对照右侧 readout 里的 R 矩阵 | 它按行优先逐行打印;把主序搞错(行列颠倒)相当于用转置矩阵做投影,点全部错位到荒诞的位置 |
先看一个具体画面:同一块棋盘格,放在 2 m 处用短焦镜头拍,和放在 4 m 处用长焦镜头拍, 在照片上可以长得完全一样,格子数量相同、大小相同、位置相同。 照片里只有像素坐标、没有深度,所以从一张照片无法区分这两种情形。 这就是焦距与距离之间的尺度歧义:板子实际远一倍,在图上就小一倍,两者恰好抵消。
一张平面靶标只给出一个单应矩阵(8 个自由度), 它能提供的关于内参 K 的约束只有 2 个;而 K 至少有 4 个未知数 (fx、fy、cx、cy),畸变系数还没算进去。方程数少于未知数,解不唯一。
由此可以直接推出张正友标定法的两条要求:必须多张、而且姿态要散开 拍 15~30 张,且板子必须倾斜。如果每张都正对镜头,约束数始终只有那 2 个, fy 和畸变系数永远解不出来。
同理,"所有照片都拍在画面正中央"是标定禁忌,畸变系数的约束全部来自画面边缘。
标定不是四个独立的步骤,而是一条有依赖的流水线。依赖关系如下:
外参标定依赖内参:
求 T_cam_lidar 要先把点云投到图像上
投影需要 K
→ K 错了,投出来的位置就错了
→ 于是优化的"外参"是在【补偿 K 的误差】
→ 结果:一个错误的内参 + 一个"学会补偿它"的外参
→ 换个距离/视角就崩
内参误差会污染外参,这就是为什么标定必须按顺序来, 而且内参的重投影误差要先验收通过(< 0.3 px)再往下走。
内参 → 外参 → 时间 → 融合质量 → 标注质量 → 模型质量
↑ │
└──────────── 发现问题,从最上游查起 ────────────┘
| 内参 | 外参 | 时间参数 | |
|---|---|---|---|
| 描述 | 自身映射 | 空间关系 | 时间关系 |
| 相机 | K + 畸变 | T_world/cam | 曝光时刻、与 IMU 的偏移 |
| LiDAR | 逐线角偏/距偏(固件里) | T_world/lidar | 帧时间戳、逐点时间 |
| IMU | bias, scale, 非正交 | T_world/imu | 与相机偏移 |
| 深度相机 | 两套(深度+彩色) | 两层(内部+对外) | — |
| 怎么标 | 棋盘格 / 靶标 | 靶标或点云-图像对齐 | 运动法 / Kalibr |
| 验收 | 重投影误差 < 0.3 px | 边缘错位 < 2 px | 残留偏移最小 |
| 错了的表现 | 中心对齐、边缘错位 | 全图均匀错位 / 近处偏 | 随速度变化 |
内参 = 自身映射(相机:fx fy cx cy + 畸变);外参 = 空间关系(4×4)。
判断方法:描述"自己怎么读数"的是内参,描述"相对别人在哪"的是外参。
内外参是相对的(深度相机的两层外参),不是绝对属性。
分类有三类,漏掉的那类危害最大:
内参 / 外参 / 时间参数
最反直觉的一条:
我们花最多时间标的几毫米平移误差,往往【不是主要矛盾】。
时间误差(33ms = 103px)和旋转误差(0.5° = 8px)的杀伤力,
通常大于平移误差(1cm = 3px)。
诊断决策树(看错位形态反推病因):
中心对齐边缘错 → 内参 全图均匀错 → 旋转
近处大远处小 → 平移 随速度变化 → 时间
标定顺序必须【先内后外】—— 内参误差会污染外参,
标出一个"学会补偿内参误差"的假外参。