本文将要学习摄像机有什么类型失真,如何发现相机的内在和外在特性,如何根据这些特性消除图像的失真。有一些针孔相机会给图像带来严重的失真,主要存在这两种类型:径向畸变和切向畸变。径向畸变会导致直线拍摄出来的图里变成曲线,径向畸变越厉害,那么图像的点就会偏离图像中心点越远。比如如下图所示:
从上图可以看到,红线是理论上的直线,但是实际上棋盘线已经偏离了很多,与红线不重合,所有直线都已经变形,这个就是径向畸变,可以使用下面的公式来表示:
同样,切向畸变主要原因是因为相机的镜头平面与物体的平面不是在一个完全平行的两个平面,就会发现有一部分图像比另外一部分图像变小,可以用下面的公式来表示这种变化:
综合上面两个公式,就可以发现主要有5个参数会影响图像的变化:
除此之外,我们还需要一些其他信息,比如相机的内部和外部参数。内部参数是相机特有的特性,比如焦距(fx,fy)和光学中心点(Cx,Cy)。焦距和光学中心可以创建一个相机矩阵,可以用来消除由于特定相机镜头造成的失真,由于相机矩阵是针对镜头的,只要计算一次,就可以应用于所有照片的纠正。这个矩阵构造如下:
外部的参数主要是旋转向量和平移向量,它是用来从三维空间转换到平面坐标系的。在三维重构的应用里,这些失真都必须提前校正。为了计算出来这些参数,需要使用的一些固定模式的照片来进行,比如这里使用棋盘的照片。因为我们已经知道这些点在实际环境里的位置,以及这些角点的相对位置关系。再从照片里找到这些角点的位置,这样就可以计算纠正变换矩阵了。不过为了更加准确,一般采用不同角度至少拍摄10次照片,越多的照片就越准确。
由前面可知,在校准的过程里需要知道实物实际的坐标和图像里对应的坐标。对于图像里的2D坐标是比较容易的,可以通过找角点就可以计算出来。但是三维空间里的实物坐标,就比较难找了,因为我们没有一个一个地测量它,那么用什么方法找到呢?其实我们可以假定XYZ里,Z轴的坐标总是在0的位置,这样就可以只找XY轴坐标就行了,另外由于棋盘的每一个格是大小相等的,也是一个正方形,说明可以用理论上的坐标就可模拟它了,而不用实际去测量,唯一不同的就是每个正方形的边长大小不一样。其实这个跟远近有关系,因此我们只需要比例对得上就可以了,这样就可以把校准里需要的三维坐标与图像的二维坐标准备好了。
在校准之前,需要计算好三维的坐标,比如输入棋盘实际的大小,比如是8X8,还是5X5,或是7X6的,这些都是指内部相交点的个数。设置好棋盘的实际格数之后,就可以使用cv.findChessboardCorners()来寻找,如果找到相同大小的格数,那么就说明成功找到校准点数。一旦找到所有角点之后,就可以使用函数cv.cornerSubPix()来找更加精确的位置,这样让坐标点更加准确。同时也可以使用函数cv.drawChessboardCorners()来显示找到棋盘坐标点的模式是否正确。
前面两步构造了三维的坐标点和找到图像里二维坐标点,这样就可以调用cv.calibrateCamera()函数来计算校准参数返回,不过在使用校准之前,会遇到这样一个问题。由于照片是径向凸出的,如果纠正之后,会出现如下图所示:
这时看到原来是正矩形的照片,在四边上凹下去了。有时候并不想要这样的照片,那么就需要使用函数cv.getOptimalNewCameraMatrix(),如果你不想看到这些黑边凹下去,就要设置alpha为0,否则设置为1。最后可以使用cv.undistort()或者cv.remap()函数返回纠正之后的结果。
演示的例子代码如下:
#python 3.7.4,opencv4.1
#蔡军生 https://blog.csdn.net/caimouse/article/details/51749579
#
import numpy as np
import cv2
from matplotlib import pyplot as plt
#角点迭代退出条件
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
#准备3D里的坐标点, like (0,0,0), (1,0,0), (2,0,0) ....,(9,5,0)
objp = np.zeros((9*5,3), np.float32)
objp[:,:2] = np.mgrid[0:9,0:5].T.reshape(-1,2)
#保存实物坐标和图像二维坐标数组
objpoints = [] # 3d空间坐标点数组
imgpoints = [] # 2d图像坐标点数组
images = ['chessCA1.png'] #glob.glob('*.jpg')
for fname in images:
img = cv2.imread(fname) #读取图片
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#找角点
ret, corners = cv2.findChessboardCorners(gray, (9,5), None)
print(ret)
# 如果发模式匹配,就添加三维坐标和图像二维坐标
if ret == True:
objpoints.append(objp)
corners2 = cv2.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria)
imgpoints.append(corners)
# 显示找到的二维坐标点
cv2.drawChessboardCorners(img, (9,5), corners2, ret)
cv2.imshow('img', img)
cv2.waitKey(500)
#校准参数计算
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
img = cv2.imread(fname) #要校准的照片,这里采用原图一样
h, w = img.shape[:2]
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('calibresult.png', dst)
#
cv2.waitKey(0)
cv2.destroyAllWindows()
结果输出如下:
输入校准的照片,标上找到的角点
纠正之后的图片
来源:https://blog.csdn.net/caimouse/article/details/102760875