版权声明:本文为博主的文章,未经博主禁止可以随意转载。 https://blog.csdn.net/ONE_SIX_MIX/article/details/84076589
自写自用的单目相机标定工具,双目的还没弄好
教程写起来好麻烦啊。。
使用方式
第一步
启动程序,输入相机编号,回车启动相机循环
第二步
viewer窗口接收按键信息
h 帮助,显示在控制台窗口上
e 结束照片采集, 继续计算畸变参数
d 从文件夹内已有图像进行采集角点对
space 从摄像头采集一张图像并采集角点对
q 退出
按空格键采集角点对,采集成功findCorners窗口会显示刚刚采集的角点对图像
采集大约20张以上不同角度图像后,按e结束照片采集, 继续计算畸变参数
等待一段时间
计算完后的相机内参和畸变参数会保存在 params.txt 文件内
反投影误差会打印到控制台上,可以大概看作是本次标定的质量
第三步
然后 viewer 窗口会进入 反畸变模式
h 帮助,显示在控制台窗口上
space 切换反畸变和直接显示
e 切换是否裁剪图像
q 退出
空格键切换 反畸变和直接显示,要是ok就按q键结束程序
完整代码
import cv2
import numpy as np
import imageio
import os
import time
import random
from glob import glob
img_dir = '标定图'
os.makedirs(img_dir, exist_ok=True)
# 观察窗口分辨率
viewer_hw = [720, 1280]
# 相机分辨率
capture_resolution_hw = [1080, 1920]
cam_id = int(input('please input cam id\n'))
# dshow only for windows
cam_cap = cv2.CAP_DSHOW
# v4l2 only for linux
# cam_cap = cv2.CAP_V4L2
cam = cv2.VideoCapture(cam_cap + cam_id)
if not cam.isOpened():
print('open cam id %d failure' % cam_id)
exit(-1)
imhw = [0, 0]
# 预热相机
print('预启动相机')
while True:
ret, img = cam.read()
if ret:
imhw = img.shape[:2]
break
print('预热失败')
print('open cam success')
# 启动相机设置面板,只有windows并且使用dshow时可用,Linux上无效
cam.set(cv2.CAP_PROP_SETTINGS, 0)
# 设置相机分辨率,windows上opencv默认使用vfw,会造成实际无法更改分辨率情况,所以需要上面设置为dshow设备
print('设定相机X分辨率', cam.set(cv2.CAP_PROP_FRAME_WIDTH, capture_resolution_hw[1]))
print('设定相机Y分辨率', cam.set(cv2.CAP_PROP_FRAME_HEIGHT, capture_resolution_hw[0]))
print('当前相机X分辨率', cam.get(cv2.CAP_PROP_FRAME_WIDTH))
print('当前相机Y分辨率', cam.get(cv2.CAP_PROP_FRAME_HEIGHT))
# 初始化观察窗口
cv2.namedWindow('viewer', cv2.WINDOW_FREERATIO | cv2.WINDOW_GUI_EXPANDED)
cv2.resizeWindow('viewer', viewer_hw[1], viewer_hw[0])
cv2.namedWindow('findCorners', cv2.WINDOW_FREERATIO | cv2.WINDOW_GUI_EXPANDED)
cv2.resizeWindow('findCorners', viewer_hw[1], viewer_hw[0])
cv2.imshow('viewer', np.zeros([100, 100], np.uint8))
cv2.imshow('findCorners', np.zeros([100, 100], np.uint8))
cv2.waitKey(1)
# 查找阈值
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
#棋盘格标定板规格
w = 9
h = 6
real_size = 1
# 世界坐标系中的棋盘格点,例如(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)
objp *= real_size
# 储存棋盘格角点的世界坐标和图像坐标对
objpoints = [] # 在世界坐标系中的三维点
imgpoints = [] # 在图像平面的二维点
filelist = [] # 文件名列表
def find_corners(img):
'''
查找角点
:param img: 输入图像
:return: 找到角点时返回角点列表,没找到角点返回None
'''
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, (w, h), None,
cv2.CALIB_CB_NORMALIZE_IMAGE | cv2.CALIB_CB_ADAPTIVE_THRESH)
if ret:
cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
return corners
return None
# 循环获取和储存待标定图像,
while True:
ret, img = cam.read()
if ret:
cv2.imshow('viewer', img)
key = cv2.waitKey(1000 // 60) & 0xff
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
if key == ord(' '):
print('正在查找角点')
cv2.setWindowTitle('viewer', 'viewer | searching corners')
corners = find_corners(img)
if corners is not None:
# 保存截取图像,便于下次使用
imfile = os.path.join(img_dir, str(time.time()) + '.jpg')
filelist.append(imfile)
nim = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
imageio.imwrite(imfile, nim)
objpoints.append(objp)
imgpoints.append(corners)
# 在图像上标记角点
cv2.drawChessboardCorners(img, (w, h), corners, True)
cv2.imshow('findCorners', img)
print('角点已记录')
cv2.setWindowTitle('viewer', 'viewer | corners pair has been record')
else:
print('没找到角点')
cv2.setWindowTitle('viewer', 'viewer | corners pair not found')
elif key == ord('e'):
break
elif key == ord('d'):
print('正在载入已有图像并采集角点对')
new_files = set(glob(os.path.join(img_dir, '*.jpg'))) - set(filelist)
for imfile in new_files:
img = imageio.imread(imfile)
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
cv2.imshow('viewer', img)
corners = find_corners(img)
if corners is not None:
filelist.append(imfile)
cv2.imshow('viewer', img)
objpoints.append(objp)
imgpoints.append(corners)
# 将角点在图像上显示
cv2.drawChessboardCorners(img, (w, h), corners, True)
cv2.imshow('findCorners', img)
print('角点已记录')
cv2.setWindowTitle('viewer', 'viewer | corners pair has been record')
else:
cv2.setWindowTitle('viewer', 'viewer | corners pair not found')
cv2.waitKey(1)
print('载入完成')
elif key == ord('h'):
cv2.setWindowTitle('viewer', 'viewer | help info has been output console')
print('-------------------------')
print('h 帮助')
print('e 结束照片采集, 继续计算畸变参数')
print('d 从文件夹内已有图像进行采集角点对')
print('space 从摄像头采集一张图像并采集角点对')
print('q 退出')
elif key == ord('q'):
exit(0)
else:
key = cv2.waitKey(1000 // 60)
print('获取相机图像失败')
if len(objpoints) < 2:
print('角点对少于2对,请重新查找角点')
exit(-1)
print('正在计算畸变参数')
# 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, imhw[::-1], None, None, flags=cv2.CALIB_FIX_K3, criteria=criteria)
print('畸变参数计算完成')
# print(mtx, dist, rvecs, tvecs)
print(mtx, '\n', dist, '\n')
# 随机选取一张图像并显示去畸变结果
im = imageio.imread(random.choice(filelist))
im = cv2.resize(im, imhw[::-1])
im = cv2.cvtColor(im, cv2.COLOR_RGB2BGR)
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, imhw[::-1], 0, imhw[::-1]) # 自由比例参数
dst = cv2.undistort(im, mtx, dist, None, newcameramtx)
# 根据前面ROI区域裁剪图片
#x,y,w,h = roi
#dst = dst[y:y+h, x:x+w]
cv2.imshow('calibresult', dst)
cv2.resizeWindow('calibresult', 1280, 720)
cv2.imwrite('calibresult.jpg', dst)
cv2.waitKey(1)
# 计算反投影误差,越小越好
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))
# 输出相机内参到文本,第一行相机矩阵,第二行畸变矩阵
with open('params.txt', 'w') as f:
f.write(str(mtx.reshape([-1]).tolist())[1:-1])
f.write('\n')
f.write(str(dist.reshape([-1]).tolist())[1:-1])
# 切换显示标定前和标定后的图像
is_show_calibrated = True
is_show_black_edge = True
cv2.setWindowTitle('viewer', 'viewer | calibrated')
while True:
ret, img = cam.read()
if ret:
if is_show_calibrated:
img = cv2.undistort(img, mtx, dist, None, newcameramtx)
cv2.imshow('viewer', img)
key = cv2.waitKey(1000 // 60) & 0xff
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
if key == ord(' '):
is_show_calibrated = not is_show_calibrated
if is_show_calibrated:
cv2.setWindowTitle('viewer', 'viewer | calibrated')
else:
cv2.setWindowTitle('viewer', 'viewer | not calibrated')
elif key == ord('q'):
break
elif key == ord('e'):
is_show_black_edge = not is_show_black_edge
cv2.setWindowTitle('viewer', 'viewer | is_show_black_edge : ' + str(is_show_black_edge))
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, imhw[::-1], float(is_show_black_edge), imhw[::-1]) # 自由比例参数
elif key == ord('h'):
cv2.setWindowTitle('viewer', 'viewer | help info has been output console')
print('-------------------------')
print('h 帮助')
print('space 切换反畸变和直接显示')
print('e 切换是否裁剪图像')
print('q 退出')
else:
key = cv2.waitKey(1000 // 60)
print('获取相机图像失败')