计算机视觉——基于使用 OpenCV 与 Python 实现相机标定畸变校正
概述
相机标定是一种旨在通过确定相机的内参(焦距、光学中心、畸变系数)和外参(相机的位置和方向),提高图像在现实世界中的几何精度的过程。该过程可以纠正相机拍摄的图像中的畸变,使相机能够准确感知现实世界中的距离、角度和物体。一个很好的例子是纠正鱼眼相机拍摄的图像。
一、什么是相机标定
相机通过将其投影到二维平面上来捕捉现实世界。然而,由于光学元件和镜头的结构特性,这些图像可能会出现误差。最常见的误差是畸变和透视误差。相机标定通过计算相机的内参和外参来纠正这些误差,从而实现更准确的测量和几何计算。
关键参数
-
内参
- 焦距:根据镜头的焦距确定图像的大小。
- 光学中心(主点):相机镜头的中心点。
- 畸变系数:用于纠正镜头畸变,如桶形畸变和枕形畸变。(你可以在图 1 中清楚地看到这一点。)
注意:术语“畸变”指的是镜头引起的误差,如变形或弯曲。
-
外参
- 相机位置:相机相对于世界的位置(x、y、z 坐标)。
- 相机方向:相机相对于世界的视角(旋转角度)。
二、如何进行相机标定
通常使用已知的几何图案(如棋盘格)进行相机标定。该图案的已知尺寸和位置用作参考,以检测相机图像中的畸变。 标定过程包括以下步骤:
- 图像采集:要进行相机标定,你需要一组至少 15 张从不同角度拍摄的棋盘格图案的图像。该图案的角点有助于检测图像中的畸变。
- 角点检测:在每张图像中检测棋盘格图案的角点。正确检测这些角点对于准确标定至关重要。
- 内参和外参的计算:根据相机图像中的角点与它们的真实世界坐标之间的差异,优化并计算相机的内参和外参。通常使用 AI 算法或数学优化技术进行此计算。
- 畸变纠正:使用计算出的参数纠正图像中的畸变,去除非线性镜头畸变。
- 验证:为了测试标定的准确性,用相机拍摄一张新图像,并应用标定参数来纠正图像。然后观察纠正的准确性。
三、使用 OpenCV 在 Python 中进行相机标定
OpenCV 是 Python 中用于相机标定最常用的库之一。OpenCV 提供了相机标定和畸变纠正所需的函数。以下是简单的标定示例:
import cv2
import numpy as np
import glob# 棋盘格的尺寸(内部角点的数量)
grid_size = (9, 6)# 每个正方形的实际尺寸(2 厘米)
square_size = 2 # 厘米# 棋盘格的 3D 世界坐标
obj_points = np.zeros((grid_size[0] * grid_size[1], 3), np.float32)
obj_points[:, :2] = np.mgrid[0:grid_size[0], 0:grid_size[1]].T.reshape(-1, 2) * square_size# 用于存储标定所需点的列表
object_points = [] # 3D 世界坐标
image_points = [] # 2D 图像坐标# 存放标定图像的文件夹
images = glob.glob('calibration_images/*.jpg')for fname in images:img = cv2.imread(fname)gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 查找棋盘格的角点ret, corners = cv2.findChessboardCorners(gray, grid_size, None)if ret:object_points.append(obj_points)image_points.append(corners)# 可视化角点cv2.drawChessboardCorners(img, grid_size, corners, ret)cv2.imshow('Chessboard Corners', img)cv2.waitKey(500)cv2.destroyAllWindows()# 执行标定
ret, camera_matrix, dist_coeffs, rvecs, tvecs = cv2.calibrateCamera(object_points, image_points, gray.shape[::-1], None, None
)# 保存相机矩阵和畸变系数
np.savez('calibration_data.npz', camera_matrix=camera_matrix, dist_coeffs=dist_coeffs)# 打印标定结果
print("Camera Matrix:\n", camera_matrix)
print("Distortion Coefficients:\n", dist_coeffs)
四、代码解释
import cv2
import numpy as np
import glob
- cv2(OpenCV):我们导入 OpenCV,这是一个用于图像处理的库。该库提供了许多用于相机标定和图像处理的函数。
- numpy(np):我们导入 NumPy,以便轻松进行数值运算和处理数组。
- glob:用于组织文件路径并列出文件夹中的文件。在这里,它用于获取包含标定图像的文件夹中的所有图像。
grid_size = (9, 6)
- grid_size:指定棋盘格上内部角点的数量。在此示例中,棋盘格图案有 9 列和 6 行角点(9 列 × 6 行 = 54 个角点)。此值应与实际使用的棋盘格匹配。
square_size = 2 # cm
- square_size:棋盘格上的每个正方形的实际边长设置为 2 厘米。
obj_points = np.zeros((grid_size[0] * grid_size[1], 3), np.float32)
obj_points[:, :2] = np.mgrid[0:grid_size[0], 0:grid_size[1]].T.reshape(-1, 2) * square_size
- obj_points:我们创建棋盘格角点在真实世界坐标中的 3D 位置。每个角点的 Z 轴值设置为 0。
- np.zeros((grid_size[0] * grid_size[1], 3), np.float32):创建一个 3D 空矩阵,为每个角点包含(x、y、z)坐标。
- np.mgrid[0:grid_size[0], 0:grid_size[1]]:生成棋盘格上角点的(x、y)坐标。
注意:mgrid
是 NumPy 的一个函数,用于创建多维网格结构,其语法为 mgrid[start:end:step]
。
例如:
# 二维网格(网格)
x, y = np.mgrid[0:3, 0:3]
print("X:\n", x)
print("Y:\n", y)
输出:
X:[[0 0 0][1 1 1][2 2 2]]
Y:[[0 1 2][0 1 2][0 1 2]]
- T.reshape(-1, 2):将二维角点转换为单个矩阵。
object_points = [] # 3D 世界坐标
image_points = [] # 2D 图像坐标
- object_points:一个列表,用于存储每张图像的 3D 真实世界棋盘格角点。
- image_points:一个列表,用于存储每张图像的 2D 图像角点。
images = glob.glob('calibration_images/*.jpg')
- images:查找
calibration_images
文件夹中的所有.jpg
文件,并将它们存储在一个列表中。这些是用于标定的棋盘格图案图像。
for fname in images:img = cv2.imread(fname)gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
- 此循环处理每张图像:
- cv2.imread(fname):读取由
fname
指定的图像。 - cv2.cvtColor(img, cv2.COLOR_BGR2GRAY):将读取的图像从彩色(BGR)转换为灰度。在灰度图像上检测棋盘格图案更容易。
- cv2.imread(fname):读取由
ret, corners = cv2.findChessboardCorners(gray, grid_size, None)
- cv2.findChessboardCorners:尝试在灰度图像中查找棋盘格的角点。
- ret:一个标志(True/False),指示是否成功找到棋盘格的角点。
- corners:检测到的图像中角点的 2D 坐标。
if ret:object_points.append(obj_points)image_points.append(corners)
- 如果 ret 为
True
,即成功找到棋盘格的角点:- object_points.append(obj_points):将 3D 真实世界坐标添加到列表中。
- image_points.append(corners):将 2D 图像坐标添加到列表中。
cv2.drawChessboardCorners(img, grid_size, corners, ret)
cv2.imshow('Chessboard Corners', img)
cv2.waitKey(500)
- cv2.drawChessboardCorners:通过在图像上绘制线条来可视化检测到的棋盘格角点。
- cv2.imshow:在一个新窗口中显示带有检测到的角点的图像。
- cv2.waitKey(500):显示图像 500 毫秒(0.5 秒)。
cv2.destroyAllWindows()
- cv2.destroyAllWindows:关闭所有打开的窗口。
ret, camera_matrix, dist_coeffs, rvecs, tvecs = cv2.calibrateCamera(object_points, image_points, gray.shape[::-1], None, None
)
- camera_matrix:一个包含相机内参(焦距、光学中心等)的矩阵。
- dist_coeffs:镜头畸变系数。
- rvecs:旋转向量。
- tvecs:平移向量。
- cv2.calibrateCamera:执行相机标定。该函数根据真实世界 3D 点与其在图像中的对应 2D 点之间的关系计算相机参数。
- object_points:3D 真实世界坐标。
- image_points:图像中的 2D 投影坐标。
- gray.shape[::-1]:图像分辨率(宽度和高度)。
- camera_matrix(默认值:None)—— calibrateCamera 的参数:这是一个 3×3 矩阵,表示相机的内参(焦距、主点等)。如果提供为 None,这些参数将由函数计算。如果你有一个已知的相机矩阵,你可以在这里提供它。
- dist_coeffs(默认值:None)—— calibrateCamera 的参数:这是一个表示畸变系数(畸变)的向量。如果提供为 None,畸变系数将由函数计算。
注意:提供 **camera_matrix=None**
和 **dist_coeffs=None**
的原因是你希望计算这些参数并获得标定的结果。 在你的代码中,你试图从特定图像中推导出相机的内参和镜头畸变系数(camera_matrix 和 dist_coeffs)。
- flags(可选):这些是用于在标定期间指定某些选项的标志。例如:
cv2.CALIB_USE_INTRINSIC_GUESS
:启用内参(相机矩阵和畸变系数)的初始猜测。cv2.CALIB_FIX_PRINCIPAL_POINT
:固定主点。cv2.CALIB_FIX_ASPECT_RATIO
:固定纵横比。
- criteria(可选):指定迭代标准。这是优化过程的停止条件,通常与
cv2.TERM_CRITERIA_MAX_ITER
和cv2.TERM_CRITERIA_EPS
等选项一起使用。
np.savez('calibration_data.npz', camera_matrix=camera_matrix, dist_coeffs=dist_coeffs)
- np.savez:将计算出的相机矩阵和畸变系数保存到名为
calibration_data.npz
的文件中。此文件允许你稍后重用标定参数。
print("Camera Matrix:\n", camera_matrix)
print("Distortion Coefficients:\n", dist_coeffs)
- print:在屏幕上显示 camera_matrix(内参)和 dist_coeffs。
此代码使用 OpenCV 进行相机标定,并保存结果。相机标定对于纠正图像中的畸变以及进行准确测量至关重要。
五、标定结果中你将获得的值
在这一过程结束时,camera_matrix
和 dist_coeffs
将由函数计算并返回:
camera_matrix
该矩阵包含相机的内参(焦距、主点等)。它用于了解相机镜头的特性以及透视变换。它是一个 3×3 矩阵,可能如下所示:
在这里,f_x 和 f_y 是相机沿水平和垂直方向的焦距(以像素为单位),c_x 和 c_y 是图像平面上主点的像素坐标(通常是图像的中心)。
什么是主点? 主点表示图像的光学中心,并定义了相机镜头与图像平面之间的关系。如果相机的光轴偏离中心,这种偏移可以通过 c_x 和 c_y 坐标检测到。
示例:
假设你的相机分辨率为 1920×1080 像素,标定后你获得以下 camera_matrix:
- f_x = 1200 和 f_y = 1200:沿水平和垂直方向的焦距。
- c_x = 960 和 c_y = 540:主点的坐标,位于图像平面的中心(960 和 540 表示中心点,因为你的分辨率为 1920×1080)。
dist_coeffs
该向量包含镜头的畸变系数。这些系数用于纠正镜头的几何畸变(例如桶形畸变或枕形畸变)。
通常,该向量包含以下系数:
这些系数:
-
k_1、k_2、k_3:径向畸变系数。这些系数用于纠正桶形或枕形畸变。如果图像中的畸变随着距离中心的增加而变得更加明显,这就是径向畸变,k_1、k_2 和 k_3 用于纠正这种畸变。k_1 纠正靠近图像中心的畸变。k_2 纠正向图像边缘的较大畸变。k_3 对远离中心的点(尤其是边缘)进行微调,特别是对于边缘处的畸变。
桶形畸变:一种向边缘膨胀的畸变。
枕形畸变:一种向边缘收缩的畸变。
-
p_1、p_2:切向畸变系数。当镜头与传感器未完美对齐时,会发生这种畸变。如果镜头未完美居中或存在轴向偏移,图像倾向于向边缘偏移。p_1 和 p_2 用于纠正这种倾斜。p_1 纠正沿 x 轴(水平平面)的偏移或畸变。p_2 纠正沿 y 轴(垂直平面)的偏移或畸变。
什么是径向畸变和切向畸变?
径向畸变:这些畸变导致图像中的直线随着距离中心的增加而弯曲。k_1、k_2 和 k_3 用于纠正这些曲线。
切向畸变:当镜头与传感器不完全垂直时发生,导致图像向边缘偏移。p_1 和 p_2 用于纠正这些偏移。
-
k_4、k_5、k_6(可选):高阶径向畸变系数。这些参数用于纠正更复杂的畸变,例如来自超广角镜头的畸变。它们通常不用于标准标定,但在需要更精确的校正时可以应用。
示例:
dist_coeffs = [0.1, -0.25, 0.001, 0.002, 0.03]
- k_1 = 0.1 和 k_2 = -0.25:用于纠正径向畸变的系数。
- p_1 = 0.001 和 p_2 = 0.002:用于纠正切向畸变的系数。
- k_3 = 0.03:一个高阶径向畸变系数。
K 和 P 的值越大,校正效果越强。随着它们的减小,校正效果减弱。