python3 简单的单目相机标定工具

版权声明:本文为博主的文章,未经博主禁止可以随意转载。 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('获取相机图像失败')

猜你喜欢

转载自blog.csdn.net/ONE_SIX_MIX/article/details/84076589