目录
相机标定在机器人视觉和畸变校正上都是很关键的一部分,接下来用张正友相机标定法标定我的手机(Vivo xpaly5A)后置摄像头。
首先先简单的了解一下相机标定的原理。
摄像机标定(Camera calibration)简单来说是从世界坐标系换到图像坐标系的过程,也就是求最终的投影矩阵 P 的过程。
一般来说,标定的过程分为两个部分:
C C C 点表示camera centre,即相机的中心点,也是相机坐标系的中心点;
Z Z Z 轴表示principal axis,即相机的主轴;
p p p 点所在的平面表示image plane,即相机的像平面,也就是图片坐标系所在的二维平面;
p p p 点表示principal point,即主点,主轴与像平面相交的点;
C C C 点到 p p p 点的距离,也就是右边图中的 f f f 表示focal length,即相机的焦距;
像平面上的 x x x 和 y y y 坐标轴是与相机坐标系上的 X X X 和 Y Y Y 坐标轴互相平行的;
相机坐标系是以 X X X, Y Y Y, Z Z Z三个轴组成的且原点在 C C C 点,度量值为米(m);
像平面坐标系是以 x x x, y y y(小写)两个轴组成的且原点在 p p p 点,度量值为米(m);
图像坐标系一般指图片相对坐标系,在这里可以认为和像平面坐标系在一个平面上,不过原点是在图片的角上,而且度量值为像素的个数(pixel);
通过对上面坐标系的介绍,可以得到以下的转换公式:
加上偏移量:
如上图所示,其中主点 p p p 是像平面坐标系的原点,但在图像坐标系中的位置为 ( p x , p y ) (p_{x},p_{y}) (px,py),在这里,图形坐标系的原点是图片的左下角,所以可以得到:
把这个换成矩阵计算:
整理一下:
最后,可以得到矩阵K,就是相机内参:
投影矩阵P(在这里可以认为旋转矩阵 R 为单位矩阵 I,平移矩阵 t 都为0):
最终的转换式:
从上面两个转换的过程,我们可以得到从世界坐标轴转换到图像的过程可以把投影矩阵P表示为:
在这里, K K K 一般称为相机内参(intrinsic parameters),描述了相机的内部参数,包括焦距 f f f、主点 p p p 的位置、以及像素与真实环境的大小比例等,这个是固有属性,是提供好的; R R R 和 t t t 称为相机外参(extrinsic parameters), R R R 在这里是旋转矩阵,可以转换为三维的旋转向量,分别表示绕 x x x, y y y, z z z 三个轴的旋转角度, t t t 目前就是一个平移向量,分别表示在 x x x, y y y, z z z 三个方向上的平移量。
在几何光学和阴极射线管(CRT)显示中,畸变(distortion) 是对直线投影(rectilinear projection)的一种偏移。简单来说直线投影是场景内的一条直线投影到图片上也保持为一条直线。那畸变简单来说就是一条直线投影到图片上不能保持为一条直线了,这是一种光学畸变(optical aberration)。
畸变一般可以分为两大类,包括径向畸变和切向畸变。主要的一般径向畸变有时也会有轻微的切向畸变。
该部分参考:https://blog.csdn.net/honyniu/article/details/51004397
了解完一定原理之后,开始实践一下。
首先,要明确通过相机标记我们需要得到以下的参数:
相机标定的流程可以简单的表述为:
主要准备图片即可,10张左右的标定图片,黑白棋盘格即可。
可以给图片进行一定的缩小,加快运算速度。
拍摄过程中,如果方格纸是横着摆的就一直横着摆着拍,因为横纵角点个数是要提前设置好的。
提取角点:
使用函数:findChessboardCorners(image,(w,h),None);
第一个参数为图片,第二个为图片横纵角点的个数。
找寻亚像素角点:cornerSubPix(gray, corners, (winsize, winsize), (-1, -1), criteria);
winsize为搜索窗口边长的一半。
zeroZone:搜索区域中间的dead region边长的一半,有时用于避免自相关矩阵的奇异性。如果值设为(-1,-1)则表示没有这个区域。
criteria:角点精准化迭代过程的终止条件。也就是当迭代次数超过criteria.maxCount,或者角点位置变化小于criteria.epsilon时,停止迭代过程。
可以使用该函数把角点画出来:drawChessboardCorners(image, (w, h), corners, ret)
;
其中corners和ret为第一个函数的输出值。
效果如下:
标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points, size, None, None);
其中:
obj_points = [] # 存储3D点
img_points = [] # 存储2D点
mtx 内参数矩阵
dist 畸变系数
rvecs 旋转向量
tvecs 平移向量
准备了9张图片,可以有两张无论如何也不能完成标定,这两张和其他图片并没有很大的差别,角度光线也都很正常。
畸变矫正
左图为矫正过的图片,右边为原图
通过相机标定得到了,vivo xplay5A的后置摄像头内置参数矩阵如下:
也得到了每张图片的外参数,并且实现了畸变矫正,虽然效果极其不明显。
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((6 * 8, 3), np.float32)
objp[:, :2] = np.mgrid[0:8, 0:6].T.reshape(-1, 2) # 将世界坐标系建在标定板上,所有点的Z坐标全部为0,所以只需要赋值x和y
obj_points = [] # 存储3D点
img_points = [] # 存储2D点
images = glob.glob("D:/pycharmfile/biaoding/image/*.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, (8, 6), 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, (8, 6), corners, ret) # 记住,OpenCV的绘制函数一般无返回值
i+=1;
cv2.imwrite('conimg'+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:\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 ) # 平移向量 # 外参数
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)
参考:https://blog.csdn.net/firemicrocosm/article/details/48594897#
调试问题:
The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()