相机标定(Camera calibration)及实践

在机器视觉领域,相机的标定是一个关键的环节,它决定了机器视觉系统能否有效的定位,能否有效的计算目标物。相机的标定基本上可以分为两种,第一种是相机的自标定;第二种是依赖于标定参照物的标定方法。前者是相机拍摄周围物体,通过数字图像处理的方法和相关的几何计算得到相机参数,但是这种方法标定的结果误差较大,不适合于高精度应用场合。后者是通过标定参照物,由相机成像,并通过数字图像处理的方法,以及后期的空间算术运算计算相机的内参和外参。这种方法标定的精度高,适用于对精度要求高的应用场合,本文主要针对后者进行描述。

标定的概念:在图像测量过程以及机器视觉应用中,为确定空间物体表面某点的三维几何位置与其在图像中对应点之间的相互关系,必须建立相机成像的几何模型,这些几何模型参数就是相机参数。在大多数条件下这些参数必须通过实验与计算才能得到,这个求解参数(内参、外参、畸变参数)的过程就称之为相机标定(或摄像机标定)。无论是在图像测量或者机器视觉应用中,相机参数的标定都是非常关键的环节,其标定结果的精度及算法的稳定性直接影响相机工作产生结果的准确性。因此,做好相机标定是做好后续工作的前提,提高标定精度是科研工作的重点所在。畸变(distortion)是对直线投影(rectilinear projection)的一种偏移。简单来说直线投影是场景内的一条直线投影到图片上也保持为一条直线。畸变简单来说就是一条直线投影到图片上不能保持为一条直线了,这是一种光学畸变(optical aberration),可能由于摄像机镜头的原因。

相机在计算机视觉应用中起着重要作用,作为图像数据来源,影响着后续各个处理步骤。成像模型就是用数学公式刻画整个成像过程,即被拍摄物体空间点到照片成像点之间的几何变换关系。

总体上,相机成像可以分为四个步骤:刚体变换、透视投影、畸变校正和数字化图像。

一、刚体变换(从世界坐标系到相机坐标系)

世界坐标系(world coordinate)(xw,yw,zw),也称为测量坐标系,是一个三维直角坐标系,以其为基准可以描述相机和待测物体的空间位置。世界坐标系的位置可以根据实际情况自由确定。

相机坐标系(camera coordinate)(xc,yc,zc),也是一个三维直角坐标系,原点位于镜头光心处,x、y轴分别与相面的两边平行,z轴为镜头光轴,与像平面垂直。

刚体变换只改变物体的空间位置(平移)和朝向(旋转),而不改变其形状,可用两个变量来描述:旋转矩阵R和平移向量t:

齐次坐标下可写为:

旋转矩阵R是正交矩阵,可通过罗德里格斯(Rodrigues)变换转换为只有三个独立变量的旋转向量:

因此,刚体变换可用6个参数来描述,这6个参数就称为相机的外参(Extrinsic),相机外参决定了空间点从世界坐标系转换到相机坐标系的变换,也可以说外参描述了相机在世界坐标系中的位置和朝向。

二、透视投影(从相机坐标系到图像坐标系)

我们可以将透镜的成像简单地抽象成下图所示:

设 f=OB 表示透镜的焦距,m=OC 为像距,n=AO 为物距,有:

一般地,由于物距远大于焦距,即 n>>f,所以 m≈f,此时可以用小孔模型代替透镜成像:

可得:

齐次坐标下有:

如果将成像平面移到相机光心与物体之间,则有中心透视模型:

可得:

齐次坐标下有:

总体上看,透视投影将相机坐标系中的点投影到理想图像坐标系,其变换过程只与相机焦距 f 有关。

三、畸变矫正

