所谓的相机标定就是将外界世界的坐标信息转化为计算机(自带相机/摄像头)可以理解的“距离”,将世界坐标系转换到相机坐标系。我们可以理解为从一个坐标系转换到另一个坐标系所需要的转换关系就是相机标定。简单地说:A=F(B),其中F()就是相机标定要做的工作。 通俗地讲,例如:我和你在世界坐标系(平常我们所说的距离)下的距离为0.5m,但是相机并不知道我和你到底有多近!因此我们可以利用相机标定的方式,使相机知道我和你之间的距离。
在图像测量过程以及机器视觉应用中,为确定空间物体表面某点的三维几何位置与其在图像中对应点之间的相互关系,必须建立相机成像的几何模型,这些几何模型参数就是相机参数。在大多数条件下这些参数必须通过实验与计算才能得到,这个求解参数的过程就称之为相机标定(或摄像机标定)。
相机标定主要是通过三维坐标点和其在图像中的成像位置,求取内置参数(fx,fy。u0,v0)和畸变参数(k1,k2,k3,p1,p2)的过程。 工业相机的成像主要才用小孔成像原理,成像示意图如图1所示,其中O为相机光心(即镜片的光心),成像平面1为相机的感光元件,成像平面2为成像平面1的中心翻转平面(为了数学建模方便)。相机标定过程中共有世界坐标系Ow、相机坐标系Oc(以镜头光心为原点)、图像物理坐标系(以相机光心为原点)和图像像素坐标系(以图像左上角坐标点为原点)
畸变(distortion)是对直线投影(rectilinear projection)的一种偏移。简单来说直线投影是场景内的一条直线投影到图片上也保持为一条直线。畸变简单来说就是一条直线投影到图片上不能保持为一条直线了,这是一种光学畸变(optical aberration),可能由于摄像机镜头的原因。
畸变一般可以分为:径向畸变、切向畸变
1、径向畸变来自于透镜形状
2、切向畸变来自于整个摄像机的组装过程
畸变还有其他类型的畸变,但是没有径向畸变、切向畸变显著
径向畸变
透过镜头边缘的光线很容易产生畸变,距离镜头中心越远,畸变程度越大,径向畸变示意图如下图所示。
此时,三维坐标点在图像中的二维坐标表示如下,其中,k1,k2,k3是径向畸变系数,
x,y是归一化以后的坐标,r2=x2+y^2 。(x_d,y_d),(x,y) 分别为畸变矫正后的像素归一化坐标,畸变校正前的像素归一化坐标。
切向畸变
切向畸变主要发生在相机传感器平面和镜头不平行的情况。因为有夹角,所以光透过镜头打到成像传感器时位置发生了变化。切向畸变原理图如下图所示。
优点:适用任意摄像机模型,标定精度高;
不足:需标定参照物,某些应用中难以实现;
传统相机标定法需要使用尺寸已知的标定物,通过建立标定物上坐标已知的点与其图像点之间的对应,利用一定的算法获得相机模型的内外参数。根据标定物的不同可分为三维标定物和平面型标定物。三维标定物可由单幅图像进行标定,标定精度较高,但高精密三维标定物的加工和维护较困难。平面型标定物比三维标定物制作简单,精度易保证,但标定时必须采用两幅或两幅以上的图像。传统相机标定法在标定过程中始终需要标定物,且标定物的制作精度会影响标定结果。同时有些场合不适合放置标定物也限制了传统相机标定法的应用。
优点:算法简单,往往能够获得线性解,故鲁棒性较高;
缺点:系统的成本高、实验设备昂贵、实验条件要求高,而且不适合于运动参数位置或无法控制的场合。
基于主动视觉的相机标定法是指已知相机的某些运动信息对相机进行标定。该方法不需要标定物,但需要控制相机做某些特殊运动,利用这种运动的特殊性可以计算出相机内部参数。
优点:自标定方法灵活性强,可对相机进行在线定标;
缺点:它是基于绝对二次曲线或曲面的方法,其算法鲁棒性差。
目前出现的自标定算法中主要是利用相机运动的约束。相机的运动约束条件太强,因此使得其在实际中并不实用。利用场景约束主要是利用场景中的一些平行或者正交的信息。其中空间平行线在相机图像平面上的交点被称为消失点,它是射影几何中一个非常重要的特征,所以很多学者研究了基于消失点的相机自标定方法。
基本步骤:
打印一张棋盘格A4纸张(黑白间距已知),并贴在一个平板上
针对棋盘格拍摄若干张图片(一般10-20张)
在图片中检测特征点(Harris特征)
利用解析解估算方法计算出5个内部参数,以及6个外部参数
根据极大似然估计策略,设计优化目标并实现参数的refinement
代码实现:
import cv2
import numpy as np
import glob
# 找棋盘格角点
# 阈值
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# 棋盘格模板规格
# 内角点个数,内角点是和其他格子连着的点
w = 9
h = 6
# 世界坐标系中的棋盘格点,例如(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('image/*.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('image/6.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('image/6_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))
标定所用棋盘格数据集:
运行结果:
畸变矫正:
前:
后:
从畸变矫正前后的图片对比情况,发现从我们的肉眼观察,并不能观察出太大的区别,说明我们自己的相机镜头不存在太大的畸变。
实验时遇到的问题
出现error: (-215:Assertion failed) nimages > 0 in function ‘cv::calibrateCameraRO’ 如下图所示
通过查找资料,知道出现该情况的原因可能是 图片中不包含棋盘格或者图片模糊等问题,导致查找棋盘格角点失败。
小结:
1.OpenCV中自带了提取棋盘格中内角点的函数:findChessboardCorners()。
2.有两个函数可以实现提取亚像素角点信息:cornerSubPix、find4QuadCornerSubpix。在提取棋盘格角点时两者的效果差不多,随便使用哪一个都行。
3.利用drawChessboardCorners函数画出角点。函数功能很简单,就是在图片中画出检测到的角点。
4.标定函数是calibrateCamera,也是相机标定的核心了。
实验中可以利用很多模板,但是棋盘格是最流行、最常见的图案设计。通常通过首先对摄像机图像进行二值化并找到四边形(黑色的棋盘区域)来找到棋盘角点的候选点。过滤步骤只保留那些满足特定大小标准的四边形,并组织在一个规则的网格结构中,网格结构的尺寸与用户指定的尺寸匹配。
在对标定板进行初步检测后,可以以非常高的精度确定角点位置。
在OpenCV中,整个棋盘必须在所有图像中可见才能被检测到。这通常使得从图像的边缘获取信息变得困难。这些区域通常是很好的信息来源,因为它们适当地约束了镜头失真模型。
在检测出棋盘格后,可以进行亚像素细化,以找到具有亚像素精度的鞍点。这利用了给定角点位置周围像素的确切灰度值,并且精度比整数像素位置所允许的精度要精确得多。