Python计算机视觉编程_04
相机标定
-
前言
-
1.相机投影模型
-
2.相机参数
-
- 2.1.内部参数
- 2.2.外部参数
-
3.相机标定
-
3.1.线性标定
-
3.2.非线性标定
-
4.代码实现
前言
之前我们进行了平面图像的特征点检测,特征点匹配,图像拼接,而近几年计算机视觉更偏向三维模型相关的内容,包括但不限于3D建模,3D点云,机器人(无人机等)的定位,自动驾驶等。
而在进行以上这些工程的时候,我们得先确定我们使用工具的具体信息,哪怕我们使用的各种仪器(摄像头,照相机等)都有提供对应的参数,但因为工艺问题或者使用不当,参数都会有一定的波动,这就需要我们进行相机标定来进行相机参数的确定。
1.相机投影模型
进行参数标定之前我们肯定要先明白相机是如何将三维的现实世界压缩成二维图片的。

图为简易的模型,事实上相机的投影应该是在光心center的左边,但为了便于理解和方便演示,我们将投影对称的放到光心的右边;很明显,根据初中的数学知识,现实空间的点可以等比的投影到平面上,这个比例为f(光心到投影平面的距离,即焦距)/Z(目标到光心的距离,即目标与相机之间的距离)
所以我们很显然可以得到这个公式
f/Z=x/X=y/Y

整理变化后,我们可以得到如图所示的式子,这里我们引入齐次坐标以解决统一计算和欧式空间中,无穷远点无法表示的问题,具体原理可以参考这篇文章:https://www.jianshu.com/p/0cce4406d5ad
2.相机参数
2.1.内部参数


u0,v0表示像主点P的偏移距离,f为焦距,实际上相机的内部参数还涉及到非正方形像素(像素点不是正方形),径向畸变(光线在远离透镜中心的地方比靠近中心的地方更加弯曲)
2.2.外部参数
相机相对于立体世界的坐标系的变化就是相机的外部参数(位移和旋转)

位移是简单粗暴的坐标原点之差(可以理解为相机中心在立体坐标系中的坐标),而旋转就有点复杂了


3.相机标定
同步标定内部参数和外部参数,一般包括两种策略:
1.光学标定: 利用已知的几何信息(如定长棋盘格)实
现参数求解。
2.自标定: 在静态场景中利用 structure from
motion估算参数。
而参数的计算方法也有两种:
1.线性回归(最小二乘法)
2.非线性优化
3.1.线性标定

我们通过空间中已知坐标的(特征)点 (Xi,Y i,Zi),以及它们在图像中的对应坐标 (ui,vi),直接估算 11 个待求解的内部和外部参数。


就像以前介绍过的那样,最小二乘法解11个未知参数至少需要6个匹配对(每个匹配对提供2个方程从而解得两个未知数)
线性标定的优缺点都很明显
优点:
1.所有的相机参数集中在一个矩阵中,便于求解
2.通过矩阵可以直接描述世界坐标中的三维点,到二维图像平面中点的映射关系
缺点:
1.无法直接得知具体的内参数和外参数
2.求解出的11个未知量,比待标定参数(9个)更多。带来了参数不独立/相关的问题
3.对噪声/误差敏感
4.高精度的标定板难以制作
3.2.非线性标定
采用概率的角度开看这个最小二乘问题,特征点投影方程为:

给定{(ui,vi)},标定参数矩阵 M 的似然函数为:

给定{(ui,vi)},标定参数矩阵 M 似然函数的对数为:

等价为:

相应求解策略: 牛顿方法、高斯-牛顿方法、Levenberg-Marquardt算法等就不具体展开了,感兴趣的同学自行百度搜索
4.代码实现
import cv2
import numpy as np
import glob
# 找棋盘格角点
# 阈值
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
#棋盘格模板规格
w = 11 #内角点个数,内角点是和其他格子连着的点
h = 8
# 世界坐标系中的棋盘格点,例如(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('D:/vscode/python/xiangjibiaoding/*.jpg') # 标定所用图像
for fname in images:
img = cv2.imread(fname)
#压缩图片,以便显示
img = cv2.resize(img,(1920,1080))
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) # 平移向量 # 外参数

输出参数解析:
ret: 7.813532046330992
mtx:#内参数矩阵(内参数指的是3D到2D投影过程中用到的投影矩阵):
mtx:
[[2.94978184e+03 0.00000000e+00 9.00429374e+02]
[0.00000000e+00 2.98557688e+03 5.65762116e+02]
[0.00000000e+00 0.00000000e+00 1.00000000e+00]]
dist:#畸变系数(畸变指的是镜头质量等原因导致的2D点的偏移)
[[-1.70527505e+00 4.16991642e+01 1.48626470e-01 -1.30328489e-02
-2.58033071e+01]]
rvecs:#旋转向量 (外参数-外参数矩阵是世界坐标系相对于摄像机坐标系的旋转平移关系)
[array([[-0.46180406],
[ 0.06415486],
[ 0.28836327]]), array([[-0.46775858],
[-0.25636269],
[-0.59481099]]), array([[-0.02274233],
[-0.08546825],
[ 0.02659502]]), array([[-0.9900263 ],
[ 0.95003289],
[ 1.27102692]]), array([[-0.53796888],
[ 0.61597059],
[ 1.55084495]]), array([[-0.10903875],
[-0.41527817],
[ 1.52876306]]), array([[-0.59273208],
[-0.45190321],
[-1.54642641]]), array([[-0.60990669],
[ 0.82084414],
[ 1.5205967 ]]), array([[-0.0767073 ],
[ 0.99479296],
[ 2.95260118]]), array([[-0.40079807],
[-0.77192656],
[-1.86045946]])]
tvecs:#平移向量 (外参数)
[array([[-5.7532102 ],
[-5.72047508],
[39.06836569]]), array([[-7.63654276],
[-1.46454734],
[42.12089291]]), array([[-6.17004012],
[-4.69221268],
[34.58200071]]), array([[ 3.18334773],
[-1.03012556],
[35.6642428 ]]), array([[ 3.08256458],
[-4.07942079],
[40.03881092]]), array([[ 6.65065396],
[-5.4647451 ],
[44.9550541 ]]), array([[-4.35688569],
[ 2.70636401],
[39.48894589]]), array([[-0.45213803],
[-3.82961859],
[47.21558383]]), array([[ 6.78472078],
[ 3.4384634 ],
[38.69076164]]), array([[ 1.15678223],
[ 1.08630664],
[43.60570658]])]
