在图像测量过程以及机器视觉应用中,为确定空间物体表面某点的三维几何位置与其在图像中对应点之间的相互关系,必须建立相机成像的几何模型,这些几何模型参数就是相机参数。在大多数条件下这些参数必须通过实验与计算才能得到,这个求解参数的过程就称之为相机标定(或摄像机标定)。
相机标定主要是通过三维坐标点和其在图像中的成像位置,求取内置参数(fx,fy。u0,v0)和畸变参数(k1,k2,k3,p1,p2)的过程。 工业相机的成像主要才用小孔成像原理,成像示意图如图1所示,其中O为相机光心(即镜片的光心),成像平面1为相机的感光元件,成像平面2为成像平面1的中心翻转平面(为了数学建模方便)。相机标定过程中共有世界坐标系Ow、相机坐标系Oc(以镜头光心为原点)、图像物理坐标系(以相机光心为原点)和图像像素坐标系(以图像左上角坐标点为原点)。
焦距为相机光心距离成像平面的距离f,焦距越大(调整焦距时,调整成像平面位置),物体在成像平面上所占像素越多,物体越大,反之亦然。## 1.引入库
1、设P在世界坐标系Ow中的坐标为(Xw,Yw,Zw),P点从世界坐标系转换到相机坐标系Oc的转换公式如公式1所示,此时的[R、t]为世界坐标系相对于相机坐标系的旋转平移矩阵。
2、 相机坐标系和图像物理坐标系的转换关系如图3所示.,此时x,y的单位为mm。
3、 图像物理坐标系-图像像素坐标系的转换关系如图4所示,其中dx,dy为像素水平,竖直方向的宽度。
4、世界坐标系-图像像素坐标系的转换关系
图像传感器像原尺寸在制造过程可能不是正方形,同时由于安装工艺的限制,图像传感器成像平面可能存在歪斜,因此需要考虑这些影响因素,传感器歪斜和不是正方形主要对相机x和y方向的焦距产生影响。
小孔成像模型虽然充分考虑了相机内部参数对成像的影响,但没有考虑成像系统另一个重要的部分,镜头。任何镜头都存在不同程度的畸变,不同类型的镜头用到的畸变模型也不相同。根据镜头制造和成像的物理特性,普通镜头主要考虑径向畸变和切向畸变,且畸变模型都可以用多项式来近似。
5.1 径向畸变
透过镜头边缘的光线很容易产生畸变,距离镜头中心越远,畸变程度越大,径向畸变示意图如图5.1所示。
此时,三维坐标点在图像中的二维坐标表示如下,其中,k1,k2,k3是径向畸变系数,
x,y是归一化以后的坐标,r2=x2+y^2 。(x_d,y_d),(x,y) 分别为畸变矫正后的像素归一化坐标,畸变校正前的像素归一化坐标。
5.2 切向畸变
切向畸变主要发生在相机传感器平面和镜头不平行的情况。因为有夹角,所以光透过镜头打到成像传感器时位置发生了变化。切向畸变原理图如图5.2所示。
此时,三维坐标点在图像中的二维坐标表示如下,其中p1和p2为切向畸变系数,x,y是归一化以后的坐标,r^2 = x^2 + y^2 。
import cv2 as cv
import numpy as np
import glob
# 棋盘格内角点
w = 7
h = 10
# 每个棋盘小格的实际长度,单位为mm
chesslength = 100
# 计算亚像素角点时终止迭代阈值,最大计算次数30次,最大误差容限0.001
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# 准备格式如 (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)的3d角点
objp = np.zeros((w*h, 3), np.float32)
objp[:,:2] = np.mgrid[0:w,0:h].T.reshape(-1,2)
# Arrays to store object points and image points from all the images.
objpoints = [] # 存储3D角点
imgpoints = [] # 存储2D角点
images = glob.glob('*.jpg')
for fname in images:
img = cv.imread(fname) # 读取图片的格式为[h,w,c]
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 计算棋盘格角点
ret, corners = cv.findChessboardCorners(gray, (w, h), None)
print(ret)
# If found, add object points, image points (after refining them)
if ret == True:
objpoints.append(objp * chesslength)
# 角点精细化,其中corners为初始计算的角点向量
corners = cv.cornerSubPix(gray, corners, (11, 11), (-1,-1), criteria)
imgpoints.append(corners)
# 绘制角点并展示
cv.drawChessboardCorners(img, (w,h), corners, ret)
cv.imshow('img', img)
cv.waitKey(0)
cv.destroyAllWindows()
# 相机标定,依次返回标定结果、内置参数矩阵、畸变参数、旋转向量、平移向量
ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(objpoints, imgpoints, (img.shape[1],img.shape[0]), None, None)
print("ret:", ret) # ret为bool值
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) # 平移向量,外参数
# 计算重投影误差
mean_error = 0
for i in range(len(objpoints)):
imgpoints2, _ = cv.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
error = cv.norm(imgpoints[i], imgpoints2, cv.NORM_L2)/len(imgpoints2)
mean_error += error
print("total error: {}".format(mean_error/len(objpoints)))