目标
获取相机内参,畸变参数,从而进行相机校准,为手眼标定做准备。
1.相机成像原理
相机将三维世界中的坐标点映射到二维图像平面的过程能够抽象成用一个几何模型进行描述。这个模型有很多种,其中最简单的称为针孔模型。
现实世界中源于某个物体的光线穿过小孔,会在摄像机成像平面上形成一幅倒立的图像。
- P是现实生活中的一点,其所在的坐标系称为世界坐标系。以其为基准可以描述相机和待测物体的空间位置。世界坐标系的位置可以根据实际情况自由确定。
- 以光心O为原点的坐标系XOY,称为相机坐标系。原点位于镜头光心处(凸透镜则是位于透镜中央),x、y轴与相面的两边平行,z轴为镜头光轴,与像平面垂直。
- 成像平面中点为O′,坐标系O′X′Y′称为图像坐标系。光心O的投影为坐标原点。
- uv:像素坐标系,原点为图像右上角,单位为pixel。像素坐标(u,v)是该像素在数组中的列数和行数。视觉识别定位的时候的坐标,是图像坐标系的,比如YOLOv5.
2.坐标变换
2.1世界坐标系到相机坐标系
从世界坐标系到相机坐标系,涉及到旋转和平移。R为旋转矩阵,T为偏移向量。因此只要知道R和T就知道两坐标系之间的联系。推到过程可看知乎相关文章
备注:左边的矩阵增加1个维度可以更加方便的表示,可以参考博客。最终是一个4x4的矩阵与一个4x1的矩阵相乘。
2.2相机坐标系到图像坐标系
从相机坐标系到图像坐标系,属于透视投影关系,满足三角形的相似定理。小孔成像是中心对称关系,实像是倒立的,而我们在显示器中看到的图像却是正立的(显示器显示的图像其实就是对称的虚像)。因此用对称的虚像进行计算更加方便。
我们可以把小孔成像的像平面放在物体同侧,结果如下:
在已知相机坐标(Xc,Yc,Zc),并且知道焦距f的时候,可以得到图像坐标
注意:realsense是定焦设备,之所以可以在一定范围内都清楚,是因为有一定的景深
2.3图像坐标系到像素坐标系
像素坐标系和图像坐标系都在成像平面上,只是各自的原点和度量单位不一样。图像坐标系的单位是mm,属于物理单位,而像素坐标系的单位是pixel,我们平常描述一个像素点都是几行几列。
dx和dy表示每一列和每一行分别代表多少mm。
2.4 从世界坐标到像素坐标的转化
- 第一个矩阵中的四个参数称之为相机内参,因为他们只与相机有关。为了以下方便叙述,此处称其为M1。
- 第二个矩阵的两个参数称之为相机外参,只要相机和世界坐标系的相对位置发生改变,它们就会改变。
2.5 考虑畸变
以上转换关系是理想针孔模型的,由于通过针孔的光线少,摄像机曝光太慢,在实际使用中均采用透镜,可以使图像生成迅速,但代价是引入了畸变。径向畸变包括桶形畸变和枕形畸变两种。以下分别是枕形和桶形畸变示意图:
径向畸变的转换公式:
切向畸变是由于透镜本身与相机传感器平面(像平面)或图像平面不平行而产生的,这种情况多是由于透镜被粘贴到镜头模组上的安装偏差导致。切向畸变公式:
将径向畸变和切向畸变放在一起考虑,可以得到下式:
2.6 小结
所以对于镜头畸变一共有 5 个参数 k1, k2, k3, p1, p2 需要校准。opencv 输出的即便参数顺序是 k1, k2, p1, p2, k3 因为 k3 没那么重要。
进行摄像机标定的目的:求出相机的内参矩阵,以及畸变参数[k1,k2,k3,p1,p2]。
相机标定的外参一般没有用
3.opencv标定
3.1原理
通过一组三维点(Xw,Yw,Zw)以及这些点对应的像素位置(u,v)来计算相机参数。
- 我们的世界坐标是由以下这个棋盘格图案固定的,固定棋盘格,使其处于一平面上。上面的任何一角都可以选择到世界坐标系的原点,而棋盘格的间距固定,确定了原点之后其余的点的坐标也可以确定。棋盘上所有的点的Zw=0。
- 保持棋盘格静止,从多个角度拍摄棋盘格
- 利用findChessboardCorners提取角点,cornersubix优化角点以提高精度。(这里得到了一系列的点的像素坐标以及世界坐标)
- 利用caliberecamera函数,进行计算,得到相机内参以及每张图片的旋转、平移矩阵
3.2实践
拍摄多个角度的棋盘格。网盘下载,提取码为:vfok。
完整代码如下所示:
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 * 9, 3), np.float32)
objp[:, :2] = np.mgrid[0:9, 0:6].T.reshape(-1, 2) # 将世界坐标系建在标定板上,所有点的Z坐标全部为0,所以只需要赋值x和y
obj_points = [] # 存储3D点
img_points = [] # 存储2D点
images = glob.glob("D:/company/py/pythonProject/image/*.jpg") # 标定图片的地址
for fname in images:
img = cv2.imread(fname)
cv2.imshow('img',img)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
size = gray.shape[::-1]
ret, corners = cv2.findChessboardCorners(gray, (6, 9), None)
print(ret)
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的绘制函数一般无返回值
cv2.imshow('img', img)
cv2.waitKey(2000)
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("-----------------------------------------------------")
运行结果:
看代码中的注释便可以知道具体的返回值的意义。
4.matlab标定
matlab的calibration toolbox 高度封装,能直观的看重投影误差等信息,因此更推荐用matlab进行相机标定。
4.1参数说明
radial coefficients:为径向畸变系数。在相机标定中,径向畸变去拟合的时候是用多项式去拟合的。系数越多,拟合能力越强,但是不是说越多越好。鼠标停留在上面的时候,有如下提示:
仅仅适用于广角相机,因此我这里只需要选择2coefficients即可。
skew偏斜系数:当x轴y轴不垂直的时候,需要这个。同理,当鼠标放上去的时候,提示大多数现代相机不存在这个问题,这里我不选择
tangential distortion切向畸变:这个一般也不勾选
4.2结果评价
可以剔除掉误差比较大的图:然后再次进行标定
对于50万像素以下的分辨率,重投影误差需要在0.1以下才比较好,因此我这里精度还不够。我用的是480x640<50w。目前基本在1.2左右。需要更多的拍摄技巧去完善
4.3注意
opencv和matlab获取的内参矩阵互为转置
5.标定板拍摄规范
- 保持相机拍摄时候的稳定
- 标定板的位姿对标定结果影响比标定照片数目的影响大得多;尽量多个角度,并且角度变化要大,相对于相机要有正视、仰视、俯视、左斜视和右斜视等;
- 标定板一定要平整
- 一般要让标定板占据整张图像的一半左右面积
- 采集图像数量不应太少,建议20+为宜;