Python-OpenCV相机标定、张正友标定法

一、相机标定介绍:

相机标定是进行视觉测量和定位的基础工作之一,标定参数准确与否直接关系到整个系统的精度,为此根据自己项目中的经验及参考相关的商用视觉软件的做法将相机标定过程中标定图片的获取过程中需要注意的问题总结如下:

标定板拍摄的张数要能覆盖整个测量空间及整个测量视场,把相机图像分成四个象限,应保证拍摄的标定板图像均匀分布在四个象限中,且在每个象限中建议进行不同方向的两次倾斜,下图是一组推荐摆放方式图片。

标定图片的数量通常在10~25张之间,图像数量太少,容易导致标定参数不准确。

圆或者圆环特征的像素数尽量大于20,标定板的成像尺寸应大致占整幅画面的1/4

用辅助光源对标定板进行打光,保证标定板的亮度足够且均匀

标定板成像不能过爆,过爆会导致特征轮廓的提取的偏移,从而导致圆心提取不准确。

标定板特征成像不能出现明显的离焦距,出现离焦时可通过调整调整标定板的距离、光圈的大小和像距(对于定焦镜头,通常说的调焦就是指调整像距)。

标定过程,相机的光圈、焦距不能发生改变,改变需要重新标定。

张正有标定法:
**相机标定的目的:**获取摄像机的内参和外参矩阵(同时也会得到每一幅标定图像的选择和平移矩阵),内参和外参系数可以对之后相机拍摄的图像就进行矫正,得到畸变相对很小的图像。
**相机标定的输入:**标定图像上所有内角点的图像坐标,标定板图像上所有内角点的空间三维坐标(一般情况下假定图像位于Z=0平面上)。
**相机标定的输出:**摄像机的内参、外参系数。

二、标定步骤为:

  1. 准备标定图片
  2. 对每一张标定图片,提取角点信息
  3. 对每一张标定图片,进一步提取亚像素角点信息
  4. 在棋盘标定图上绘制找到的内角点(非必须,仅为了显示)
  5. 相机标定
  6. 对标定结果进行评价
  7. 查看标定效果——利用标定结果对棋盘图进行矫正
    下面是对相机模型的简单介绍:
    Python-OpenCV相机标定、张正友标定法
    Python-OpenCV相机标定、张正友标定法
    Python-OpenCV相机标定、张正友标定法
    Python-OpenCV相机标定、张正友标定法

三、使用棋盘格标定

OpenCV使用棋盘格板进行标定(如下图),为了标定相机,我们需要输入一系列三维点和它们对应的二维图像点。在黑白相间的棋盘格上,二维图像点很容易通过角点检测找到。而对于真实世界中的三维点呢?由于我们采集中,是将相机放在一个地方,而将棋盘格定标板进行移动变换不同的位置,然后对其进行拍摄。所以我们需要知道(X,Y,Z)的值。但是简单来说,我们定义棋盘格所在平面为XY平面,即Z=0。对于定标板来说,我们可以知道棋盘格的方块尺寸。
Python-OpenCV相机标定、张正友标定法

四、glob.glob(’*.jpg’):

glob是python自己带的一个文件操作相关模块,用它可以查找符合自己目的的文件,类似于Windows下的文件搜索,支持通配符操作,,?,[]这三个通配符,代表0个或多个字符,?代表一个字符,[]匹配指定范围内的字符,如[0-9]匹配数字。两个主要方法如下:

1、glob方法:
glob模块的主要方法就是glob,该方法返回所有匹配的文件路径列表(list);该方法需要一个参数用来指定匹配的路径字符串(字符串可以为绝对路径也可以为相对路径),其返回的文件名只包括当前目录里的文件名,不包括子文件夹里的文件。
如:

