1.用一个带有小孔的板遮挡在屏幕与物之间,屏幕上就会形成物的倒像,我们把这样的现象叫小孔成像。前后移动中间的板,屏幕上像的大小也会随之发生变化,这种现象说明了光沿直线传播的性质。
2.相机将三维世界中的坐标点(单位:米)映射到二维图像平面(单位:像素)的过程能够用一个几何模型来描述,其中最简单的称为针孔相机模型 (pinhole camera model),其框架如下图所示。
很显然,坐标的转换便是一个降维的过程。为了确定这种三维到二维的映射关系,我们进行如下对原理的讨论。
1.世界坐标系(world coordinate)(xw,yw,zw) ,也称为测量坐标系,是一个三维直角坐标系,以其为基准可以描述相机和待测物体的空间位置。世界坐标系的位置可以根据实际情况自由确定。
2.相机坐标系(camera coordinate)(xc,yc,zc),也是一个三维直角坐标系,原点位于镜头光心处,x、y轴分别与相面的两边平行,z轴为镜头光轴,与像平面垂直。
3.像素坐标系 uov是一个二维直角坐标系,反映了相机中像素的排列情况。原点o位于图像的左上角,u轴、v轴分别于像面的两边平行。像素坐标系中坐标轴的单位是像素(整数)。
4.图像坐标系 XOY,其坐标轴的单位通常为毫米(mm),原点是相机光轴与相面的交点(称为主点),即图像的中心点,X轴、Y轴分别与u轴、v轴平行。故两个坐标系实际是平移关系,即可以通过平移就可得到。
5.世界坐标系到相机坐标系的标定:
第一步是从世界坐标系到相机坐标系的变换,这一步是三维点到三维点的变换,属于刚体变换:即物体不会发生形变,只需要进行旋转和平移。
R:表示旋转矩阵(相机外参)
T:表示偏移向量(相机外参)
6.相机坐标系到图像坐标系的标定:
第二步是相机坐标系转为图像坐标系,这一步是三维点到二维点的变换,即从3D转换到2D.属于透视投影关系。
得到矩阵公式:
由于此时投影点p的单位还是mm,并不是像素pixel,需要进一步转换到像素坐标系。
7.图像坐标系到像素坐标系的标定:
像素坐标系和图像坐标系都在成像平面上,只是各自的原点和度量单位不一样。图像坐标系的原点为相机光轴与成像平面的交点,通常情况下是成像平面的中点或者叫principal point。图像坐标系的单位为mm,属于物理单位,而像素坐标系的单位是pixel,我们平常描述一个像素点都是几行几列。所以这两者之间的转换如下:
其中dx和dy表示每一列和每一行分别代表多少mm,即1pixel = dx mm
于是由以上四个坐标系的转换得到如下式子:
右式结果的第一个矩阵为相机内参,第二个矩阵为相机外参,可由张有正标定法获取(戳这里查看张有正标定有关信息)
由于光学透镜固有的透视失真导致相机镜头畸变的现象我们统称为相机畸变。此处我们着重讨论图像径向畸变:
产生原因:
畸变的本质是光学系统在不同视场由于镜头屈光度与光阑位置不同引起像高放大率不同导致的。
分类:
1.桶状畸变:又称“桶形失真”,视野中光轴中心区域的放大率远大于边缘区域的放大率,常出现在广角镜头和鱼眼镜头中。
2.枕形畸变:又称“鞍形形变”,视野中边缘区域的放大率远大于光轴中心区域的放大率,常用在远摄镜头中。
矫正:
调节公式为:
由于日常生活中在进行拍摄时,相机的位置是不断变化的,所以会有相机旋转与平移对拍摄的影响。所以将上面坐标转换得到的x ~ K[I|0]X矩阵进行修改,得到x ~ K[R|t]X矩阵(外部参数矩阵):
1.相机旋转包括三个方向:X,Y,Z,(即pitch,yaw,roll)
2.将三个方向上的旋转相乘,得到旋转矩阵R:
同步标定内部参数和外部参数,一般包括两种策略s:
线性回归(最小二乘)单单考虑坐标的转换:
通过空间中已知的坐标的(特征)点(Xi,Yi,Zi),以及它们在图像中的对应坐标(ui,vi),直接估算11个待求解的内部和外部参数。
表示为矩阵形式:
即可用最小二乘法求解。
(若要回顾单硬性变换,请点击这里)
给定超定方程:Ax = 0 后,x的解即为A^TA最小特征值对应的特征向量。
标定参数线性回归的优缺点:
优点:
缺点:
用概率的角度去看最小二乘问题。
(1)计算单应性矩阵H
设三维世界坐标的点为X=[X,Y,Z,1]^T
二维相机平面像素坐标为m=[u,v,1]^T
所以标定用的棋盘格平面得到图像平面的单应性关系为:
其中,s为尺度因子,K为摄像机内参数,R是旋转矩阵,t为平移向量,令:
s做为齐次坐标来说,不会改变齐次坐标值。将世界坐标系构造在棋盘格平面上,令棋盘格平面为Z=0的平面。则:
将K[r1,r2,t]叫做单应性矩阵H,即:
H是一个齐次矩阵,所以有八个未知数,至少需要八个方程,每对对应点能提供两个方程,所以至少需要四个对应点,就可以算出世界平面到图像平面的单应性矩阵H。
(2)计算内参数矩阵
可以看出,B是一个对称阵,所以B的有效元素为6个,让这6个元素写成向量b,即:
利用约束条件可以得到:
通过上式,我们至少需要三幅包含棋盘格的图像,可以计算得到B,然后通过cholesky分解,得到相机的内参数矩阵K。
(3)计算外参数矩阵
由之前的推导可得:
(4)最大似然估计
由于可能存在高斯噪声,所以使用最大似然估计进行优化。这里使用的是多参数非线性系统优化问题的Levenberg-Marquardt算法进行迭代优化求最优解。
(5)径向畸变估计
数学表达式:
化作矩阵形式:
得到:
由此计算得到畸变系数k
综上,我们得到了相机的内参、外参和畸变系数。
import cv2
import numpy as np
import glob
# 找棋盘格角点
# 阈值
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
#棋盘格模板规格
w = 14 #内角点个数,内角点是和其他格子连着的点
h = 9
# 世界坐标系中的棋盘格点,例如(0,0,0), (1,0,0), (2,0,0) ....,(8,5,0),去掉Z坐标,记为二维矩阵
objp = np.zeros((w*h,3), np.float32)
objp[:,:2] = np.mgrid[0:w,0:h].T.reshape(-1,2)
# 储存棋盘格角点的世界坐标和图像坐标对
objpoints = [] # 在世界坐标系中的三维点
imgpoints = [] # 在图像平面的二维点
images = glob.glob('picture/*.jpg')
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# 找到棋盘格角点
# 棋盘图像(8位灰度或彩色图像) 棋盘尺寸 存放角点的位置
ret, corners = cv2.findChessboardCorners(gray, (w,h),None)
# 如果找到足够点对,将其存储起来
if ret == True:
# 角点精确检测
# 输入图像 角点初始坐标 搜索窗口为2*winsize+1 死区 求角点的迭代终止条件
cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
objpoints.append(objp)
imgpoints.append(corners)
# 将角点在图像上显示
cv2.drawChessboardCorners(img, (w,h), corners, ret)
cv2.imshow('findCorners',img)
cv2.waitKey(1000)
cv2.destroyAllWindows()
#标定、去畸变
# 输入:世界坐标系里的位置 像素坐标 图像的像素尺寸大小 3*3矩阵,相机内参数矩阵 畸变矩阵
# 输出:标定结果 相机的内参数矩阵 畸变系数 旋转矩阵 平移向量
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
# mtx:内参数矩阵
# dist:畸变系数
# rvecs:旋转向量 (外参数)
# tvecs :平移向量 (外参数)
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) # 平移向量 # 外参数
# 去畸变
img2 = cv2.imread('picture/5_d.jpg')
h,w = img2.shape[:2]
# 我们已经得到了相机内参和畸变系数,在将图像去畸变之前,
# 我们还可以使用cv.getOptimalNewCameraMatrix()优化内参数和畸变系数,
# 通过设定自由自由比例因子alpha。当alpha设为0的时候,
# 将会返回一个剪裁过的将去畸变后不想要的像素去掉的内参数和畸变系数;
# 当alpha设为1的时候,将会返回一个包含额外黑色像素点的内参数和畸变系数,并返回一个ROI用于将其剪裁掉
newcameramtx, roi=cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),0,(w,h)) # 自由比例参数
dst = cv2.undistort(img2, mtx, dist, None, newcameramtx)
# 根据前面ROI区域裁剪图片
x,y,w,h = roi
dst = dst[y:y+h, x:x+w]
cv2.imwrite('calibresult.jpg',dst)
# 反投影误差
# 通过反投影误差,我们可以来评估结果的好坏。越接近0,说明结果越理想。
# 通过之前计算的内参数矩阵、畸变系数、旋转矩阵和平移向量,使用cv2.projectPoints()计算三维点到二维图像的投影,
# 然后计算反投影得到的点与图像上检测到的点的误差,最后计算一个对于所有标定图像的平均误差,这个值就是反投影误差。
total_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)
total_error += error
print (("total error: "), total_error/len(objpoints))
(1)输入数据集:
输入数据集是一组来自严格平面的棋盘格,用自己的手机拍摄了14张,故计算手机相机的内外参数:
(2)输出角点检测结果:
OpenCV中自带了提取棋盘格中内角点的函数:findChessboardCorners()。这个函数的功能是确定输入图像中是否有棋盘格图案,并检测棋盘格的内角点。如果所有的内角点都找到了,那么函数返回一个非0值;如果没有找到所有的内角点,就会返回0。
因为我们使用的是有14×9的内格子,所以我们在设置w和h时分别填写14和9。函数返回每一一个角点,如果匹配到了模式,它将返回是True。这些角点将按一定顺序标注出来(从左到右,从上到下)。
(3)相机参数:
畸变系数dist:
旋转向量rvecs:
rvecs: [array([[-0.15864377],
[ 0.06980558],
[ 0.16090932]]), array([[-0.26484807],
[-0.17818488],
[ 0.03831059]]), array([[-0.24593887],
[ 0.33735992],
[-0.00986063]]), array([[-0.00906031],
[ 0.01517562],
[ 0.06098918]]), array([[-0.28737887],
[ 0.03681998],
[ 0.06031694]]), array([[0.05507092],
[0.02459088],
[0.01313248]]), array([[-0.29605163],
[ 0.00182411],
[ 0.06790322]]), array([[-0.09594883],
[ 0.03165031],
[ 0.12594066]]), array([[-0.09307569],
[ 0.0338839 ],
[-0.04746944]]), array([[-0.07165137],
[ 0.39360228],
[ 0.00174199]]), array([[-0.1892262 ],
[-0.31510907],
[-0.10536917]]), array([[-0.59337799],
[ 0.23014183],
[ 0.07634885]]), array([[ 0.12684867],
[-0.00313582],
[ 0.04928179]]), array([[-0.22086014],
[ 0.14914275],
[ 0.04814694]])] ```
平移向量tvecs:
tvecs: [array([[-4.59692988],
[-4.56254851],
[17.32213156]]), array([[-4.11514485],
[-4.60415106],
[15.43947814]]), array([[-7.56009876],
[-3.55997914],
[17.8127442 ]]), array([[-6.03322459],
[-3.22313036],
[16.17444347]]), array([[-4.73783498],
[-4.04532552],
[17.60559336]]), array([[-5.970035 ],
[-3.47727784],
[14.78357089]]), array([[-5.75140128],
[-3.89551428],
[17.74619271]]), array([[-4.40524727],
[-3.66910702],
[19.77897714]]), array([[-5.21074683],
[-3.19815475],
[16.82278397]]), array([[-6.55567674],
[-3.42179644],
[18.35279753]]), array([[-4.43566524],
[-3.60398259],
[15.54220295]]), array([[-5.6594958 ],
[-3.52781208],
[19.56802292]]), array([[-5.33115856],
[-2.88742627],
[15.62377258]]), array([[-4.96680286],
[-4.09675094],
[17.95433939]])] ```
(4)注意:
畸变图像校正
坐标系转换