原始数据集
首先我们下载公开的相机标定数据集,其中包括了相机本身的参数,以及在各个角度拍摄的黑白棋盘格标定图像。
在代码实现时,需要先加载图像。
import cv2
import numpy as np
import glob
import matplotlib.pyplot as plt
#导入必要的库文件
images = glob.glob('test/*.png')#读取图像文件
i = 0
for frame in images:
i += 1
img = cv2.imread(frame)
plt.subplot(3,4,i)
plt.imshow(img)
角点检测
接下来需要找到参与相机标定所需要的棋盘格角点,在检测角点时,可以使用以下代码检测。需要说明的是,采用
ret, corners = cv2.findChessboardCorners(gray, (w, h), None)
测出来的角点只是一个较为粗略的值,需要使用代码
cv2.drawChessboardCorners(img, (w, h), corners, ret)
将所得的粗略角点精细化。
i = 0
# 角点精确检测阈值
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 找到棋盘格角点
ret, corners = cv2.findChessboardCorners(gray, (w, h), None)
if ret == True:
# 角点精确检测
i += 1
cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
# 将角点在图像上显示
cv2.drawChessboardCorners(img, (w, h), corners, ret)
plt.subplot(3,4,i)
plt.imshow(img)
得到了角点检测结果
相机标定
而在完成相机标定的过程中,必不可少的就是角点的世界坐标系坐标,以及其在图像中的像素坐标,而我们在棋盘格标定中默认了棋盘格所在白纸就是世界坐标系的xOy平面,而每个角点的世界坐标如图所示。
而图像角点的像素坐标已经由corners中给出,接下来可以进行相机标定。
# 世界坐标系中的棋盘格点,例如(0,0,0), (1,0,0), (2,0,0) ....,(8,5,0),去掉Z坐标,记为二维矩阵
objp = np.zeros((w * h, 3), np.float32)
objp[:, :2] = np.mgrid[0:w, 0:h].T.reshape(-1, 2)
# 储存棋盘格角点的世界坐标和图像坐标对
objpoints = [] # 在世界坐标系中的三维点
imgpoints = [] # 在图像平面的二维点
i = 0
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 找到棋盘格角点
ret, corners = cv2.findChessboardCorners(gray, (w, h), None)
# 如果找到足够点对,将其存储起来
if ret == True:
# 角点精确检测
i += 1
cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
objpoints.append(objp)
imgpoints.append(corners)
# 标定
# 输入:世界坐标系里的位置 像素坐标 图像的像素尺寸大小 3*3矩阵,相机内参数矩阵 畸变矩阵
# 输出:标定结果 相机的内参数矩阵 畸变系数 旋转矩阵 平移向量
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
# mtx:内参数矩阵
# dist:畸变系数
# rvecs:旋转向量 (外参数)
# tvecs :平移向量 (外参数)
print(("ret:"), ret)
print(("mtx:\n"), mtx) # 内参数矩阵
print(("dist:\n"), dist) # 畸变系数 distortion cofficients = (k_1,k_2,p_1,p_2,k_3)
print(("rvecs:\n"), rvecs) # 旋转向量 # 外参数
print(("tvecs:\n"), tvecs) # 平移向量 # 外参数
最终得到的相机标定的参数矩阵。
mtx: [[535.9408931 0. 324.42873643] [ 0. 537.70952677 241.77575315] [ 0. 0. 1. ]] dist: [[ 1.49846290e-02 -1.04155881e-01 -2.89149243e-03 -2.32605223e-04 4.19011469e-01]] |
而在官方数据集中的相机标定参数为:
mtx: [[535.4 0. 320.1 ] [ 0. 539.2 247.6 ] [ 0. 0. 1. ]] dist: [[ 0 0 0 0 0]] |
可以发现其结果较为吻合。
原始图像去畸变
最后我们可以对原始的拍摄的相机图像通过得到的相机畸变参数完成去畸变。
# 去畸变
imgs = glob.glob('corret/*.jpg')
i = 0
for frame in imgs:
i += 1
img2 = cv2.imread(frame)
h, w = img2.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 0, (w, h))
dst = cv2.undistort(img2, mtx, dist, None, newcameramtx)
plt.subplot(3,4,i)
plt.imshow(dst)
cv2.imwrite('corret/' + str(i) + '.jpg', dst)
得到去畸变结果为:
可以发现,由于得到的相机畸变参数都较小,所以去畸变结果和原始图像基本一致。
反投影误差计算
最后计算反投影误差,通过反投影误差,我们可以来评估结果的好坏。越接近0,说明结果越理想。通过之前计算的内参数矩阵、畸变系数、旋转矩阵和平移向量,使用cv2.projectPoints()计算三维点到二维图像的投影,然后计算反投影得到的点与图像上检测到的点的误差,最后计算一个对于所有标定图像的平均误差,这个值就是反投影误差。
total_error = 0
for i in range(len(objpoints)):
imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
total_error += error
print(("total error: "), total_error / len(objpoints))
total error: 0.025468250649175708
说明相机标定结果较好。