4. 相机内参:内参矩阵K的定义、焦距与主点、畸变模型(径向与切向)
好,咱们进入相机内参的世界。说实话,这是整个联合标定里最绕不开的一块基石。你想想看,相机拍出来的图像,跟真实世界之间到底差了多少层?内参就是描述这个“映射关系”的数学语言。我当年刚入行时,总觉得内参就是个矩阵,背下来就行。直到第一次标定出来的点云投影到图像上,偏差了十几个像素,我才意识到——嗯,这里面的门道深着呢。
4.1 内参矩阵K:从3D到2D的“翻译官”
内参矩阵K,说白了就是描述相机如何把三维空间里的点,映射到二维图像平面上的。它长这样:
K = [fx 0 cx
0 fy cy
0 0 1 ]
这个3x3矩阵,包含了两个核心信息:焦距和主点。我习惯把它理解成“相机的DNA”——每台相机都有自己的K矩阵,换了镜头或者动了CMOS,K就得重新标。
投影公式其实很简单:
s * [u, v, 1]^T = K * [X, Y, Z]^T
这里s是尺度因子,[u,v]是像素坐标,[X,Y,Z]是相机坐标系下的三维点。为什么要有s?因为从3D到2D,深度信息丢失了,我们只能知道方向,不知道距离。这个s就是用来归一化的。
核心要点:内参矩阵K只跟相机本身有关,跟场景、光照、物体位置都没关系。所以一旦标定好,只要不动镜头,K就是固定的。
4.2 焦距与主点:fx、fy、cx、cy到底代表什么?
咱们拆开来看这四个参数:
- fx, fy:分别是x方向和y方向的焦距,单位是像素。为什么会有两个?因为CMOS的像素不一定是正方形,有些传感器x和y方向的像素尺寸不一样。我遇到过一台工业相机,fx和fy差了0.3%,虽然不大,但在远距离测量时误差会累积。
- cx, cy:主点坐标,单位也是像素。理论上主点应该在图像正中心,但实际装配总有偏差。我记得有一次标定出来的cx偏了5个像素,一开始以为是标定板没放正,后来发现是镜头座有点歪。
这里有个小公式:fx = F / dx,其中F是物理焦距(毫米),dx是单个像素的物理尺寸(毫米/像素)。所以同样的镜头,放在不同分辨率的传感器上,fx会不一样。这个道理你想想看——像素越小,同样的物理距离覆盖的像素数就越多,fx自然就大了。
我的经验:在联合标定中,fx和fy的比值(纵横比)如果偏离1太多,要小心。我曾经遇到一个项目,标定出来的纵横比是1.02,排查了半天才发现是图像缩放时没保持宽高比。所以标定前,先确认图像有没有被resize过。
4.3 畸变模型:为什么直线会变弯?
理想情况下,相机是针孔模型。但现实中的镜头都有畸变。说白了,就是光线经过镜头时被“掰弯”了。畸变主要分两种:径向畸变和切向畸变。
4.3.1 径向畸变:桶形与枕形
径向畸变是沿着半径方向发生的变形。越靠近图像边缘,畸变越明显。它有两种常见形态:
- 桶形畸变:图像边缘向外凸出,像木桶。常见于广角镜头。
- 枕形畸变:图像边缘向内凹陷,像枕头。常见于长焦镜头。
数学上,我们用多项式来建模:
x_distorted = x * (1 + k1*r^2 + k2*r^4 + k3*r^6)
y_distorted = y * (1 + k1*r^2 + k2*r^4 + k3*r^6)
其中r是点到主点的距离,k1、k2、k3是径向畸变系数。一般只用k1和k2就够了,k3是给鱼眼镜头准备的。我做过一个项目,用了鱼眼镜头,不加k3的话边缘畸变根本矫正不过来。
注意:径向畸变系数k1、k2、k3的正负号决定了畸变方向。k1为正时是枕形畸变,为负时是桶形畸变。我在调试时习惯先看k1的符号,基本就能判断镜头类型了。
4.3.2 切向畸变:镜头与传感器不平行
切向畸变,也叫离心畸变。产生的原因是镜头的光轴没有完全垂直于CMOS平面。说白了,就是镜头装歪了。数学上:
x_distorted = x + (2*p1*x*y + p2*(r^2 + 2*x^2))
y_distorted = y + (p1*(r^2 + 2*y^2) + 2*p2*x*y)
p1和p2是切向畸变系数。这个畸变在工业相机中比较常见,尤其是那些用螺丝固定的镜头。我遇到过一台相机,p1和p2都很大,拆下来一看,镜头底座有个螺丝松了。
4.4 畸变矫正:把弯的掰直
标定出畸变系数后,我们就能做矫正了。流程是这样的:
- 对每个像素(u, v),计算它在归一化平面上的坐标(x, y)
- 用畸变模型计算畸变后的坐标(x_distorted, y_distorted)
- 查表得到畸变后的像素值,赋值给原像素
代码实现大概是这样:
def undistort_image(img, K, dist_coeffs):
h, w = img.shape[:2]
new_K, roi = cv2.getOptimalNewCameraMatrix(K, dist_coeffs, (w, h), 1, (w, h))
mapx, mapy = cv2.initUndistortRectifyMap(K, dist_coeffs, None, new_K, (w, h), 5)
undistorted_img = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)
return undistorted_img
这里有个细节:getOptimalNewCameraMatrix的alpha参数。alpha=0时,会裁剪掉畸变矫正后产生的黑边;alpha=1时,保留所有像素但会有黑边。我一般设成0.5,既保留大部分视野,又不会出现太多无效区域。
避坑指南:我曾经在标定完畸变后,直接把原图拿去投影点云,结果发现边缘区域误差很大。后来才意识到——点云投影到图像上时,也要用同样的畸变模型。也就是说,联合标定中,激光雷达的点投影到相机图像时,必须先经过畸变矫正,或者直接用畸变后的坐标。这个顺序搞反了,标定就全废了。
4.5 知识体系总览
下面这张图,是我自己总结的相机内参知识结构。你可以把它当成一个“检查清单”——标定前过一遍,看看有没有遗漏:
4.6 标定中的常见问题
最后,我把自己踩过的坑整理一下,希望能帮你少走弯路:
| 问题 | 现象 | 可能原因 | 解决方法 |
|---|---|---|---|
| 重投影误差大 | 标定板角点投影偏差>1像素 | 标定板不平、图像模糊、畸变模型阶数不够 | 换硬质标定板、调高快门速度、尝试k3 |
| 主点偏移严重 | cx, cy偏离图像中心>10像素 | 镜头装配偏差、图像被裁剪过 | 检查镜头安装、确认图像原始尺寸 |
| 畸变矫正后图像扭曲 | 直线变S形 | 畸变系数符号错误、标定图片数量不足 | 检查k1符号、增加不同角度的标定图片 |
| fx和fy差异大 | 纵横比>1.05或<0.95 | 像素非正方形、图像缩放未保持比例 | 确认传感器规格、检查图像预处理流程 |
嗯,关于相机内参,今天就聊到这儿。记住一句话:内参标定是联合标定的第一步,也是最重要的一步。这一步做扎实了,后面的外参标定才能顺风顺水。我见过太多项目,外参怎么调都对不上,最后发现是内参标定出了问题——白白浪费了好几天时间。
我的习惯:每次拿到新相机,第一件事就是标定内参。标定完我会拍一张棋盘格照片,用标定结果反投影回去,看看重投影误差。如果误差在0.3像素以内,我才放心用它做联合标定。这个习惯帮我省了不少排查时间。
可可精品资料库