张正友标记的学习实现

一、概念

“张正友标定”是指张正友教授于1998年提出的单平面棋盘格的摄像机标定方法。张氏标定法已经作为工具箱或封装好的函数被广泛应用。张氏标定的原文为“A Flexible New Technique forCamera Calibration”。此文中所提到的方法,为相机标定提供了很大便利,并且具有很高的精度。从此标定可以不需要特殊的标定物,只需要一张打印出来的棋盘格。

简单而言,我们拍摄的物体都处于三维世界坐标系中,而相机拍摄时镜头看到的是三维相机坐标系,成像时三维相机坐标系向二维图像坐标系转换。不同的镜头成像时的转换矩阵不同,同时可能引入失真,标定的作用是近似地估算出转换矩阵和失真系数。为了估算,需要知道若干点的三维世界坐标系中的坐标和二维图像坐标系中的坐标,也就是拍摄棋盘的意义。

二、算法原理及描述

基本原理:
在这里插入图片描述
其中,K为摄像机的内参数矩阵,[X Y 1]T为模板平面上点的齐次坐标,[u v 1]T为模板平面上点投影到图象平面上对应点的齐次坐标,[r1 r2 r3]和t 分别是摄像机坐标系相对于世界坐标系的旋转矩阵和平移向量。
在这里插入图片描述
根据旋转矩阵的性质,即r1Tr2=0和||r1||=||r2||=1,每幅图象可以获得以下两个对内参数矩阵的基本约束
在这里插入图片描述
由于摄像机有5个未知内参数,所以当所摄取得的图像数目大于等于3时,就可以线性唯一求解出K,即畸变系数。

三、算法步骤

1.准备阶段
首先用word绘制一个1010的国际象棋棋盘格,可知内角点数为99,并将图片打印出来。用手机拍摄10-20张棋盘的图像。
拍摄手机型号:vivo y67
张正友标记的学习实现_第1张图片
2.角点标记
  为了找到棋盘格模板,我们使用openCV中的函数cv2.findChessboardCorners()。我们也需要告诉程序我们使用的模板是什么规格的,例如88的棋盘格或者55棋盘格等,建议使用x方向和y方向个数不相等的棋盘格模板。下面实验中,我们使用的是107的棋盘格,每个方格边长是20mm,即含有96的内部角点。这个函数如果检测到模板,会返回对应的角点,并返回true。当然不一定所有的图像都能找到需要的模板,所以我们可以使用多幅图像进行定标。除了使用棋盘格,我们还可以使用圆点阵,对应的函数为cv2.findCirclesGrid()。
  找到角点后,我们可以使用cv2.cornerSubPix()可以得到更为准确的角点像素坐标。我们也可以使用cv2.drawChessboardCorners()将角点绘制到图像上显示。

其中一张图片的角点标记:
张正友标记的学习实现_第2张图片
3.标定
  通过上面的步骤,我们得到了用于标定的三维点和与其对应的图像上的二维点对。我们使用cv2.calibrateCamera()进行标定,这个函数会返回标定结果、相机的内参数矩阵、畸变系数、旋转矩阵和平移向量。
张正友标记的学习实现_第3张图片
4.去畸变
  第三步我们已经得到了相机内参和畸变系数,在将图像去畸变之前,我们还可以使用cv.getOptimalNewCameraMatrix()优化内参数和畸变系数,通过设定自由自由比例因子alpha。当alpha设为0的时候,将会返回一个剪裁过的将去畸变后不想要的像素去掉的内参数和畸变系数;当alpha设为1的时候,将会返回一个包含额外黑色像素点的内参数和畸变系数,并返回一个ROI用于将其剪裁掉。
 对以下图片去畸变:
张正友标记的学习实现_第4张图片
去畸变结果为:
张正友标记的学习实现_第5张图片
5.计算反投影误差
通过之前计算的内参数矩阵、畸变系数、旋转矩阵和平移向量,使用cv2.projectPoints()计算三维点到二维图像的投影,然后计算反投影得到的点与图像上检测到的点的误差,最后计算一个对于所有标定图像的平均误差,这个值就是反投影误差。通过反投影误差,我们可以来评估结果的好坏。越接近0,说明结果越理想。
在这里插入图片描述
本次实验计算得出的反投影误差。

四、实验代码

#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 = 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('zphoto/*.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.imwrite('resultret.png',img)
        cv2.waitKey(1)
cv2.destroyAllWindows()

# 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
print("标定结果:",ret)
print("相机的内参数矩阵:",mtx)
print("畸变系数:",dist)
print("旋转矩阵:",rvecs)
print("平移向量:",tvecs)
# 去畸变
img2 = cv2.imread('zphoto/zzy1.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.png',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))

五、附录
参考博客:
https://blog.csdn.net/h2oco2ch4/article/details/79504449

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

你可能感兴趣的:(图像处理)