第1章:相机成像原理——针孔相机模型与坐标系转换

大家好,我是你们的老朋友。今天咱们来聊聊多相机融合里最基础、也最绕不开的一个话题——相机成像原理

说实话,我刚开始做视觉的时候,觉得这东西不就是拍个照嘛,有啥好研究的?直到第一次做多相机标定,发现图像怎么都对不齐,才意识到——坐标系转换没搞明白,后面全是坑

这一章,咱们就把针孔相机模型、四个坐标系、内外参矩阵这些硬骨头,一块块啃下来。

1.1 针孔相机模型——最朴素的成像方式

你想想看,相机是怎么把三维世界变成一张二维照片的?

最简单的解释就是针孔相机模型。说白了,就是一个小孔,光线穿过它,在后面的感光面上投影成像。

我记得第一次给学生讲这个,有人问:“那为什么我们不用针孔相机拍照?” 嗯,因为针孔太小,进光量不够,需要长时间曝光。但作为数学模型,它非常优雅。

核心公式:

X_screen = f * (X_world / Z_world)
Y_screen = f * (Y_world / Z_world)

其中 f 是焦距,单位是像素或毫米。

这里有个关键点:成像其实是倒立的。但咱们在算法里通常把它正过来处理,省得脑子转不过弯。

我的经验: 实际项目中,针孔模型已经够用。除非你用鱼眼镜头,否则别急着上复杂模型。我曾经在一个项目中用了畸变模型,结果发现标定板拍歪了,折腾了两天——其实针孔模型加个简单的径向畸变就搞定了。

1.2 四个坐标系——从世界到像素的“四步走”

做多相机融合,最头疼的就是坐标系转换。我习惯把这件事拆成四步:

  1. 世界坐标系:你放标定板的地方,单位是米。
  2. 相机坐标系:以相机光心为原点,Z轴朝前。
  3. 图像坐标系:在成像平面上,单位是毫米。
  4. 像素坐标系:最终图像,单位是像素。

为什么会搞混?因为每个坐标系的原点和单位都不一样。我见过有人把毫米和像素直接相加,结果图像全飞了。

咱们用一张图来理清关系:

世界坐标系 (Xw, Yw, Zw) 单位:米 外参矩阵 相机坐标系 (Xc, Yc, Zc) 单位:米 投影变换 图像坐标系 (x, y) 单位:毫米 内参矩阵 像素 (u,v) 像素 从世界到像素:四步转换流程 完整转换公式 s * [u, v, 1]^T = K * [R | t] * [Xw, Yw, Zw, 1]^T 其中:K = 内参矩阵,[R|t] = 外参矩阵,s = 尺度因子 像素坐标 = 内参 × 外参 × 世界坐标

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 从世界到像素——完整流程

咱们把整个流程串起来:

  1. 世界坐标 → 相机坐标:乘以外参矩阵 [R|t]
  2. 相机坐标 → 图像坐标:除以Zc,得到归一化坐标 (x/Zc, y/Zc)
  3. 图像坐标 → 像素坐标:乘以内参矩阵 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 实战小贴士——标定流程

最后,分享一个我常用的标定流程:

  1. 打印一张棋盘格标定板(A4纸就行,贴在硬纸板上)。
  2. 用相机从不同角度拍15-20张照片。
  3. 用OpenCV的findChessboardCorners找到角点。
  4. 调用calibrateCamera得到内参、外参和畸变系数。
  5. 用initUndistortRectifyMap生成去畸变映射表。
  6. 用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