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 畸变矫正:把弯的掰直

标定出畸变系数后,我们就能做矫正了。流程是这样的:

  1. 对每个像素(u, v),计算它在归一化平面上的坐标(x, y)
  2. 用畸变模型计算畸变后的坐标(x_distorted, y_distorted)
  3. 查表得到畸变后的像素值,赋值给原像素

代码实现大概是这样:

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 知识体系总览

下面这张图,是我自己总结的相机内参知识结构。你可以把它当成一个“检查清单”——标定前过一遍,看看有没有遗漏:

相机内参知识体系 相机内参 内参矩阵 K fx, fy: 焦距(像素) cx, cy: 主点坐标 畸变模型 径向畸变: k1, k2, k3 切向畸变: p1, p2 桶形畸变 枕形畸变 畸变矫正 remap + 双线性插值 关键原则 标定一次,只要不动镜头,内参就固定不变 点云投影到图像时,必须经过相同的畸变模型

4.6 标定中的常见问题

最后,我把自己踩过的坑整理一下,希望能帮你少走弯路:

问题 现象 可能原因 解决方法
重投影误差大 标定板角点投影偏差>1像素 标定板不平、图像模糊、畸变模型阶数不够 换硬质标定板、调高快门速度、尝试k3
主点偏移严重 cx, cy偏离图像中心>10像素 镜头装配偏差、图像被裁剪过 检查镜头安装、确认图像原始尺寸
畸变矫正后图像扭曲 直线变S形 畸变系数符号错误、标定图片数量不足 检查k1符号、增加不同角度的标定图片
fx和fy差异大 纵横比>1.05或<0.95 像素非正方形、图像缩放未保持比例 确认传感器规格、检查图像预处理流程

嗯,关于相机内参,今天就聊到这儿。记住一句话:内参标定是联合标定的第一步,也是最重要的一步。这一步做扎实了,后面的外参标定才能顺风顺水。我见过太多项目,外参怎么调都对不上,最后发现是内参标定出了问题——白白浪费了好几天时间。

我的习惯:每次拿到新相机,第一件事就是标定内参。标定完我会拍一张棋盘格照片,用标定结果反投影回去,看看重投影误差。如果误差在0.3像素以内,我才放心用它做联合标定。这个习惯帮我省了不少排查时间。

可可精品资料库