3、相机模型与标定:针孔相机模型、内参与外参、张正友标定法简介
各位同学,咱们今天聊聊相机模型和标定。说实话,这是BEV感知里最基础、也最容易踩坑的一环。我刚开始做视觉融合的时候,就因为在标定上偷了个懒,结果后面整个系统都跑偏了。嗯,咱们一步步来。
3.1 针孔相机模型——说白了就是小孔成像
你想想看,相机是怎么把三维世界拍到二维照片上的?最经典的解释就是针孔模型。说白了,就是光线通过一个小孔,在后面的成像平面上倒着投影。
数学上怎么描述?我们通常把三维空间中的点 (X, Y, Z) 映射到图像平面上的 (u, v)。这个过程可以写成:
λ * [u, v, 1]^T = K * [R | t] * [X, Y, Z, 1]^T
这个公式看着复杂,其实就干了两件事:
- 外参:把世界坐标转到相机坐标(旋转R + 平移t)
- 内参:把相机坐标投影到像素平面(矩阵K)
我在项目中遇到过一个问题:明明标定好了,但投影出来的点总是有偏差。后来发现是忽略了镜头畸变。所以这里要提醒大家,针孔模型是理想情况,实际镜头都有畸变,后面会讲。
3.2 内参矩阵——相机的“身份证”
内参矩阵K长这样:
K = [fx, 0, cx;
0, fy, cy;
0, 0, 1]
这里:
fx, fy:焦距,单位是像素。说白了就是镜头到成像平面的距离,换算成像素个数cx, cy:光心偏移。理想情况下光心在图像正中心,但实际总有偏差
我个人习惯在拿到新相机后,第一件事就是标定内参。为什么?因为内参是固定的,只要不换镜头、不变焦距,它就不变。你想想看,如果内参错了,后面所有三维重建都是错的。
3.3 外参——把相机放到世界坐标系里
外参解决的是“相机在哪、朝哪看”的问题。它由旋转矩阵R和平移向量t组成:
[R | t] = [r11, r12, r13, t1;
r21, r22, r23, t2;
r31, r32, r33, t3]
在BEV感知里,外参特别重要。因为我们要把多个相机的图像统一到同一个坐标系下(通常是车身坐标系)。
举个例子:
- 前视相机的外参:把相机坐标转到车头方向
- 环视相机的外参:把四个鱼眼相机都转到车身中心
这里有个坑——外参会随着时间变化。车开久了,摄像头支架可能松动,或者车身轻微变形。所以,我建议每隔一段时间重新标定一下外参。
3.4 张正友标定法——最经典的标定方法
说到标定,就绕不开张正友标定法。这个方法1998年提出,到现在还是工业界的主流。为什么?因为它只需要一个棋盘格,操作简单,精度还高。
核心思路是这样的:
- 拍一组不同角度的棋盘格照片
- 提取棋盘格的角点坐标
- 利用棋盘格的物理尺寸(比如格子边长30mm)和图像坐标,求解单应性矩阵
- 通过多张照片的约束,解出内参和外参
- 最后用最小二乘法优化畸变参数
说白了,就是利用“我知道棋盘格长什么样”这个先验信息,反推出相机的参数。
我记得第一次用OpenCV实现张正友标定时,代码其实很简单:
import cv2
import numpy as np
# 准备棋盘格角点的世界坐标
pattern_size = (9, 6) # 内角点数量
objp = np.zeros((pattern_size[0]*pattern_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2)
# 假设格子边长30mm
objp *= 30.0
# 存储所有图片的角点
objpoints = [] # 世界坐标
imgpoints = [] # 图像坐标
# 遍历图片
for fname in image_files:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)
if ret:
objpoints.append(objp)
imgpoints.append(corners)
# 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None
)
print("内参矩阵:\n", mtx)
print("畸变系数:\n", dist)
这段代码跑完,你就得到了内参矩阵和畸变系数。嗯,就这么简单。
3.5 畸变校正——别让镜头骗了你
实际镜头都有畸变,主要是两种:
- 径向畸变:画面边缘的直线变弯了,像鱼眼效果
- 切向畸变:镜头和成像平面不平行,导致画面倾斜
OpenCV里用5个参数来描述畸变:k1, k2, p1, p2, k3。前两个是径向畸变,中间两个是切向畸变,最后一个k3是径向畸变的高阶项。
校正畸变的代码也很直接:
# 获取校正映射
h, w = img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))
# 校正图像
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
我曾经在项目里遇到过一个问题:校正后的图像边缘有黑边。这是因为校正后图像的有效区域变小了。解决办法是用 cv2.getOptimalNewCameraMatrix 的 alpha 参数控制保留多少边缘像素。
3.6 小结
这一章咱们聊了:
- 针孔相机模型:三维到二维的映射
- 内参:相机的固有属性,焦距和光心
- 外参:相机在世界坐标系中的位姿
- 张正友标定法:用棋盘格标定内参和外参
- 畸变校正:让图像更真实
下一章咱们会讲鱼眼相机模型和环视系统的标定。嗯,那个更有意思,因为鱼眼相机的畸变更大,标定也更讲究技巧。
记住一句话:标定是BEV感知的基石。标定做不好,后面所有工作都是白费。我见过太多项目因为标定问题返工的了。