本篇博客主要介绍cv2模块中的摄像头标定。
摄像头会发生径向畸变和切向畸变。
摄像头的内部参数和外部参数。内部参数是摄像头特异的,它包括的信息有x方向和y方向的焦距(fx, fy),光圈中心(cx, cy),相机的内部参数也称为摄像头矩阵。
在进行摄像头标定时至少需要使用10张图案模式,3D点称为对象点,2D点称为图像点,可以使用棋盘或者环形格子来进行摄像头标定,使用函数cv2.findCircleGrid()来找图案。
代码示例:
# encoding:utf-8
import numpy as np
import cv2
import glob
# 设置终止条件,迭代30次或移动0.001
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# 准备对象点,类似(0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6 * 7, 3), np.float32)
objp[:, : 2] = np.mgrid[0: 7, 0: 6].T.reshape(-1, 2) # np.mgrid()返回多维结构
# 从所有图像中存储对象点和图像点的数组
objpoints = [] # 真实世界的3D点
imgpoints = [] # 图像的2D点
# glob.globglob.glob函数的参数是字符串。这个字符串的书写和我们使用
# linux的shell命令相似,或者说基本一样。也就是说,只要我们按照平常使
# 用cd命令时的参数就能够找到我们所需要的文件的路径。字符串中可以包括“*”、
# “?”和"["、"]",其中“*”表示匹配任意字符串,“?”匹配任意单个字符,
# [0-9]与[a-z]表示匹配0-9的单个数字与a-z的单个字符。
images = glob.glob('../data/left*.jpg')
images += glob.glob('../data/right*.jpg')
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 找到棋盘边界,角点检测
ret, corners = cv2.findChessboardCorners(gray, (7, 6), None)
# 如果找到,则添加对象点和图像点
if ret == True:
objpoints.append(objp)
# 亚像素级角点检测,在角点检测中精确化角点位置
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
imgpoints.append(corners)
# 绘制并展示边界
cv2.drawChessboardCorners(img, (7, 6), corners2, ret)
cv2.imshow('img', img)
cv2.waitKey(500)
cv2.destroyAllWindows()
注:由于使用的数据有点多,就不一一上传了,需要的可以私信我邮箱
输出结果(截取其中的两张):
代码2:
# encoding:utf-8
import cv2
import numpy as np
creteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
cap = cv2.VideoCapture(0)
# 等比缩放
frame_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
frame_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
frame_height = int(480 / frame_height * frame_width)
ret = cap.set(cv2.CAP_PROP_FRAME_HEIGHT, frame_height)
ret = cap.set(cv2.CAP_PROP_FRAME_WIDTH, 480)
while cap.isOpened():
ret, img = cap.read()
cv2.imshow('img0', img)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
'''
第一个参数Image,传入拍摄的棋盘图Mat图像,必须是8位的灰度或者彩色图像;
第二个参数patternSize,每个棋盘图上内角点的行列数,一般情况下,行列数不要相同,便于后续标定程序识别标定板的方向;
第三个参数corners,用于存储检测到的内角点图像坐标位置,一般用元素是Point2f的向量来表示:vector image_points_buf;
第四个参数flage:用于定义棋盘图上内角点查找的不同处理方式,有默认值。
'''
ret, corners = cv2.findChessboardCorners(image=gray, patternSize=(6, 4), corners=None)
print(corners)
print('---------')
if ret == True:
# objpoints.append(objp)
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), creteria)
# imgpoints.append(corners)
# Draw and display the corners
cv2.drawChessboardCorners(img, (6, 4), corners2, ret)
cv2.imshow('img', img)
key = cv2.waitKey(delay=10)
if key == ord("q"):
break
cv2.destroyAllWindows()
结果:
标定:
代码:
# encoding:utf-8
import numpy as np
import cv2
import glob
# 设置终止条件,迭代30次或移动0.001
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# 准备对象点,类似(0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6 * 7, 3), np.float32)
objp[:, : 2] = np.mgrid[0: 7, 0: 6].T.reshape(-1, 2) # np.mgrid()返回多维结构
# 从所有图像中存储对象点和图像点的数组
objpoints = [] # 真实世界的3D点
imgpoints = [] # 图像的2D点
# glob.globglob.glob函数的参数是字符串。这个字符串的书写和我们使用
# linux的shell命令相似,或者说基本一样。也就是说,只要我们按照平常使
# 用cd命令时的参数就能够找到我们所需要的文件的路径。字符串中可以包括“*”、
# “?”和"["、"]",其中“*”表示匹配任意字符串,“?”匹配任意单个字符,
# [0-9]与[a-z]表示匹配0-9的单个数字与a-z的单个字符。
images = glob.glob('../data/left*.jpg')
images += glob.glob('../data/right*.jpg')
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 找到棋盘边界,角点检测
ret, corners = cv2.findChessboardCorners(gray, (7, 6), None)
# 如果找到,则添加对象点和图像点
if ret == True:
objpoints.append(objp)
# 亚像素级角点检测,在角点检测中精确化角点位置
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
imgpoints.append(corners)
# 绘制并展示边界
# cv2.drawChessboardCorners(img, (7, 6), corners2, ret)
# print(objpoints)
# print(imgpoints)
# cv2.imshow('img', img)
# cv2.waitKey(500)
# cv2.destroyAllWindows()
# 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[:: -1], None, None)
# 畸形校正
img = cv2.imread('../data/left12.jpg')
cv2.imshow('source', img)
h, w = img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))
# 使用cv2.undistort(),和上面得到的ROI对结果进行剪裁
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
# 裁剪图像
x, y, w, h = roi
dst = dst[y: y + h, x: x + w]
cv2.imshow('re1', dst)
# 找到畸变图像到畸变图像的映射方程,在使用重映射方程
mapx, mapy = cv2.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w, h), 5)
dst = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)
# 裁剪图像
x, y, w, h = roi
dst = dst[y: y + h, x: x + w]
cv2.imshow('re2', dst)
'''
反向投影误差,我们可以利用反向投影误差对我们找到的参数的准确性评估,
得到的结果越接近0越好,有了内部参数、畸变参数和旋转变化矩阵,
就可以使用cv2.projectPoints()将对象转换到图像点
然后就可以计算变换得到的图像与角点检测算法的绝对差了
最后计算所有标定图像的误差平均值
'''
mean_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)
mean_error += error
print("total error: ", mean_error / len(objpoints))
cv2.waitKey(0)
cv2.destroyAllWindows()
原图:
运行结果: