摄像机标定
坐标转换
世界坐标系(3D)——>相机坐标系(3D)——>图像物理坐标系(2D)——>图像像素坐标系(2D)
R代表旋转矩阵,T代表平移矩阵
f为摄像机的焦距
每个像素点在图像坐标系x轴、y轴方向的尺寸为:dx、dy
除了在四个坐标系之间的转化外,仍要考虑镜头畸变的影响
透镜的畸变主要分为径向畸变和切向畸变(还有薄透镜畸变等等,但都没有径向和切向畸变影响显著,所以我们在这里只考虑径向和切向畸变)。
径向畸变
是由于透镜形状的制造工艺导致。且越向透镜边缘移动径向畸变越严重。实际情况中我们常用r=0处的泰勒级数展开的前几项来近似描述径向畸变。矫正径向畸变前后的坐标关系为:
•xcorrected = x(1+k1r2+k2r4+k3r6)
•ycorrected = y(1+k1r2+k2r4+k3r6)
由此可知对于径向畸变,我们有3个畸变参数需要求解。
切向畸变
是由于透镜和成像平面不平行。切向畸变需要两个额外的畸变参数来描述,矫正前后的坐标关系为:
•xcorrected = x + [ 2p1y + p2 (r2 + 2x2) ]
•ycorrected = y + [ 2p2x + p1 (r2 + 2y2) ]
由此可知对于切向畸变,我们有2个畸变参数需要求解。
综上,我们一共需要5个畸变参数(k1、k2、k3、p1和p2 )来描述透镜畸变。
相机内参和外参
我们将之前的坐标变换合并之后能够得到如上的式子,左边的矩阵代表了摄像机的内参,右边的矩阵代表了摄像机的外参。
标定方法
传统相机标定法需要使用尺寸已知的标定物,通过建立标定物上坐标已知的点与其图像点之间的对应,利用一定的算法获得相机模型的内外参数
有了以上的理论知识,我们至少需要 10张 图案模式来进行摄像机标定。为了便于理解,我们可以认为仅有一张棋盘图像。重要的是在进行摄 像机标定时我们要输入一组 3D 真实世界中的点以及与它们对应 2D 图像中的点。2D 图像的点可以在图像中很容易的找到。(这些点在图像中的位置是棋盘上两个黑色方块相互接触的地方)
那么真实世界中的 3D 的点呢?这些图像来源与静态摄像机和棋盘不同的摆放位置和朝向。所以我们需要知道(X,Y,Z)的值。但是为了简单,我 们可以说棋盘在 XY 平面是静止的,(所以 Z 总是等于 0)摄像机在围着棋 盘移动。这种假设让我们只需要知道 X,Y 的值就可以了
于是我用手机围绕电脑拍了13张不同角度的棋盘照片,作为标定物
代码
计算相机内外参数
1.1、寻找标定物
我们要使用函数cv2.findChessboardCorners()。我们还需要传入图案的类型,我的棋盘图是5*7的格式。它会返 回角点,如果得到图像的话返回值类型(Retl)就会是 True。这些角点会按顺序排列(从左到右,从上到下)。
ret, corners = cv2.findChessboardCorners(image, patternSize, corners, flags)
1.2、提高准确度
corners2 = cv2.cornerSubPix(gray, corners, (5,5), (-1,-1), criteria)
1.3、绘制角点
image = cv2.drawChessboardCorners(image, patternSize, corners, patternWasFound)
1.4、标定
在得到了这些对象点和图像点之后,我们已经准备好来做摄像机标定了。我们要使用的函数是 cv2.calibrateCamera()。它会返回摄像机矩阵,畸变系数,旋转和变换向量等。
ret, cameraMatrix, distCoeffs, rvecs, tvecs = cv2.calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs, flags, criteria)
畸变矫正
现在我们找到我们想要的东西了,我们可以找到一幅图像来对他进行校正了。不过在那之前我们可以使用 从函数 cv2.getOptimalNewCameraMatrix() 得到的*缩放系数对摄 像机矩阵进行优化。如果缩放系数 alpha = 0,返回的非畸变图像会带有最少量 的不想要的像素。它甚至有可能在图像角点去除一些像素。如果 alpha = 1,所 有的像素都会被返回,还有一些黑图像。它还会返回一个 ROI 图像,我们可以 用来对结果进行裁剪。
img = cv2.imread(fname)
h, w = img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))
# undistort
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
# crop the image
x, y, w, h = roi
dst = dst[y:y+h, x:x+w]
cv2.imwrite('calibresult.png', dst)
完整代码
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((5*7,3), np.float32)
objp[:,:2] = np.mgrid[0:7,0:5].T.reshape(-1,2) # 将世界坐标系建在标定板上,所有点的Z坐标全部为0,所以只需要赋值x和y
obj_points = [] # 存储3D点
img_points = [] # 存储2D点
images = glob.glob('D:/sxjbd/*')
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,5), None)
if ret:
obj_points.append(objp)
corners2 = cv2.cornerSubPix(gray, corners, (5,5), (-1,-1), criteria) # 在原角点的基础上寻找亚像素角点
if [corners2]:
img_points.append(corners2)
else:
img_points.append(corners)
cv2.drawChessboardCorners(img, (7,5), corners, ret) # 记住,OpenCV的绘制函数一般无返回值
cv2.imwrite(fname, img)
cv2.waitKey(50)
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("-----------------------------------------------------")
# 畸变校正
l = len(images)
for i in range(l):
img = cv2.imread(images[i])
h, w = img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h))
print(newcameramtx)
dst = cv2.undistort(img,mtx,dist,None,newcameramtx)
x,y,w,h = roi
dst1 = dst[y:y+h,x:x+w]
imgName="targetPic"+str(i)+".jpg"
cv2.imwrite(imgName, dst1)
print("dst的大小为:", dst1.shape)
原始图像
标定后图像