2. 鱼眼相机成像基础
做环视拼接,第一步就是把相机“摸透”。你连镜头怎么成像、图像怎么扭曲的都不清楚,后面做拼接肯定踩坑。这一章咱们就把相机模型和标定基础讲透。
针孔模型:最朴素的成像原理
针孔模型是相机成像的“理想国”。光线穿过一个小孔,在后面的感光面上倒立成像。说白了,就是初中物理的小孔成像实验。
数学上,它用一个 3×4 投影矩阵 描述:
s * [u, v, 1]^T = K * [R | t] * [X, Y, Z, 1]^T
这里面:
- K 是内参矩阵(焦距、主点)
- [R | t] 是外参(旋转+平移)
- s 是尺度因子
内参矩阵长这样:
K = [fx, 0, cx;
0, fy, cy;
0, 0, 1]
fx, fy 是焦距(像素单位),cx, cy 是主点(光轴与像面交点)。
鱼眼模型:为什么针孔不够用?
针孔模型假设光线直线传播。但鱼眼镜头为了看到 180° 甚至更大视野,故意让光线弯曲。你想想看,如果按针孔模型算,180° 视场角的像平面得无限大——这不可能。
所以鱼眼模型用 投影函数 代替直线投影。常见的有:
- 等距投影:r = f * θ
- 等立体角投影:r = 2f * sin(θ/2)
- 正交投影:r = f * sin(θ)
- 体视投影:r = 2f * tan(θ/2)
其中 θ 是入射光线与光轴的夹角,r 是像点到主点的距离。
市面上大部分鱼眼相机用的是 等距投影 或 等立体角投影。我拆过几款车载环视摄像头,基本都是等距模型。
畸变系数:现实总是不完美
即使有了投影模型,镜头加工和装配还会引入额外畸变。主要分两类:
| 畸变类型 | 产生原因 | 数学表示 |
|---|---|---|
| 径向畸变 | 透镜曲面不完美 | k1, k2, k3(泰勒展开系数) |
| 切向畸变 | 镜头与像面不平行 | p1, p2 |
径向畸变让直线变弯——桶形畸变(负值)或枕形畸变(正值)。鱼眼镜头主要是桶形畸变,而且非常严重。
切向畸变相对小,但也不能忽略。我曾经调试一套环视系统,发现左右拼接缝总差几个像素,查了半天——是切向畸变没标好。
内参、外参、畸变系数:三者的关系
这三个概念容易搞混,我画个图帮你理清:
简单说:内参 + 畸变 描述相机本身的光学特性,外参 描述相机在世界坐标系中的位姿。标定的目标就是同时求出这三组参数。
张正友标定法:为什么它成了行业标准?
张正友在 1998 年提出的方法,到现在还是最主流的标定方案。它的核心思路是:
- 拍几张棋盘格照片(不同角度、不同位置)
- 提取角点(棋盘格内角点)
- 利用单应性矩阵 建立 3D 世界点与 2D 图像点的关系
- 闭式解 + 非线性优化 得到内参、外参、畸变系数
为什么它这么流行?因为:
- 只需要一个打印的棋盘格,不需要精密设备
- 算法稳定,OpenCV 直接封装好了
- 对标定环境要求低,室内室外都能做
标定流程实操:从拍照到出参数
我用 OpenCV 做标定的标准流程:
# 1. 准备棋盘格图像
# 2. 提取角点
ret, corners = cv2.findChessboardCorners(gray, (cols, rows), None)
# 3. 亚像素精炼
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners2 = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria)
# 4. 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None)
# 5. 矫正图像
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
这里面 objpoints 是棋盘格的世界坐标(Z=0),imgpoints 是对应的图像角点坐标。
标定质量评估:怎么看结果好不好?
标定完不能直接拿去用,得先看看质量。我一般看三个指标:
| 指标 | 正常范围 | 说明 |
|---|---|---|
| 重投影误差 | < 0.5 像素 | 将标定点反投影回图像,计算与原始角点的距离 |
| 内参稳定性 | 多次标定差异 < 1% | 换几组照片重新标定,看 fx, fy 是否稳定 |
| 矫正后直线度 | 肉眼观察无明显弯曲 | 用矫正后的图像看场景中的直线是否变直 |
重投影误差小于 0.3 像素算优秀,0.5 以内可接受。超过 1 像素就要检查标定板是否平整、照片是否模糊。
鱼眼标定的特殊之处
鱼眼镜头畸变大,直接用针孔模型标定会出问题。OpenCV 提供了专门的鱼眼标定接口:
# 鱼眼标定
ret, mtx, dist, rvecs, tvecs = cv2.fisheye.calibrate(
objpoints, imgpoints, gray.shape[::-1], None, None)
# 鱼眼矫正
map1, map2 = cv2.fisheye.initUndistortRectifyMap(
mtx, dist, np.eye(3), newcameramtx, size, cv2.CV_16SC2)
dst = cv2.remap(img, map1, map2, cv2.INTER_LINEAR)
区别在于:
- 鱼眼模型的畸变系数是 4 个(k1,k2,k3,k4),不是针孔的 5 个
- 投影模型不同,矫正映射的计算方式也不同
- 鱼眼标定需要更多照片(我一般拍 25-30 张),覆盖更大的视场角范围
嗯,这里要注意:鱼眼标定对棋盘格在图像中的大小有要求。如果棋盘格太小(比如只占图像 5% 的区域),角点提取精度会下降,标定结果就不准了。