校准摄像头

​ 不难知晓,摄像头的准确性对图像识别程序的重要性。为了确保摄像头不会出现图像失真严重的情况,可以通过摄像头拍摄国际象棋的棋盘,通过拍摄的棋盘图像计算摄像头的相机矩阵和失真系数。加载计算所得的相机矩阵和失真系数即可完成摄像头的校准。当然,由于使用使用平面摄像头,所以教程中并未加载校准参数,若觉得校准很麻烦可直接跳过

1、生成一张国际棋盘图

准备630*890的全黑图片,并将其重命名为3a4.bmp。运行以下程序即可获得一张国际象棋的棋盘图片。

#!/usr/bin/env python3

"""生成国际象棋棋盘图片"""

import os
import cv2


path = os.path.join(os.path.dirname(__file__), "3a4.bmp")
print(path)

frame = cv2.imread(path)
row, col, nc = frame.shape

width_of_roi = 90
# 这里是对全黑图片做处理,将图片以黑白间隔的形式zh
for j in range(row):
    data = frame[j]
    for i in range(col):
        f = int(i / width_of_roi) % 2 ^ int(j / width_of_roi) % 2
        if f:
            frame[j][i][0] = 255
            frame[j][i][1] = 255
            frame[j][i][2] = 255
cv2.imshow("", frame)
cv2.waitKey(0) & 0xFF == ord("q")
cv2.imwrite(os.path.join(os.path.dirname(__file__), "1.png"), frame)

2、拍摄棋盘图片

将棋盘图片展示在电脑上,通过摄像头拍摄多张棋盘图片。

#!/usr/bin/env python3

"""
这是一个辅助文件,帮助校准相机。
它将调用相机,获取图片并实时显示。
您可以在控制台中输入任意值来保存图片。
"""

import os
import cv2
import threading


if_save = False
# 设置摄像头编号(由于电脑型号不同,分配给USB摄像头的编号也可能不同,一般为0或1)
cap_num = int(input("Input the camare number:"))
# 设置所存储的图片名称,设置为1,即表示从1开始累加存储。如:1.jpg,2.jpg,3.jpg......
name = int(input("Input start name, use number:"))

cap = cv2.VideoCapture(cap_num)
dir_path = os.path.dirname(__file__)


def save():
    global if_save
    while True:
        input("Input any to save a image:")
        if_save = True

# 开启线程进行摄像头拍摄
t = threading.Thread(target=save)
# 设置为异步运行
t.setDaemon(True)
t.start()

while cv2.waitKey(1) != ord("q"):
    _, frame = cap.read()
    if if_save:
        # 设置名称为当前路径下,否则会因为运行环境的原因使得存储位置发生变化
        img_name = os.path.join(dir_path,str(name)+".jpg") 
        # 存储图片
        cv2.imwrite(img_name, frame)
        print("Save {} successful.".format(img_name))
        name += 1
        if_save = False
    cv2.imshow("", frame)

3、获得相机矩阵和失真系数

​ OpenCV自带相机校准函数,通过识别棋盘中的网格,自动生成我们所需的相机矩阵和失真系数。这里我们只需确保在第二步拍摄的图片能够被识别出来即可。若是都识别失效也没有关系,我们只需再次重复第二步即可。当然若不想如此麻烦的重复第二步和第三步,可以直接越过第二步,在第三步调用calibration_camera函数时设置cap_num参数即摄像头编号,进行实时处理获得相机举证以及失真系数。详细说明可仔细阅读代码中的注解。

#!/usr/bin/env python3
import os
import glob
import numpy as np
import cv2 as cv
from pprint import pprint


