一、实验内容
了解针孔照相机模型的相关知识,实现相机标定,打印内参、外参矩阵。
1.图片准备
博主用手机拍摄了11张不同角度的棋盘格图片:
2.代码标定
import cv2
import numpy as np
import glob
# 定义棋盘格的角点数目,例如8x11表示每行8个角点,每列11个角点
pattern_size = (8, 11)
# 定义棋盘格的边长,例如2表示每个格子的边长为2厘米
square_size = 2
# 创建一个空列表,用于存储棋盘格的三维坐标点
obj_points = []
# 创建一个空列表,用于存储棋盘格的图像坐标点
img_points = []
# 根据棋盘格的角点数目和边长,生成棋盘格的三维坐标点
# 例如,如果角点数目为9x6,边长为2,则生成的坐标点为
# (0,0,0), (2,0,0), (4,0,0), ..., (20.8,0,0)
# (0,2,0), (2,2,0), (4,2,0), ..., (20.8,2,0)
# ...
# (0,13,0), (2.6,13,0), (5.2,13,0), ..., (20.8,13,0)
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) * square_size
# 获取棋盘格图片的路径,例如"D:/images/*.jpg"
images_path = "D:/Computer Vision/exp4/pictures/*.png"
# 读取所有的图片文件名
images = glob.glob(images_path)
# 遍历每一张图片
for fname in images:
# 读取图片并转换为灰度图
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 查找棋盘格的角点
ret, corners = cv2.findChessboardCorners(gray, pattern_size)
# 如果找到了角点
if ret:
# 将三维坐标点添加到obj_points列表中
obj_points.append(objp)
# 对角点进行亚像素级别的精确化
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1),
(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001))
# 将图像坐标点添加到img_points列表中
img_points.append(corners2)
# 在原图上绘制角点,并显示
img = cv2.drawChessboardCorners(img, pattern_size, corners2, ret)
cv2.imshow('img', img)
cv2.waitKey(500)
# 保存标定后的图片到当前目录下,以calib_开头,后面跟原文件名
new_fname = "calib_" + fname.split("\\")[-1]
cv2.imwrite(new_fname, img)
# 关闭所有窗口
cv2.destroyAllWindows()
# 根据obj_points和img_points,计算相机内参矩阵和畸变系数
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points,
img_points,
gray.shape[::-1],
None,
None)
# 打印内参矩阵和畸变系数
print("内参矩阵:")
print(mtx)
print("畸变系数:")
print(dist)
# 打印外参矩阵(旋转矩阵和平移向量)
for i in range(len(rvecs)):
print("第{}张图片的外参矩阵:".format(i + 1))
# 将旋转向量转换为旋转矩阵
R = cv2.Rodrigues(rvecs[i])[0]
print(R)
print(tvecs[i])
3.运行结果
①打印内参、外参矩阵
②标定结果(其中一张)
二、代码说明
代码是基于OpenCV的相机标定的示例,它的目的是通过拍摄棋盘格图像,来估计相机的内参和外参,以及畸变系数。这些参数可以用来校正图像的失真,以及计算图像中的点与真实世界中的点之间的关系。:
1.首先定义棋盘格的角点数目,也就是patternSize参数。这个参数是一个cv::Size类型的变量,它表示棋盘格每行和每列的内部交点的个数。例如,我的棋盘格是9行12列,那么角点数目应该是8行11列,也就是patternSize = cv::Size(11, 8)。
②然后定义棋盘格的边长,也就是square_size参数。这个参数是一个float类型的变量,它表示每个格子的物理尺寸,例如厘米或毫米(需要用尺子测量)。例如,我的棋盘格的边长是2厘米,那么可以设置square_size = 2。
③定义三维坐标点,也就是objp变量。这个变量是一个vectorcv::Point3f类型的容器,它存储了每个角点在真实世界中的三维坐标,可以使用一个for循环来生成objp变量,其中每个角点的x坐标和y坐标分别等于其在棋盘格中的行号和列号乘以square_size,而z坐标为0。例如,我的棋盘格有88个角点,那么可以使用以下代码来生成objp变量:
# 定义三维坐标点
objp = np.zeros((88, 3), np.float32)
objp[:, :2] = np.mgrid[0:11, 0:8].T.reshape(-1, 2) * square_size
④获取棋盘格图片的路径,也就是images_path变量。这个变量是一个string类型的变量,它表示存放棋盘格图片的文件夹路径,并且包含了图片的后缀名。例如,我存放棋盘格图片的文件夹路径是"D:/Computer Vision/exp4/pictures ",并且图片格式是png,那么就设置images_path = “D:/Computer Vision/exp4/pictures/*.png”。
⑤读取所有的图片文件名,并存放在images变量中。这个变量是一个list类型的容器,它存储了所有图片文件名。可以使用glob.glob函数来读取images_path变量中指定路径下所有符合条件的文件名,并将其添加到images变量中。例如:images = glob.glob(images_path)
⑥定义两个空列表来存储三维坐标点和图像坐标点,也就是obj_points和img_points变量。这两个变量都是list类型的容器,它们分别存储了每张图片对应的三维坐标点和图像坐标点。使用append方法来向这两个列表中添加元素。
⑦遍历每一张图片,并查找棋盘格的角点。可以使用一个for循环来遍历images列表中的每一个文件名,并使用cv2.imread函数来读取图片,并转换为灰度图。然后使用cv2.findChessboardCorners函数来查找棋盘格的角点,并返回一个布尔值ret和一个角点坐标列表corners。如果ret为True,则表示找到了角点;否则表示没有找到角点。例如:
# 遍历每一张图片
for fname in images:
# 读取图片并转换为灰度图
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 查找棋盘格的角点
ret, corners = cv2.findChessboardCorners(gray, pattern_size)
⑧如果找到了角点,则需要对角点进行亚像素级别的精确化,并将三维坐标点和图像坐标点添加到obj_points和img_points列表中。可以使用cv2.cornerSubPix函数来优化角点位置,并返回一个新的角点坐标列表corners2。然后可以使用obj_points.append(objp)和img_points.append(corners2)来将三维坐标点和图像坐标点添加到对应列表中。例如:
if ret:
# 将三维坐标点添加到obj_points列表中
obj_points.append(objp)
# 对角点进行亚像素级别的精确化
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1),
(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001))
# 将图像坐标点添加到img_points列表中
img_points.append(corners2)
⑨最后需要根据obj_points和img_points列表来校准相机,并返回相机内参矩阵、畸变系数、旋转向量和平移向量等参数。使用cv2.calibrateCamera函数来校准相机,并返回ret、mtx、dist、rvecs、tvecs等参数。其中ret表示校准误差;mtx表示相机内参矩阵;dist表示畸变系数;rvecs表示旋转向量;tvecs表示平移向量。例如:
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points, gray.shape[::-1], None, None)
其他说明:
1.内参是指相机的内部参数,它们只与相机本身有关,不会因为外界环境而改变。内参包括以下几个元素:
- 焦距 f,表示相机的光心到成像平面的距离,单位是毫米。
- 像素尺寸 d_x 和 d_y,表示每个像素在成像平面上的物理尺寸,单位是毫米/像素。
- 主点坐标 c_x 和 c_y,表示相机的光轴在像素坐标系中的位置,单位是像素。
- 倾斜角 θ,表示成像平面的横边和纵边之间的夹角,一般接近90度。
内参可以用一个 3x3 的矩阵 K 来表示,形式如下:
其中,f_x = f / d_x,f_y = f / d_y,s = -f \cot\theta是内参的缩放因子。
2.外参是指相机的外部参数,它们描述了相机在世界坐标系中的位置和姿态,会随着相机的移动而变化。外参包括以下两个元素:
- 旋转矩阵 R,表示相机坐标系到世界坐标系的旋转变换,是一个 3x3 的正交矩阵。
- 平移向量 t,表示相机坐标系的原点在世界坐标系中的位置,是一个 3x1 的列向量。
外参可以用一个 3x4 的矩阵 [R | t] 来表示,形式如下:
其中,r_ij是R矩阵的第 i 行第 j 列元素,t_x, t_y, t_z是t向量的三个分量。
3.畸变系数是指由于相机透镜的不完美导致的图像失真的参数,它们会影响图像坐标系到像素坐标系的映射关系。畸变主要分为径向畸变和切向畸变两种:
- 径向畸变是由于透镜边缘处的光线比中心处更弯曲导致的沿着半径方向分布的畸变。径向畸变会使图像中心附近的点向内或向外扭曲,形成桶形或枕形失真。径向畸变可以用三个参数 k_1, k_2, k_3 来描述。
- 切向畸变是由于透镜本身与成像平面不平行导致的沿着切线方向分布的畸变。切向畸变会使图像在水平或垂直方向上发生偏移。切向畸变可以用两个参数 p_1, p_2 来描述。
畸变系数可以用一个 5x1 的列向量 D 来表示,形式如下:
4.对角点进行亚像素级别的精确化:
cornerSubPix(image, corners, winSize, zeroZone, criteria)
- image: 8位单通道的灰度图像。
- corners: 为整数值的像素位置。
- winSize: 指定了等式产生的窗口尺寸。
- zeroZone: 定义了一个禁区(与win相似,但通常比win小),这个区域在方程组以及自相关矩阵中不被考虑。设置(-1,-1)表示不需要这个区域。
- criteria: 终止条件。可以是最大迭代次数cv.TERM_CRITERIA_MAX_ITER,或者设定的精度cv.TERM_CRITERIA_EPS类型,或者两者的组合。终止条件的设置在极大程度上影响最终得到的亚像素的精度。例如知道0.1,则求得的亚像素级精度为像素的十分之一。