引言

机器视觉中经常将相机拍摄到的物体与实际存在的坐标系联系,通过图像进行视觉测量定位,为了使相机获得世界坐标系三维信息,需要对相机进行标定。

1


opencv标定板识别 opencv标定后转换坐标_相机标定 无棋盘格

相机标定原理


相机将三维世界中的坐标点(单位:米)映射到二维图像平面(单位:像素)的过程能够用一个几何模型来描述,其中最简单的称为针孔相机模型 (pinhole camera model),其框架如下图所示:



opencv标定板识别 opencv标定后转换坐标_相机标定 无棋盘格_02

其中,涉及到相机标定涉及到了四大坐标系,分别为:

像素坐标系:为了描述物体成像后的像点在数字图像上(相片)的坐标而引入,是我们真正从相机内读取到的信息所在的坐标系。单位为个(像素数目)。

成像平面坐标系:为了描述成像过程中物体从相机坐标系到图像坐标系的投影透射关系而引入,方便进一步得到像素坐标系下的坐标。单位为m。

相机坐标系:在相机上建立的坐标系,为了从相机的角度描述物体位置而定义,作为沟通世界坐标系和图像/像素坐标系的中间一环。单位为m。

世界坐标系:用户定义的三维世界的坐标系,为了描述目标物在真实世界里的位置而被引入。单位为m。



opencv标定板识别 opencv标定后转换坐标_相机标定 无棋盘格

相机标定原理


下面详细推导从世界坐标系到像素坐标的过程。

1.世界坐标系到相机坐标系

从世界坐标系到相机坐标系, 这是一个刚体变换,只需对世界坐标系的三维点作用一个旋转R和平移t(R,t即为相机的外参),变换过程可以通过一下公式完成:



opencv标定板识别 opencv标定后转换坐标_tera term 使用方法_04

2.相机坐标系到成像平面坐标系

这一过程进行了从三维坐标到二维坐标的转换,也即投影透视过程(用中心投影法将物体投射到投影面上,从而获得的一种较为接近视觉效果的单面投影图,也就是使我们人眼看到景物近大远小的一种成像方式)。

成像过程如下图所示:针孔面(相机坐标系)在图像平面(图像坐标系)和物点平面(棋盘平面)之间,所成图像为倒立实像。



opencv标定板识别 opencv标定后转换坐标_opencv标定板识别_05

但是为了在数学上更方便描述,我们将相机坐标系和图像坐标系位置对调,变成下图所示的布置方式(没有实际的物理意义,只是方便计算):



opencv标定板识别 opencv标定后转换坐标_从像素坐标到相机坐标_06

此时,假设相机坐标系中有一点M,则在理想图像坐标系下(无畸变)的成像点P的坐标为(可由相似三角形原则得出):



opencv标定板识别 opencv标定后转换坐标_opencv标定板识别_07

f为焦距,整理,得:



opencv标定板识别 opencv标定后转换坐标_opencv裁剪图片_08

成像平面坐标系到像素坐标系



opencv标定板识别 opencv标定后转换坐标_opencv裁剪图片_09

如上图,成平面坐标系和像素坐标系之间存在一个缩放和平移:



opencv标定板识别 opencv标定后转换坐标_从像素坐标到相机坐标_10

整理得:



opencv标定板识别 opencv标定后转换坐标_opencv标定板识别_11

以fx、fy的方式表示为:



opencv标定板识别 opencv标定后转换坐标_opencv标定板识别_12

其中

α、β的单位为像素/米;

fx、fy为x、y方向的焦距,单位为像素;

(cx,cy)为主点,图像的中心,单位为像素。

那么,相机坐标系到像素坐标系的最终形式可写成:



opencv标定板识别 opencv标定后转换坐标_opencv标定板识别_13

将 Zc移到左边:



opencv标定板识别 opencv标定后转换坐标_从像素坐标到相机坐标_14

所以,在世界坐标系中的三维点M=[X,Y,Z]T 和像素坐标系中二维点m=[u,v]T的关系为:



opencv标定板识别 opencv标定后转换坐标_相机标定 无棋盘格_15

即:



opencv标定板识别 opencv标定后转换坐标_opencv裁剪图片_16

其中,s为缩放因子,A为相机的内参矩阵,[R t]为相机的外参矩阵,和分别为m和M对应的齐次坐标。

3.畸变模型

我们在摄像机坐标系到图像坐标系变换时谈到透视投影。摄像机拍照时通过透镜把实物投影到像平面上,但是透镜由于制造精度以及组装工艺的偏差会引入畸变,导致原始图像的失真。因此我们需要考虑成像畸变的问题。

透镜的畸变主要分为径向畸变和切向畸变,还有薄透镜畸变等等,但都没有径向和切向畸变影响显著,所以我们在这里只考虑径向和切向畸变。

径向畸变就是沿着透镜半径方向分布的畸变,产生原因是光线在原理透镜中心的地方比靠近中心的地方更加弯曲,这种畸变在普通廉价的镜头中表现更加明显,径向畸变主要包括桶形畸变和枕形畸变两种。以下分别是枕形和桶形畸变示意图:



opencv标定板识别 opencv标定后转换坐标_从像素坐标到相机坐标_17

实际情况中我们常用r=0处的泰勒级数展开的前几项来近似描述径向畸变,矫正径向畸变前后的坐标关系为:



opencv标定板识别 opencv标定后转换坐标_从像素坐标到相机坐标_18

切向畸变是由于透镜本身与相机传感器平面(像平面)或图像平面不平行而产生的,这种情况多是由于透镜被粘贴到镜头模组上的安装偏差导致。畸变模型可以用两个额外的参数p1和p2来描述:



opencv标定板识别 opencv标定后转换坐标_相机标定 无棋盘格_19

其中:



opencv标定板识别 opencv标定后转换坐标_tera term 使用方法_20

所以,我们一共需要5个畸变参数(k1,k2,k3,p1,p2)来描述透镜畸变。

相机标定方法有:传统相机标定法、主动视觉相机标定法、相机自标定法。

2



opencv标定板识别 opencv标定后转换坐标_相机标定 无棋盘格

opencv相机标定使用

#coding:utf-8
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('calib/*.png')
for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    # 找到棋盘格角点
    ret, corners = cv2.findChessboardCorners(gray, (w,h),None)
    # 如果找到足够点对,将其存储起来
    if ret == True:
        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(1)
cv2.destroyAllWindows()
# 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
# 去畸变
img2 = cv2.imread('calib/00169.png')
h,  w = img2.shape[:2]
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.png',dst)
# 反投影误差
total_error = 0
for i in xrange(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】相机标定方法. 计算机视觉life

【2】使用opencv进行标定. sylvester0510.