第1章:相机成像原理——针孔相机模型与坐标系转换
大家好,我是你们的老朋友。今天咱们来聊聊多相机融合里最基础、也最绕不开的一个话题——相机成像原理。
说实话,我刚开始做视觉的时候,觉得这东西不就是拍个照嘛,有啥好研究的?直到第一次做多相机标定,发现图像怎么都对不齐,才意识到——坐标系转换没搞明白,后面全是坑。
这一章,咱们就把针孔相机模型、四个坐标系、内外参矩阵这些硬骨头,一块块啃下来。
1.1 针孔相机模型——最朴素的成像方式
你想想看,相机是怎么把三维世界变成一张二维照片的?
最简单的解释就是针孔相机模型。说白了,就是一个小孔,光线穿过它,在后面的感光面上投影成像。
我记得第一次给学生讲这个,有人问:“那为什么我们不用针孔相机拍照?” 嗯,因为针孔太小,进光量不够,需要长时间曝光。但作为数学模型,它非常优雅。
核心公式:
X_screen = f * (X_world / Z_world)
Y_screen = f * (Y_world / Z_world)
其中 f 是焦距,单位是像素或毫米。
这里有个关键点:成像其实是倒立的。但咱们在算法里通常把它正过来处理,省得脑子转不过弯。
我的经验: 实际项目中,针孔模型已经够用。除非你用鱼眼镜头,否则别急着上复杂模型。我曾经在一个项目中用了畸变模型,结果发现标定板拍歪了,折腾了两天——其实针孔模型加个简单的径向畸变就搞定了。
1.2 四个坐标系——从世界到像素的“四步走”
做多相机融合,最头疼的就是坐标系转换。我习惯把这件事拆成四步:
- 世界坐标系:你放标定板的地方,单位是米。
- 相机坐标系:以相机光心为原点,Z轴朝前。
- 图像坐标系:在成像平面上,单位是毫米。
- 像素坐标系:最终图像,单位是像素。
为什么会搞混?因为每个坐标系的原点和单位都不一样。我见过有人把毫米和像素直接相加,结果图像全飞了。
咱们用一张图来理清关系:
1.3 内参矩阵——相机的“身份证”
内参矩阵,说白了就是描述相机内部光学特性的。每个相机都有自己的内参,就像人的指纹一样。
它的标准形式是:
K = [fx, 0, cx]
[ 0, fy, cy]
[ 0, 0, 1]
其中:
- fx, fy:焦距在x和y方向上的像素值。理论上应该相等,但实际有误差。
- cx, cy:光心在像素坐标系中的位置。理想情况下在图像中心,但实际会偏移。
注意: 内参矩阵是上三角矩阵,最后一行是[0,0,1]。这个结构不能乱改。我曾经见过有人把内参矩阵写成了满秩矩阵,结果标定出来的结果完全不对。
内参怎么得到?标定。用棋盘格或者圆点标定板,拍个十几张不同角度的照片,用OpenCV的calibrateCamera函数就能算出来。
我的习惯: 标定的时候,尽量让标定板占满画面,并且覆盖各个角落。我一般拍20张左右,角度从正对到倾斜30度左右。太少的话,内参的稳定性不够。
1.4 外参矩阵——相机在哪儿?朝哪儿看?
外参矩阵解决的是相机在世界坐标系中的位置和朝向。它由两部分组成:
- 旋转矩阵 R:3×3,描述相机的朝向。
- 平移向量 t:3×1,描述相机的位置。
合起来就是 [R | t],一个3×4的矩阵。
举个例子:
R = [[1, 0, 0],
[0, 1, 0],
[0, 0, 1]] # 相机正对着世界坐标系
t = [0, 0, 1]^T # 相机在世界坐标系原点正前方1米处
这个例子表示相机没有旋转,就在世界坐标系原点的正前方1米处。
关键理解: 外参矩阵的作用是把世界坐标转换到相机坐标。说白了,就是回答一个问题:“这个点相对于相机,在哪儿?”
1.5 从世界到像素——完整流程
咱们把整个流程串起来:
- 世界坐标 → 相机坐标:乘以外参矩阵 [R|t]
- 相机坐标 → 图像坐标:除以Zc,得到归一化坐标 (x/Zc, y/Zc)
- 图像坐标 → 像素坐标:乘以内参矩阵 K
写成公式就是:
s * [u, v, 1]^T = K * [R | t] * [Xw, Yw, Zw, 1]^T
这里的 s 是尺度因子,因为从3D到2D丢失了深度信息。
避坑指南: 我曾经在融合两个相机的数据时,发现同一个点在两个图像上的像素坐标对不上。查了半天,原来是两个相机的外参矩阵用的世界坐标系原点不一样。一个用标定板左上角,一个用标定板中心。嗯,从那以后,我统一用标定板中心作为世界坐标系原点。
1.6 畸变模型——现实世界不完美
针孔模型是理想情况。实际镜头会有畸变,主要是两种:
- 径向畸变:图像边缘向内(桶形)或向外(枕形)弯曲。
- 切向畸变:镜头和成像平面不平行导致的。
畸变模型通常用几个参数来描述:
径向畸变:k1, k2, k3
切向畸变:p1, p2
标定的时候,这些参数会一起算出来。然后你就可以用它们去去畸变。
我的建议: 对于普通镜头,用k1, k2, p1, p2四个参数就够了。k3一般用不上,除非是鱼眼镜头。我见过有人把k3也加上,结果去畸变后图像边缘反而更扭曲了。
1.7 实战小贴士——标定流程
最后,分享一个我常用的标定流程:
- 打印一张棋盘格标定板(A4纸就行,贴在硬纸板上)。
- 用相机从不同角度拍15-20张照片。
- 用OpenCV的findChessboardCorners找到角点。
- 调用calibrateCamera得到内参、外参和畸变系数。
- 用initUndistortRectifyMap生成去畸变映射表。
- 用remap对图像去畸变。
代码示例(Python + OpenCV):
import cv2
import numpy as np
# 准备标定板角点
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
objp = np.zeros((6*9, 3), np.float32)
objp[:,:2] = np.mgrid[0:9, 0:6].T.reshape(-1, 2)
objpoints = [] # 世界坐标
imgpoints = [] # 像素坐标
# 对每张图片找角点
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, (9,6), None)
if ret:
objpoints.append(objp)
corners2 = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria)
imgpoints.append(corners2)
# 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None)
print("内参矩阵:\n", mtx)
print("畸变系数:\n", dist)
嗯,代码不长,但每一步都有坑。比如角点检测失败,可能是标定板不够清晰,或者光照不均匀。我建议你拍的时候,保证标定板平整,光线均匀。
好了,这一章的内容就到这儿。针孔模型、四个坐标系、内外参矩阵,这些是后续所有多相机融合技术的基础。搞懂了它们,后面的路就好走了。
公众号:蓝海资料掘金营,微信deep3321