理想的针孔成像模型确定的坐标变换关系均为线性的,而实际上,现实中使用的相机由于镜头中镜片因为光线的通过产生的不规则的折射,镜头畸变(lens distortion)总是存在的,即根据理想针孔成像模型计算出来的像点坐标与实际坐标存在偏差。畸变的引入使得成像模型中的几何变换关系变为非线性,增加了模型的复杂度,但更接近真实情形。畸变导致的成像失真可分为径向失真和切向失真两类:

畸变类型很多,总体上可分为径向畸变和切向畸变两类,径向畸变的形成原因是镜头制造工艺不完美,使得镜头形状存在缺陷,包括枕形畸变和桶形畸变等,可以用如下表达式来描述:

切向畸变又分为薄透镜畸变和离心畸变等,薄透镜畸变则是因为透镜存在一定的细微倾斜造成的;离心畸变的形成原因是镜头是由多个透镜组合而成的,而各个透镜的光轴不在同一条中心线上。切向畸变可以用如下数学表达式来描述:

在引入镜头的畸变后,成像点从理想图像坐标系到真实图像坐标系的变换关系可以表示为:

实际计算过程中,如果考虑太多高阶的畸变参数,会导致标定求解的不稳定。

四、数字化图像

光线通过相机镜头后最终成像在感光阵列(CCD或CMOS)上,然后感光阵列将光信号转化为电信号,最后形成完整的图像。我们用dx和dy分别表示感光阵列的每个点在x和y方向上物理尺寸,即一个像素是多少毫米,这两个值一般比较接近,但由于制造工艺的精度问题,会有一定误差,同样的,感光阵列的法向和相机光轴也不是完全重合,即可以看作成像平面与光轴不垂直。

我们用仿射变换来描述这个过程,如上图,O点是图像中心点,对应图像坐标(u0,v0),Xd - Yd是真实图像坐标系,U-V是数字化图像坐标系,有:

齐次坐标下有:

上式中的变换矩阵即为相机的内参数矩阵 K,其描述了相机坐标系中点到二维图像上点的变换过程。

综上所述,在不考虑镜头畸变的情况下,相机的整个成像过程可表示为:

四个坐标系之间存在着下述关系 ( 矩阵依次左乘 ):

五、参数标定

参数标定即通过一定方法求得上述成像模型中的各个未知量(5个内参、6个外参以及畸变参数)。相机标定主要有传统标定方法和自标定方法两类,传统标定方法需要标定参照物,参照物的参数已知,然后分析拍摄到的参照物图像,求得相机参数,如直接线性变换(DLT)方法、Tsai两步标定法和张正友平面标定法等。传统方法操作相对复杂,但精度较高。

自标定方法不需要标定参照物,只需要多幅图像点的对应关系就能求解出相机参数,如基于无穷远平面、绝对二次曲面的自标定方法、基于Kruppa方程的自标定方法等。自标定方法灵活方便,但由于是非线性标定,精度和鲁棒性都不高。

这里主要介绍张正友平面标定法,其操作相对简单,且精度较高,实际应用中很常用。

整个标定过程包括如下步骤:

  • 打印一张棋盘格,把它贴在一个平面上,作为标定物。
  • 通过调整标定物或摄像机的方向,为标定物拍摄一些不同方向的照片。
  • 从照片中提取棋盘格角点。
  • 估算理想无畸变的情况下,五个内参和六个外参。
  • 应用最小二乘法估算实际存在径向畸变下的畸变系数。
  • 极大似然法,优化估计,提升估计精度。

首先是准备数据,本人的数据示例如图:

1、MATLAB相机标定

实际应用中,常使用MATLAB标定工具箱进行相机标定 Camera Calibration Toolbox for Matlab,只需导入拍摄的棋盘格照片,输入一些参数,然后对每张照片选择棋盘区域,就可以自动完成整个标定过程,并得到可视化的标定结果。

  • 打开matlab,找到“Camera Calibrator”并打开

  • 在新窗口中选择添加图片“Add Images”

  • 添加图片之后,会有如下提示,设置棋盘格的实际大小之后,点击ok

