针孔相机标定
前段时间曾经做过一段时间的摄像头标定,这里对以前做的事情做一个总结。首先,介绍一下针孔相机的标定吧,主要还是代码解析和一些细节说明,为了让自己更好的理解相机标定。当时做摄像头标定是为了实现基于视觉测定摄像头检测目标的位置,通过摄像头测定相机的内参和外参之后,需要基于公式得到精确的坐标转换矩阵
思路详解
相机代码见 https://github.com/wisdom-bob/Camera_calibration
基于张正友标定法,通过opencv完成摄像头标定,简而言之,我把摄像头标定分为以下几个步骤:
Step1:采集若干照片,筛选照片
Step2:基于图像计算摄像头内参和外参
Step3:取出摄像头实际工作环境的外参,这里由于我默认只求地面的点,则应该取图片平放置地面的那组参数
Step4:基于单应性标定法,求出对应坐标变换矩阵,相关见链接[^1].
这里我就直接开始配合着代码讲内容吧!
# 声明全局变量和一些调试参数
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 300, 0.00001)
find_flag = cv2.CALIB_CB_ADAPTIVE_THRESH+cv2.CALIB_CB_FAST_CHECK+cv2.CALIB_CB_NORMALIZE_IMAGE
global mtx, dist
采集照片,筛选
张正友标定法是通过一种机器学习的方法,利用大量匹配的数据点像素坐标、世界坐标,基于极大似然估计拟合得到一个最优解,所以一定程度上,数据越多,结果就越准确,但是这里也需要注意,采集数据应该要分布均匀,尽可能均匀分布相机视界的所有位置,也就是尽量满足机器学习数据的独立同分布要求~~。
如下图所示,就差不多这样,但是要多拍一些,大概40张左右,可以让自己从容的筛照片。
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6 * 9, 3), np.float32)
objp[:,:2] = 40*np.mgrid[0:9, 0:6].T.reshape(-1, 2)
# Arrays to store object points and image points from all the images.
objpoints = [] # 3d points in real world space
imgpoints = [] # 2d points in image plane.
img_with_corners = []
# Make a list of calibration images
images = glob.glob('./calibration/b*.jpg')
# Step through the list and search for chessboard corners
for i in range(len(images)):
img = cv2.imread(images[i])
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, (9,6), None)
print(images[i],ret)
# If found, add object points, image points
if ret == True:
cv2.cornerSubPix(gray,corners,(15,15),(1,-1),criteria)
imge = cv2.drawChessboardCorners(img, (9,6), corners, ret)
img_with_corners.append(imge)
objpoints.append(objp)
imgpoints.append(corners)
以上的函数就是为了创造数据,用于标定摄像头的。样本数据是cv2.findChessboard函数在图像上找到的标定板的黑白格子交点集X,ground_truth为世界坐标点集,即objp。
#######################解释以下objp是啥———没兴趣可跳过#############################
每张图片对应的objp点集都是一样,而X则各不相同,这里做了一个参考系转换,以标定板为中心,而非摄像头为中心,那么对于每组数据,实际就是相机的位置是不同的,而标定板一直在坐标原点(大概这个意思),因此,objp自然都是一样的。
##################################################################################
基于图像计算摄像头内参和外参
img = cv2.imread('calibration/btest.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img_size = gray.shape[::-1]
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, img_size, None, None)
基于cv2.calibrateCamera得到针孔相机的内参与图像匹配外参。
# estimate the accuracy of the calibration
# by calculating the Standard Deviation
imgpoints2=[]
for i in range(len(objpoints)):
imgpoint,jacobian=cv2.projectPoints(objpoints[i],np.array(rvecs)[i],np.array(tvecs)[i],mtx,dist,0)
imgpoints2.append(imgpoint)
average_d_square=0
for i in range(len(objpoints)):
temp=np.array(imgpoints[i])-np.array(imgpoints2[i])
temp=pow(temp,2)
average_d_square+=np.sqrt(np.sum(temp))/54
# print(np.sqrt(np.sum(temp))/54)
print ("average_d_square:",average_d_square/len(objpoints))
那么我们如何判断我们的标定结果呢,这里opencv给出了一个函数cv2.projectPoints,基于objp利用求得得mtx,dist反推出X,再与实际X基于均方误差和评价结果得准确性,达到0.1以下基本准确。当然也可以利用这个函数,剔除掉一些偏差值太大的图片,提高计算精度。
取出摄像头实际工作环境的外参
要实现坐标转换,基于内参还不够,这里基于拍摄角度,取出一张标定板平铺地面的图片,以这张图的外参作为基准(从rvecs, tvecs把对应的参数翻出来)。
基于单适性因子求出坐标转换矩阵
整理一下,目前我们已经得到摄像头内参mtx,畸变参数dist,对应的外参rvecs,tvecs。现在我们要基于这些得到坐标变换矩阵。
def cal_undistort(img):
# convert image into undistort scale
undist = cv2.undistort(img, mtx, dist, None, mtx)
return undist
既然求得dist,我们明确摄像头由于工艺等多种原因,存在图片畸变的问题,那么,去畸变对于结果准确性就至关重要。
# catch the need extrinsic and intrinsic parameters
rvec_test=rvecs[21]
tvec_test=np.mat(tvecs[21])
mtx_test=np.hstack((mtx,np.mat([0,0,0]).T))
# calculate the KRT(单适性矩阵)
rmat,_ =cv2.Rodrigues(rvec_test)
RT=np.hstack((rmat,tvec_test))
KRT=np.vstack((RT,[0,0,0,1]))
# calculate the xw,yw
temp = KRT
temp=np.delete(temp,2,axis=1)
temp=np.delete(temp,3,axis=0)
temp=mtx*temp
tempI=temp.I
基于张正友单应性方法,通过矩阵操作,得到最后的变换矩阵temp(世界->图像)和tempI(图像->世界)。关于具体的理论见1,里面讲的很详细。
##########################一些注意事项,可跳过#####################################
理论不讲,但是实际操作中还是有不少坑的:首先标定板有效点应超过12个以上,最好20以上,虽然算法要求最低3x4个点,但是那么大的误差也是你不想要的;张正友标定法中的坐标变换矩阵是摄像头内参矩阵x透视变换矩阵x外参矩阵,而无论opencv还是matlab,标定结果mtx=内参矩阵*透视变换矩阵;顺带一提的是,单应性标定法之所以使用广泛的原因还是因为满秩,方阵,可逆,还是好用。。。
##########################################################################
总结
事实上无论是opencv还是matlab都可以完成摄像头标定,这里我直接说了opencv的,matlab更加方便,两个结果也是一样的,细微差别。但是对于鱼眼相机,matlab我没找到可用工具箱,大家有兴趣可以自己尝试一下喽~
如有侵权,请私戳,感谢~
[^1]:https://blog.csdn.net/u010128736/article/details/52860364
1 ↩︎