def calibration_camera(row, col, path=None, cap_num=None, saving=False):
    """校准摄像头

    参数说明:
        row (int): 网格中的行数。
        col (int): 网格中的列数。
        path (string): 存放校准图片的位置。
        cap_num (int): 表示摄像头的编号,一般0或1
        saving (bool): 是否存放相机矩阵和失真系数(.npz).
    """

    # 终止准则/失效准则
    criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)
    # 准备物体点, 比如 (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
    obj_p = np.zeros((row * col, 3), np.float32)
    obj_p[:, :2] = np.mgrid[0:row, 0:col].T.reshape(-1, 2)
    # 组用于存储来自所有图像的对象点和图像点。
    obj_points = []  # 3d点在现实世界的位置。
    img_points = []  # 2d点在图片中的位置。

    gray = None

    def _find_grid(img):
        # 使用函数外的参数
        nonlocal gray, obj_points, img_points
        # 将图片转换为灰色度图片
        gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
        # 寻找棋盘的角落
        ret, corners = cv.findChessboardCorners(gray, (row, col), None)
        # 如果找到,则添加处理后的2d点和3d点
        if ret == True:
            obj_points.append(obj_p)
            corners2 = cv.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
            img_points.append(corners)
            # 在图片中绘制并展示所寻找到的角
            cv.drawChessboardCorners(img, (row, col), corners2, ret)

    # 要求必须选择使用图片校准或者摄像头实时捕获校准中的一种
    if path and cap_num:
        raise Exception("The parameter `path` and `cap_num` only need one.")
    # 图片校准
    if path:
        # 获取当前路径中的所有图片
        images = glob.glob(os.path.join(path, "*.jpg"))
        pprint(images)
        # 对获取的每张图片进行处理
        for f_name in images:
            # 读取图片
            img = cv.imread(f_name)
            _find_grid(img)
            # 展示图片
            cv.imshow("img", img)
            # 图片展示等待0.5s
            cv.waitKey(500)
    # 摄像头实时捕获校准
    if cap_num:
        # 开启摄像头
        cap = cv.VideoCapture(cap_num)
        while True:
            # 读取摄像头开启后的每帧图片
            _, img = cap.read()
            _find_grid(img)
            cv.imshow("img", img)
            cv.waitKey(500)
            print(len(obj_points))
            if len(obj_points) > 14:
                break
    # 销毁展示窗口
    cv.destroyAllWindows()
    # 通过计算获取的3d点和2d点得出相机矩阵和失真系数
    ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(
        obj_points, img_points, gray.shape[::-1], None, None
    )
    print("ret: {}".format(ret))
    print("matrix:")
    pprint(mtx)
    print("distortion: {}".format(dist))
    # 决定是否存储所计算出的参数
    if saving:
        np.savez(os.path.join(os.path.dirname(__file__), "mtx_dist.npz"), mtx=mtx, dist=dist)

    mean_error = 0
    for i in range(len(obj_points)):
        img_points_2, _ = cv.projectPoints(obj_points[i], rvecs[i], tvecs[i], mtx, dist)
        error = cv.norm(img_points[i], img_points_2, cv.NORM_L2) / len(img_points_2)
        mean_error += error
    print("total error: {}".format(mean_error / len(obj_points)))

    return mtx, dist


if __name__ == "__main__":
    path = os.path.dirname(__file__)
    mtx, dist = calibration_camera(8, 6, path, saving=True)
    # 设置是否需要测试计算出的参数
    if_test = input("If testing the result (default: no), [yes/no]:")
    if if_test not in ["y", "Y", "yes", "Yes"]:
        exit(0)

    cap_num = int(input("Input camera number:"))
    cap = cv.VideoCapture(cap_num)
    while cv.waitKey(1) != ord("q"):
        _, img = cap.read()
        h, w = img.shape[:2]
        # 相机校准
        dst = cv.undistort(img, mtx, dist)
        cv.imshow("", dst)

4、加载相机矩阵和失真系数

以下代码主要是为了说明如何加载上一步存储的校准参数,对相机进行校准。

"""加载校准参数对摄像头进行校准."""

import os
import numpy as np
import cv2 as cv


if __name__ == "__main__":
    # 加载校准参数
    load = np.load(os.path.join(os.path.dirname(__file__), "mtx_dist.npz"))
    mtx = load["mtx"]
    dist = load["dist"]

    cap = cv.VideoCapture(0)
    while cv.waitKey(1) != ord("q"):
        _, img = cap.read()
        h, w = img.shape[:2]
        # 相机校准
        dst = cv.undistort(img, mtx, dist, None)
        cv.imshow("", dst)

results matching ""

    No results matching ""