注意,假如在采集图片时角度均较好,则可以顺利进行下一步,但是本人咋实验时,由于采集的图片有几张角度不太好,因此在这一步之后出现了一个提示,是说有几张图片弃用了,若出现了可以不用管它,但是可以看一下都有哪几张图片弃用了。

  • 点击“Calibrate”开始计算

点击完成计算之后,可以在右侧看到如图:

  • 点击“Export Camera Parameters”,输出到matlab命令窗口

  • 导出的数据如下:

  • 在命令窗口输入以下命令即可获得内参矩阵和径向畸变:

2、opencv-python 张正友相机标定法实现

简单而言,我们拍摄的物体都处于三维世界坐标系中,而相机拍摄时镜头看到的是三维相机坐标系,成像时三维相机坐标系向二维图像坐标系转换。不同的镜头成像时的转换矩阵不同,同时可能引入失真,标定的作用是近似地估算出转换矩阵和失真系数。为了估算,需要知道若干点的三维世界坐标系中的坐标和二维图像坐标系中的坐标,也就是拍摄棋盘的意义。此部分内容主要参考于:https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_calib3d/py_calibration/py_calibration.html?highlight=findchessboardcorners

import cv2
import glob
import numpy as np
'''
cbraw和cbcol是我自己加的。tutorial用的棋盘足够大包含了7×6以上
个角点,我自己用的只有6×4。这里如果角点维数超出的话,标定的时候会报错。
'''
cbraw = 6
cbcol = 4
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((cbraw*cbcol,3), np.float32)
'''
设定世界坐标下点的坐标值,因为用的是棋盘可以直接按网格取;
假定棋盘正好在x-y平面上,这样z值直接取0,简化初始化步骤。
mgrid把列向量[0:cbraw]复制了cbcol列,把行向量[0:cbcol]复制了cbraw行。
转置reshape后,每行都是4×6网格中的某个点的坐标。
'''
objp[:,:2] = np.mgrid[0:cbraw,0:cbcol].T.reshape(-1,2)

objpoints = [] # 3d point in real world space
imgpoints = [] # 2d points in image plane.
#glob是个文件名管理工具
images = glob.glob("/home/hqd/桌面/c1/*.jpg")
for fname in images:
#对每张图片,识别出角点,记录世界物体坐标和图像坐标
    img = cv2.imread(fname) #source image
    #我用的图片太大,缩小了一半
    img = cv2.resize(img,None,fx=0.5, fy=0.5, interpolation = cv2.INTER_CUBIC)
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #转灰度
    #cv2.imshow('img',gray)
    #cv2.waitKey(1000)
    #寻找角点,存入corners,ret是找到角点的flag
    ret, corners = cv2.findChessboardCorners(gray,(6,4),None)
    #criteria:角点精准化迭代过程的终止条件
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
    #执行亚像素级角点检测
    corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)

    objpoints.append(objp)
    imgpoints.append(corners2)
    #在棋盘上绘制角点,只是可视化工具
    img = cv2.drawChessboardCorners(gray,(6,4),corners2,ret)
    cv2.imshow('img',img)
    #cv2.waitKey(1000)
'''
传入所有图片各自角点的三维、二维坐标,相机标定。
每张图片都有自己的旋转和平移矩阵,但是相机内参和畸变系数只有一组。
mtx,相机内参;dist,畸变系数;revcs,旋转矩阵;tvecs,平移矩阵。
'''
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1],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    # 平移向量  # 外参数

在用前面的数据图片进行实验得到的结果为:

NB:关于相机标定,还有使用vs+opencv的方法,在本人实践后再更新

参考资料:

https://blog.csdn.net/aoulun/article/details/78768570

https://blog.csdn.net/h2oco2ch4/article/details/79504449

https://blog.csdn.net/u010128736/article/details/52875137

猜你喜欢

转载自blog.csdn.net/hitzijiyingcai/article/details/82715921