本文目录导读:

相机畸变校正是计算机视觉和摄影测量中的基本步骤,由于镜头的光学特性以及制造工艺的限制,相机拍摄的图像往往会产生失真,即畸变(Distortion)。
以下是对相机畸变校正的全面解析,包括原理、数学模型、常用工具和实操步骤。
什么是相机畸变?
畸变就是“直线变弯”,主要分为两大类:
- 径向畸变:光线在远离镜头中心的地方弯曲程度更大。
- 桶形畸变:图像边缘向外凸出,直线变成向外弯曲的曲线(常见于广角镜头、鱼眼镜头)。
- 枕形畸变:图像边缘向内凹陷,直线变成向内弯曲的曲线(常见于长焦镜头)。
- 切向畸变:由于镜头与成像平面(传感器)不平行(安装误差)导致的畸变,表现为图像在某个方向被拉伸或倾斜。
畸变校正的数学模型
校正过程本质上是建立一个从畸变图像坐标到理想(无畸变)图像坐标的映射关系。
径向畸变模型
使用泰勒级数展开来描述。
假设(x, y)是归一化平面上的无畸变坐标,(x_distorted, y_distorted)是畸变后的坐标,r^2 = x^2 + y^2。
-
公式:
-
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) -
参数:
k1,k2,k3:径向畸变系数。- 注意:对于普通相机,通常只需要
k1和k2。k3主要用于高畸变镜头(如鱼眼镜头)。
-
切向畸变模型
-
公式:
-
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:切向畸变系数。
-
内参矩阵
除了畸变参数,还需要相机内参(从像素坐标到相机坐标的转换):
fx,fy:焦距。cx,cy:主点坐标(光轴与图像平面的交点,通常接近图像中心)。
完整的校正流程: 原始像素点 ( (u, v) ) → 转换到归一化平面 ( (x, y) ) → 应用畸变模型得到 ( (x', y') ) → 重投影回像素坐标系得到畸变后的像素 ( (u', v') ),然后用这些映射关系去重采样原图像(插值)。
如何获取这些参数(相机标定)
你无法手动计算这些参数,需要通过相机标定来获得。
最经典的方法:张正友标定法(Zhang’s Method)
所需工具:
- 棋盘格 或 圆点标定板。
- 标定软件:OpenCV、Matlab Camera Calibrator。
操作步骤:
- 拍摄多张照片:手持标定板,在相机前摆出不同的角度、距离和位置(至少要20张,覆盖整个视场角,尤其是边缘区域)。
- 提取角点:程序自动识别每张图片中棋盘格的角点坐标。
- 求解参数:利用角点的实际物理坐标(世界坐标)和图像上的坐标进行最小二乘法求解。
- 优化:使用非线性优化(如Levenberg-Marquardt算法)进一步降低重投影误差。
标定输出:内参矩阵 + 畸变系数 (k1, k2, p1, p2, k3)。
代码实操(OpenCV Python示例)
这是最常见且标准的方法。
import cv2
import numpy as np
import glob
# 1. 准备标定板参数
CHECKERBOARD = (9, 6) # 内角点数量 (列, 行)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# 2. 准备世界坐标系中的点 (假设棋盘格内角点间隔为固定尺寸,如2.5cm)
objp = np.zeros((CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32)
# 注意:这里设为单位长度,实际物理尺寸可用但不需要,因为畸变与尺度无关
objp[:, :2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2)
# 储存所有图片的点
objpoints = [] # 3D点 in real world space
imgpoints = [] # 2D点 in image plane
# 读取所有标定图片
images = glob.glob('calibration_images/*.jpg') # 替换为你的路径
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 3. 查找棋盘格角点
ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD, None)
if ret == True:
objpoints.append(objp)
# 亚像素级精确角点坐标
corners2 = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria)
imgpoints.append(corners2)
# 可视化角点
cv2.drawChessboardCorners(img, CHECKERBOARD, corners2, ret)
cv2.imshow('img', img)
cv2.waitKey(500)
cv2.destroyAllWindows()
# 4. 执行标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
print("相机内参矩阵: \n", mtx)
print("畸变系数 (k1, k2, p1, p2, k3): \n", dist)
# 5. 校正图像
img = cv2.imread('test_image.jpg') # 读取一张待校正的图
h, w = img.shape[:2]
# 方法一:使用 getOptimalNewCameraMatrix 调整可视区域
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
# (可选)裁剪图像边缘(如果新相机矩阵有缩放)
x, y, w, h = roi
dst = dst[y:y+h, x:x+w]
cv2.imshow('Undistorted Image', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
常见问题与注意事项
- 标定板要占据画面不同区域:不要只把标定板放在画面正中间,要让它在画面的四个角和边缘都出现,这样才能准确估计边缘的畸变。
- 标定板要倾斜:不要只保持与相机平行,要有不同角度的倾斜(如30度、45度),这样才能准确估计焦距。
- 固定焦距:在标定和实际拍摄时,不要调整变焦环(物理变焦),因为焦距变化后内参和畸变系数都会变,如果是定焦镜头,最佳。
- 对焦清晰:标定照片需要清晰,模糊的边缘会导致角点检测不准。
- 重投影误差:标定完成后,检查
ret(重投影误差),通常小于 5 像素 为好的标定结果,如果误差很大(>1像素),说明照片质量或角点提取有问题。 - 对于鱼眼镜头:OpenCV 有专门的
cv2.fisheye模块,其模型不同(通常使用k1,k2,k3,k4),不能用上面的普通针孔相机模型。
没有标定板怎么办?
- 自标定:通过场景中的结构(如建筑的直线、电线杆)进行自动校正,但精度不如标定板。
- 直接读取内参:某些高端工业相机或手机厂商会提供内参和畸变系数(如通过 EXIF 数据或传感器接口 API)。
| 步骤 | 核心任务 | 关键工具/参数 |
|---|---|---|
| 获取畸变参数 | 拍摄标定板照片 | 棋盘格、OpenCV findChessboardCorners |
| 计算内参与畸变 | 最小二乘法求解 | calibrateCamera 输出 mtx (内参) , dist (畸变) |
| 映射与重采样 | 建立无畸变到畸变的像素映射 | getOptimalNewCameraMatrix + undistort |
| 验证与调整 | 检查直线是否变直 | 重投影误差 < 0.5 像素 |
如果你能提供具体的截图(例如棋盘格照片或畸变明显的图像),我可以针对性地分析畸变类型或调整标定策略。