glob.glob(r’c:*.txt’)
获得C盘下的所有txt文件
glob.glob(r’E:\pic**.jpg’)
获得指定目录下的所有jpg文件
使用相对路径:
glob.glob(r’../*.py’)

2、iglob方法:
获取一个迭代器( iterator )对象,使用它可以逐个获取匹配的文件路径名。与glob.glob()的区别是:glob.glob同时获取所有的匹配路径,而 glob.iglob一次只获取一个匹配路径。
如:
父目录中所有的.py文件,f是一个迭代器对象,通过遍历,可以输出所有满足条件的*.py文件。

f = glob.iglob(r'../*.py')
print f
<generator object iglob at 0x00B9FF80>

for py in f:
    print py

五、标定棋盘格角点检测:

使用cv2.findChessboardCorners()函数进行棋盘格角点检测,实现代码为:

retval, corners	=cv.findChessboardCorners(image, patternSize[, corners[, flags]])

参数分别表示为:
image:棋盘图像,8位灰度或彩色图像。
patternSize[]:棋盘的尺寸,注意应为内角点个数,内角点是和其他格子连着的点。
corners[]:存放角点的位置。
flags:迭代的准则。
返回值:
retval:是否检测出角点。
corners:角点的位置。

六、角点精确检测:

cv2.cornerSubPix函数实现代码:

corners	= cv.cornerSubPix( image, corners, winSize, zeroZone, criteria	)

参数:
image:输入图像,8位或者float型。
corners:角点初始坐标。
winsize:搜索窗口为2*winsize+1。
zerozone:死区,不计算区域,避免自相关矩阵的奇异性。没有死区,参数为(-1,-1)
criteria:求角点的迭代终止条件。
返回值:
corner:角点位置。
显示角点位置:cv.drawChessboardCorners

image	=cv.drawChessboardCorners(image, patternSize, corners, patternWasFound	)

patternWasFound:标志位,检测是否所有board都被检测到,若为是,则将角点连线,否则不连线。
角点检测结果为:
Python-OpenCV相机标定、张正友标定法

七、标定、去畸变:

通过上面的步骤,我们得到了用于标定的三维点和与其对应的图像上的二维点对。我们使用**cv2.calibrateCamera()**进行标定,这个函数会返回标定结果、相机的内参数矩阵、畸变系数、旋转矩阵和平移向量。

retval, cameraMatrix, distCoeffs, rvecs, tvecs	=	
                                    cv.calibrateCamera(
                                    objectPoints, imagePoints, imageSize, cameraMatrix, 
                                    distCoeffs[, rvecs[, tvecs[, flags[, criteria]]]]
)

objectPoints:世界坐标系里的位置。
imagePoints: 像素坐标。
imageSize:为图像的像素尺寸大小。
cameraMatrix:3*3矩阵,相机内参数矩阵。
disCoeffs:畸变矩阵
rvecs:旋转向量
tvecs:位移向量
flags:标定采用的算法
criteria:迭代终止条件设定。
纠正图像:
我们已经得到了相机内参和畸变系数,在将图像去畸变之前,我们还可以使用cv.getOptimalNewCameraMatrix()优化内参数和畸变系数,通过设定**比例因子alpha。当alpha设为0的时候,将会返回一个剪裁过的将去畸变后不想要的像素去掉的内参数和畸变系数;当alpha设为1的时候,将会返回一个包含额外黑色像素点的内参数和畸变系数,并返回一个ROI用于将其剪裁掉。

retval, validPixROI = cv.getOptimalNewCameraMatrix(	
                        cameraMatrix, distCoeffs, imageSize, 
                        alpha[, newImgSize[, centerPrincipalPoint]]	)

imageSize:原始图像尺寸。
newImageSize:校正后图像尺寸。
alpha:取0或1。
centerPrincipalPoint:是否作用于中心,默认为opencv自己根据图像选择位置。
去畸变方法有两个:
1、使用cv2.undistort()
这是一个最直接的办法,只用直接调用函数就可以得到去畸变的图像,使用上面的ROI可以对其进行剪裁,代码如下:

dst = cv.undistort(	src, cameraMatrix, distCoeffs[, dst[, newCameraMatrix]]	)

scr:待矫正图片。
dst:输出图像。
2、使用remmaping
这是一个分两步的方法,首先计算一个从畸变图像到非畸变图像的映射,然后使用这个映射关系对图像进行去畸变。
代码如下:

# undistort
mapx,mapy = cv2.initUndistortRectifyMap(mtx,dist,None,newcameramtx,(w,h),5)
dst = cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR)

# crop the image
x,y,w,h = roi
dst = dst[y:y+h, x:x+w]
cv2.imwrite('1.jpg',dst)

畸变前照片显示为:
Python-OpenCV相机标定、张正友标定法
畸变后图像显示为:
Python-OpenCV相机标定、张正友标定法
反投影误差:
通过反投影误差,我们可以来评估结果的好坏。越接近0,说明结果越理想。通过之前计算的内参数矩阵、畸变系数、旋转矩阵和平移向量,使用cv2.projectPoints()计算三维点到二维图像的投影,然后计算反投影得到的点与图像上检测到的点的误差,最后计算一个对于所有标定图像的平均误差,这个值就是反投影误差。
反投影误差输出为:
total error: 0.2749755130906811
输出结果为:

# 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

ret: 2.6795184503297125
mtx:
[[1.54507253e+03 0.00000000e+00 7.08574606e+02]
[0.00000000e+00 1.53895634e+03 9.43853715e+02]
[0.00000000e+00 0.00000000e+00 1.00000000e+00]]
dist:
[[ 3.61666931e-01 -4.00948363e+00 -7.30700019e-03 4.63663938e-03
9.79330670e+00]]
rvecs:
[array([[-0.2877394 ],
[-0.21036037],
[ 0.03651362]]), array([[0.33471319],
[0.44771474],
[0.08972639]]), array([[-0.08931782],
[-0.27507353],
[ 0.03526383]]), array([[0.04235042],
[0.10054733],
[1.55701373]]), array([[ 0.05041087],
[-0.26554473],
[-0.02013848]]), array([[ 0.02156874],
[-0.19938646],
[ 1.54684626]]), array([[0.31223154],
[0.29212045],
[1.63447445]]), array([[0.34590436],
[0.20629353],
[0.00067183]]), array([[-0.10590303],
[-0.07886755],
[-0.03373028]]), array([[-0.31002089],
[ 0.7023616 ],
[-1.40739999]])]
tvecs:
[array([[-3.38786842],
[-6.87000321],
[16.11926945]]), array([[-4.73303821],
[-1.87536976],
[15.37787829]]), array([[-2.52525568],
[-6.77649149],
[15.3071956 ]]), array([[ 1.55572306],
[-6.1113527 ],
[18.78854958]]), array([[-3.14577213],
[-2.94860191],
[14.07370248]]), array([[ 4.24828394],
[-2.8946424 ],
[14.5087294 ]]), array([[ 1.29859411],
[-4.2980895 ],
[13.7318654 ]]), array([[-5.19464371],
[-1.18132754],
[14.90195973]]), array([[-5.02803085],
[-5.20308479],
[17.32464452]]), array([[-5.55783892],
[ 4.02220309],
[16.56176739]])]

mtx:内参数矩阵
dist:畸变系数
rvecs:旋转向量 (外参数)
tvecs :平移向量 (外参数)
本人手机型号为:
华为nave4e

实验代码为:

import cv2
import numpy as np
import glob

# 找棋盘格角点
# 阈值
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
#棋盘格模板规格
w = 9
h = 9
# 世界坐标系中的棋盘格点,例如(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('picture_1/*.jpg')
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)
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('picture_1/5.jpg')
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.jpg',dst)

# 反投影误差
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))