刚体从世界坐标系转换到相机坐标系的过程,可以通过旋转和平移来得到,我们将其变换矩阵由一个旋转矩阵和平移向量组合成的齐次坐标矩阵(为什么要引入齐次坐标可见后续文章)来表示:
其中,R为旋转矩阵,t为平移向量,因为假定在世界坐标系中物点所在平面过世界坐标系原点且与Zw轴垂直(也即棋盘平面与Xw-Yw平面重合,目的在于方便后续计算),所以zw=0,可直接转换成式1的形式。其中变换矩阵
即为前文提到的外参矩阵,之所称之为外参矩阵可以理解为只与相机外部参数有关,且外参矩阵随刚体位置的变化而变化。
这一过程进行了从三维坐标到二维坐标的转换,也即投影透视过程(用中心投影法将物体投射到投影面上,从而获得的一种较为接近视觉效果的单面投影图,也就是使我们人眼看到景物近大远小的一种成像方式)。我们还是拿
但是为了在数学上更方便描述,我们将相机坐标系和图像坐标系位置对调,变成图三所示的布置方式(没有实际的物理意义,只是方便计算):
此时,假设相机坐标系中有一点M,则在理想图像坐标系下(无畸变)的成像点P的坐标为(可由相似三角形原则得出):
透镜的畸变主要分为径向畸变和切向畸变,还有薄透镜畸变等等,但都没有径向和切向畸变影响显著,所以我们在这里只考虑径向和切向畸变。
径向畸变是由于透镜形状的制造工艺导致。且越向透镜边缘移动径向畸变越严重。下图所示是径向畸变的两种类型:桶形畸变和枕形畸变。
实际情况中我们常用r=0处的泰勒级数展开的前几项来近似描述径向畸变。矫正径向畸变前后的坐标关系为:
在这里插入图片描述
由此可知对于径向畸变,我们有3个畸变参数需要求解。
切向畸变是由于透镜和CMOS或者CCD的安装位置误差导致。因此,如果存在切向畸变,一个矩形被投影到成像平面上时,很可能会变成一个梯形。切向畸变需要两个额外的畸变参数来描述,矫正前后的坐标关系为:
由此可知对于切向畸变,我们有2个畸变参数需要求解。
综上,我们一共需要5个畸变参数(k1、k2、k3、p1和p2 )来描述透镜畸变。
由于定义的像素坐标系原点与图像坐标系原点不重合,假设像素坐标系原点在图像坐标系下的坐标为(u0,v0),每个像素点在图像坐标系x轴、y轴方向的尺寸为:dx、dy,且像点在实际图像坐标系下的坐标为(xc,yc),于是可得到像点在像素坐标系下的坐标为:
公式2中(xp, yp)与公式5中(xc, yc)相同,都是图像坐标系下的坐标。
若暂不考虑透镜畸变,则将式2与式5的转换矩阵相乘即为内参矩阵M:
之所以称之为内参矩阵可以理解为矩阵内各值只与相机内部参数有关,且不随物体位置变化而变化。
最后用一幅图来总结从世界坐标系到像素坐标系(不考虑畸变)的转换关系:
1.打印一张棋盘格A4纸张(黑白间距已知),并贴在一个平板.上
2.针对棋盘格拍摄若干张图片(一般10-20张)
3.在图片中检测特征点(Harris角点)
4.根据角点位置信息及图像中的坐标,求解Homographic矩阵
5.利用解析解估算方法计算出5个内部参数,以及6个外部参数
6.根据极大似然估计策略,设计优化目标并实现参数的refinement
图像集一共有二十张棋盘格,我是用wps文档插入表格,改变格子的属性颜色就可以变成棋盘格,棋盘格的宽和高为18cm18cm,然后用手机从不同的角度拍摄二十张图片,把每张图片的像素改为500500,我手机的型号为华为P30
图一
# coding=utf-8
import cv2
import numpy as np
import glob
# 设置寻找亚像素角点的参数,采用的停止准则是最大循环次数30和最大误差容限0.001
criteria = (cv2.TERM_CRITERIA_MAX_ITER | cv2.TERM_CRITERIA_EPS, 30, 0.001)
# 获取标定板角点的位置
objp = np.zeros((7 * 7, 3), np.float32)
objp[:, :2] = np.mgrid[0:7, 0:7].T.reshape(-1, 2) # 将世界坐标系建在标定板上,所有点的Z坐标全部为0,所以只需要赋值x和y
obj_points = [] # 存储3D点
img_points = [] # 存储2D点
images = glob.glob("qipan/*.jpg")
i=0
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
size = gray.shape[::-1]
ret, corners = cv2.findChessboardCorners(gray, (7, 7), None)
#print(corners)
if ret:
obj_points.append(objp)
corners2 = cv2.cornerSubPix(gray, corners, (5, 5), (-1, -1), criteria) # 在原角点的基础上寻找亚像素角点
#print(corners2)
if [corners2]:
img_points.append(corners2)
else:
img_points.append(corners)
cv2.drawChessboardCorners(img, (7, 7), corners, ret) # 记住,OpenCV的绘制函数一般无返回值
i+=1
cv2.imwrite('qipan'+str(i)+'.jpg', img)
cv2.waitKey(1500)
print(len(img_points))
cv2.destroyAllWindows()
# 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points, size, None, None)
print("ret:", ret)
print("mtx:", mtx) # 内参数矩阵
print("dist:", dist) # 畸变系数 distortion cofficients = (k_1,k_2,p_1,p_2,k_3)
print("rvecs:", rvecs) # 旋转向量 # 外参数
print("tvecs:", tvecs ) # 平移向量 # 外参数
print("-----------------------------------------------------")
img = cv2.imread(images[2])
h, w = img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h))#显示更大范围的图片(正常重映射之后会删掉一部分图像)
print (newcameramtx)
print("------------------使用undistort函数-------------------")
dst = cv2.undistort(img,mtx,dist,None,newcameramtx)
x,y,w,h = roi
dst1 = dst[y:y+h,x:x+w]
cv2.imwrite('calibresult3.jpg', dst1)
print ("方法一:dst的大小为:", dst1.shape)
重投影误差:
内参矩阵:
畸变系数:
旋转向量:
1、我从不同的角度拍摄的图像的重投影会产生一定的误差,但是误差不是很大。改变图像的数量,重投影的误差也会改变。
2、从实验中,可以得到多个结果,可以获得华为p30的内部参数和外部参数,还可以查看重投影的误差大小等有效信息。
3、就算是统一型号的手机在同一个工厂生产,手机相机的参数都会产生一定的误差。
4、图像发生畸变是很明显的,一眼就可以看出,矫正后的棋盘就很工整。矫正棋盘是实验的目的。
5、图像的像素不要过大也不要过小,过大会延长运行时间,过小会影响运行结果,我设置的是500*500。