相机标定简单来说是从世界坐标系转换为相机坐标系,再由相机坐标系转换为图像坐标系的过程。
世界坐标系: 用户定义的三维世界的坐标系,为了描述目标物在真实世界里的位置而引入。
相机坐标系: 在相机上建立的坐标系,为了从相机的角度描述物体位置而定义,作为沟通世界坐标系和图像/像素坐标系的中间一环。
图像坐标系: 为了描述成像过程中物体从相机坐标系到图像坐标系的投影透射关系而引入,方便进一步得到像素坐标系下的坐标。
世界坐标系–>相机坐标系:
这一步是三维点到三维点的转换,包括R,t(相机外参)等参数。
相机坐标系–>图像坐标系:
这一步是三维点到二维点的转换,包括K(相机内参)等参数。
可推导出下面的变换公式:
像主点的偏移:
可推导出下面的变换公式:
内参矩阵K:
f:焦距
x0、y0:像主点坐标
上述内参矩阵K其实并不能完整表达投影原理,在相机拍摄过程中,由于透镜质量或者光线在远离透镜中心的地方比靠近中心的地方更加弯曲等因素影响,图像可能会发生畸变现象,因此需要一个畸变参数s来表示畸变现象对图像产生的影响。即完整的内参矩阵K如下图所示:
畸变现象分为两类:
1)桶状畸变
畸变前:
畸变后:
2)枕形畸变
畸变前:
畸变后:
外参矩阵[R丨t]:
外参矩阵[R丨t]中R表示旋转、t表示平移。
结合内参外参公式得到总的公式如下:
相机标定的基本流程如下:
1、针对一个黑白棋盘格(黑白间距已知)拍摄若干张图片(一般10-20张)
2、在图片中检测特征点(Harris角点)
3、根据角点位置信息及图像中的坐标,求解Homographic矩阵
4、利用解析解估算方法计算出5个内部参数,以及6个外部参数
5、根据极大似然估计策略,设计优化目标并实现参数的refinement
角点检测结果展示:
矫正结果前后展示:
矫正前:
矫正后:
参数结果展示:
# coding=utf-8
import numpy as np
import cv2
import glob
# 终止标准
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
#w = 6
#h = 6
objp = np.zeros((6*6,3), np.float32)
objp[:,:2] = np.mgrid[0:6,0:6].T.reshape(-1,2)
# 用于存储所有图像中的对象点和图像点的数组。
objpoints = [] # 在现实世界空间的3d点
imgpoints = [] # 图像平面中的2d点。
images = glob.glob('C:/B/*.jpg')
for fname in images:
#对每张图片,识别出角点,记录世界物体坐标和图像坐标
print('processing img:{fname}')
img = cv2.imread(fname)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#转灰度
print('grayed')
#寻找角点,存入corners,ret是找到角点的flag
ret, corners = cv2.findChessboardCorners(gray, (6, 6),None)
# 如果找到,添加对象点,图像点(精炼后)
if ret == True:
print('chessboard detected')
objpoints.append(objp)
#执行亚像素级角点检测
corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
imgpoints.append(corners2)
# 绘制并显示角点
img = cv2.drawChessboardCorners(img, (6,6), corners2,ret)
cv2.namedWindow('img',0)
cv2.resizeWindow('img', 500, 500)
cv2.imshow('img',img)
cv2.waitKey(500)
cv2.destroyAllWindows()
img2 = cv2.imread("C:/B/13.jpg")
print("type objpoints:{objpoints[0].shape}")
print("type imgpoints:{imgpoints[0].shape}")
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1],None,None)
h, w = img2.shape[:2]
newcameramtx, roi=cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h))
#纠正畸变
dst = cv2.undistort(img2, mtx, dist, None, newcameramtx)
# 裁剪图像,输出纠正畸变以后的图片
x,y,w,h = roi
dst = dst[y:y+h, x:x+w]
cv2.imwrite('calibresult.png',dst)
#打印我们要求的两个矩阵参数
print ("newcameramtx外参:\n",newcameramtx)
print ("dist畸变值:\n",dist)
print ("newcameramtx旋转(向量)外参:\n",rvecs)
print ("dist平移(向量)外参:\n",tvecs)
#计算误差
tot_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)
tot_error += error
print ("total error: ", tot_error/len(objpoints))
1)问题:拍摄的照片运行代码时只能运行一部分照片
解决办法:可能因为是手机拍摄的原因,传输到电脑时比例失调,保存到电脑的时候有些模糊,把图片一一修改了大小之后就没有这样的问题了。
2)问题:角点数目计算错误
解决办法:角点检测时,角点数目错误会导致程序出现上图错误,更改为正确的角点数目即可。