相机标定记录

流程:

1、从棋盘格图像中获得角点,检测点位各异,需要做后处理获得棋盘格相应的角点阵列。findChessboardCorners实现角点提取与阵列化。
2、实现对棋盘格角点进行筛选与序列化本次梳理的主要任务
3、获得准确的角点阵列,可采用opencv库中calibrateCamera、stereoCalibrate等做后续相机标定求解。

畸变校正、径向校正,不做考察。

第一部分 基本介绍

在双目视觉应用领域,要想进行精确的操作,第一步要做的就是对摄像机的内参数进行求解,这个过程称之为标定。整个标定过程由cameraCalibrate()函数完成,测量相机焦距和便宜主要的原理是张正友标定法,测量畸变参数是Brown算法。该标定函数的一个输入参数是像点坐标,即在摄像机成像平面上对应角点相对于摄像机坐标系的二维坐标。而获得像点坐标的函数第一步就是找到角点坐标,函数是

findChessboardCorners(InputArray image, Size patternSize,OutputArray corners, int flags=CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE)

版本:OpenCV2.4.9
功能:找到标定板内角点位置(标定板是专用器具,需要有严格的规格控制,标定板的制作精度直接影响标定精度;角点是指黑白色相接的方块定点部分;内角点是不与标定板边缘接触的内部角点)

在这里插入图片描述

参数:
1.输入的图像矩阵,必须是8-bit灰度图或者彩色图像,在图像传入函数之前,一般经过灰度处理,还有滤波操作。
2.内角点的size,表示方式是定义Size PatSize(m,n),将PatSize作为参数传入。这里是内角点的行列数,不包括边缘角点行列数;行数和列数不要相同,这样的话函数会辨别出标定板的方向,如果行列数相同,那么函数每次画出来的角点起始位置会变化,不利于标定。
3.存储角点的数组,一般用

vector<vector<point2f>>

4.标志位,有默认值。
    CV_CALIB_CB_ADAPTIVE_THRESH:该函数的默认方式是根据图像的平均亮度值进行图像 二值化,设立此标志位的含义是采用变化的阈值进行自适应二值化;
    CV_CALIB_CB_NORMALIZE_IMAGE:在二值化之前,调用EqualizeHist()函数进行图像归一化处理;
    CV_CALIB_CB_FILTER_QUADS:二值化完成后,函数开始定位图像中的四边形(这里不应该称之为正方形,因为存在畸变),这个标志设立后,函数开始使用面积、周长等参数来筛选方块,从而使得角点检测更准确更严格。
    CALIB_CB_FAST_CHECK:快速检测选项,对于检测角点极可能不成功检测的情况,这个标志位可以使函数效率提升。
总结:该函数的功能就是判断图像内是否包含完整的棋盘图,如果能够检测完全,就把他们的角点坐标按 顺序(逐行,从左到右)记录下来,并返回非0数,否则返回0。 这里对size参数要求非常严格,函数必须检测到相同的size才会返回非0,否则返回0,这里一定要注意。角点检测不完全时,可能画不出图像,或者画出红色角点;正确的图像后面有参考。
  该函数检测的角点的坐标是不精确的,要想精确结果,需要使用 cornerSubPix()或find4QuadCornerSubpix()函数,进行亚像素精度的调整。
  亚像素化处理,不一定使得最终RMES误差降低。

单目标定算法示例

https://blog.csdn.net/qq_47947920/article/details/140371978

双目标定算法示例


# -*- coding:utf-8 -*-
import os
import numpy as np
import cv2
import glob
import argparse

import json
import pickle

import jsonlines

class Stereo_Camera_Calibration(object):
    def __init__(self, width, height, lattice):
        self.width       = width         # 棋盘格宽方向黑白格子相交点个数
        self.height      = height       # 棋盘格长方向黑白格子相交点个数
        self.lattice     = lattice

        # 设置迭代终止条件
        self.criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
        self.criteria_stereo = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-5)

    # =========================== 双目标定 =============================== #
    def stereo_calibration(self, file_L, file_R):
        # 设置 object points, 形式为 (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
        objp = np.zeros((self.width * self.height, 3), np.float32)  # 我用的是6×7的棋盘格,可根据自己棋盘格自行修改相关参数
        objp[:, :2] = np.mgrid[0:self.width, 0:self.height].T.reshape(-1, 2)
        objp       *= self.lattice

        # 用arrays存储所有图片的object points 和 image points
        objpoints = []  # 3d points in real world space
        imgpointsR = []  # 2d points in image plane
        imgpointsL = []

        for i in range(len(file_L)):
            ChessImaL = cv2.imread(file_L[i] ,0)  # 左视图
            ChessImaR = cv2.imread(file_R[i] ,0)  # 右视图

            retL, cornersL = cv2.findChessboardCorners(ChessImaL ,(self.width, self.height), cv2.CALIB_CB_ADAPTIVE_THRESH | cv2.CALIB_CB_FILTER_QUADS)  # 提取左图每一张图片的角点
            retR, cornersR = cv2.findChessboardCorners(ChessImaR ,(self.width, self.height), cv2.CALIB_CB_ADAPTIVE_THRESH | cv2.CALIB_CB_FILTER_QUADS)  # 提取右图每一张图片的角点

            if (True == retR) & (True == retL):
                objpoints.append(objp)
                cv2.cornerSubPix(ChessImaL, cornersL, (11, 11), (-1, -1), self.criteria)  # 亚像素精确化,对粗提取的角点进行精确化
                cv2.cornerSubPix(ChessImaR, cornersR, (11, 11), (-1, -1), self.criteria)  # 亚像素精确化,对粗提取的角点进行精确化
                imgpointsL.append(cornersL)
                imgpointsR.append(cornersR)


                # ret_l = cv2.drawChessboardCorners(ChessImaL, (self.width, self.height), cornersL, retL)
                # cv2.imshow(file_L[i], ChessImaL)
                # cv2.waitKey()

                # ret_r = cv2.drawChessboardCorners(ChessImaR, (self.width, self.height), cornersR, retR)
                # cv2.imshow(file_R[i], ChessImaR)
                # cv2.waitKey(500)

        # 相机的单双目标定、及校正
        #   左侧相机单独标定
        retL, K1, D1, rvecsL, tvecsL = cv2.calibrateCamera(objpoints ,imgpointsL ,ChessImaL.shape[::-1], None, None)
        #   右侧相机单独标定
        retR, K2, D2, rvecsR, tvecsR = cv2.calibrateCamera(objpoints ,imgpointsR ,ChessImaR.shape[::-1], None, None)

        # --------- 双目相机的标定 ----------#
        flags = 0
        flags |= cv2.CALIB_FIX_INTRINSIC         # K和D个矩阵是固定的。这是默认标志。如果你校准好你的相机,只求解𝑅,𝑇,𝐸,𝐹。
        # flags |= cv2.CALIB_FIX_PRINCIPAL_POINT  # 修复K矩阵中的参考点。
        # flags |= cv2.CALIB_USE_INTRINSIC_GUESS    # K和D个矩阵将被优化。对于这个计算,你应该给出经过良好校准的矩阵,以便(可能)得到更好的结果。
        # flags |= cv2.CALIB_FIX_FOCAL_LENGTH      # 在K矩阵中固定焦距。
        # flags |= cv2.CALIB_FIX_ASPECT_RATIO     # 固定长宽比。
        # flags |= cv2.CALIB_ZERO_TANGENT_DIST     # 去掉畸变。

        # 内参、畸变系数、平移向量、旋转矩阵
        retS, K1, D1, K2, D2,  R, T, E, F = cv2.stereoCalibrate(objpoints ,imgpointsL ,imgpointsR ,K1 ,D1 ,K2 ,D2,
                                                                ChessImaR.shape[::-1], self.criteria_stereo ,flags)

        print(f"Calibration ret_l: {retL}")
        print(f"Calibration ret_r: {retR}")
        print(f"Stereo Calibration ret: {retS}")


        # 左内参矩阵、左畸变向量、右内参矩阵、右畸变向量、旋转矩阵、平移矩阵
        return K1, D1, K2, D2, R, T
    # ==================================================================== #

    # =========================== 双目校正 =============================== #
    # 获取畸变校正、立体校正、重投影矩阵
    def getRectifyTransform(self, width ,height ,K1 ,D1, K2, D2, R, T):
        # 得出进行立体矫正所需要的映射矩阵
        # 左校正变换矩阵、右校正变换矩阵、左投影矩阵、右投影矩阵、深度差异映射矩阵
        R_l, R_r, P_l, P_r, Q, roi_left, roi_right = cv2.stereoRectify(K1, D1, K2, D2,
                                                                       (width, height), R, T,
                                                                       flags=cv2.CALIB_ZERO_DISPARITY, alpha=0)
        # # 标志CALIB_ZERO_DISPARITY,它用于匹配图像之间的y轴

        # 计算畸变矫正和立体校正的映射变换。
        map_lx, map_ly = cv2.initUndistortRectifyMap(K1, D1, R_l, P_l, (width, height), cv2.CV_32FC1)
        map_rx, map_ry = cv2.initUndistortRectifyMap(K2, D2, R_r, P_r, (width, height), cv2.CV_32FC1)

        return map_lx, map_ly, map_rx, map_ry, Q

    # 得到畸变校正和立体校正后的图像
    def get_rectify_img(self, imgL, imgR, map_lx, map_ly, map_rx, map_ry):
        rec_img_L = cv2.remap(imgL, map_lx, map_ly, cv2.INTER_LINEAR, cv2.BORDER_CONSTANT)  # 使用remap函数完成映射
        rec_img_R = cv2.remap(imgR, map_rx, map_ry, cv2.INTER_LINEAR, cv2.BORDER_CONSTANT)

        return rec_img_L, rec_img_R

    # 立体校正检验——极线对齐
    def draw_line(self, rec_img_L, rec_img_R):
        # 建立输出图像
        width = max(rec_img_L.shape[1], rec_img_R.shape[1])
        height = max(rec_img_L.shape[0], rec_img_R.shape[0])

        output = np.zeros((height, width * 2, 3), dtype=np.uint8)
        output[0:rec_img_L.shape[0], 0:rec_img_L.shape[1]] = rec_img_L
        output[0:rec_img_R.shape[0], rec_img_L.shape[1]:] = rec_img_R

        # 绘制等间距平行线
        line_interval = 50  # 直线间隔:50
        for k in range(height // line_interval):
            cv2.line(output, (0, line_interval * (k + 1)), (2 * width, line_interval * (k + 1)), (0, 255, 0),
                     thickness=2, lineType=cv2.LINE_AA)

        return output  # 可显示的图像
    # ===================================================================== #


def get_parser():
    parser = argparse.ArgumentParser(description='Camera calibration')
    parser.add_argument('--width', type=int, default=9, help='chessboard width size')
    parser.add_argument('--height', type=int, default=6, help='chessboard height size')
    parser.add_argument('--lattice', type=float, default=12.5, help='lattice length') # 棋盘格实际每个小格子的尺寸,量纲“毫米”
    parser.add_argument('--image_dir', type=str, default="./data/test_calib/", help='images path')
    parser.add_argument('--save_dir', type=str, default="./data/save/", help='path to save file')
    parser.add_argument('--file_name', type=str, default="camera_params", help='camera params save file')
    return parser


def get_file(path):  # 获取文件路径
    img_path = []
    for root, dirs, files in os.walk(path):
        for file in files:
            img_path.append(os.path.join(root, file))
    return img_path


if __name__ == "__main__":
    args = get_parser().parse_args()

    params_dict = {}

    file_L = get_file(args.image_dir + 'left')
    file_R = get_file(args.image_dir + 'right')

    imgL = cv2.imread(file_L[2])
    imgR = cv2.imread(file_R[2])

    height, width = imgL.shape[0:2]

    calibration = Stereo_Camera_Calibration(args.width, args.height, args.lattice)
    left_K, left_D, right_K, right_D, R, T = calibration.stereo_calibration(file_L, file_R)
    map_lx, map_ly, map_rx, map_ry, Q = calibration.getRectifyTransform(width, height, left_K, left_D,
                                                                        right_K, right_D, R, T)

    # 查看校正效果
    img_ = calibration.draw_line(imgL, imgR)
    cv2.imshow("img", img_)
    rec_img_L, rec_img_R = calibration.get_rectify_img(imgL, imgR, map_lx, map_ly, map_rx, map_ry)
    img_show = calibration.draw_line(rec_img_L, rec_img_R)
    cv2.imshow("output", img_show)
    cv2.waitKey(0)

    params_dict['size'] = [width, height]
    params_dict['K1'] = left_K.tolist()
    params_dict['D1'] = left_D.tolist()
    params_dict['K2'] = right_K.tolist()
    params_dict['D2'] = right_D.tolist()
    params_dict['map_lx'] = map_lx.tolist()
    params_dict['map_ly'] = map_ly.tolist()
    params_dict['map_rx'] = map_rx.tolist()
    params_dict['map_ry'] = map_ry.tolist()
    params_dict['R'] = R.tolist()
    params_dict['T'] = T.tolist()
    params_dict['Q'] = Q.tolist()

    # =========== 保存相机参数 =========== #
    # 保存为.json文件
    file_path = args.save_dir + args.file_name + ".json"
    with jsonlines.open(file_path, "w") as f:
        json.dump(params_dict, f, indent=1)

    print("ALL Make Done!")

标定完成后,你会得到标定的内部参数,标定完之后就可以直接用内参数和畸变参数得到畸变校正图像。接下来就可以使用OpenCV了,即用内参数和畸变参数作为initUndistortRectifyMap()函数的输入,得到原图像与畸变校正图像的x,y坐标映射关系,即两个变换矩阵。再以这两个变换矩阵作为remap()函数的输入,得到畸变校正图像。

第二部分 角点后处理
opencv中findChessboardCorners的角点后处理。
libcbdetect中生长法,https://www.cvlibs.net/software/libcbdetect/

方法一:findChessboardCorners 边缘检测+四边形,迭代获得角点阵列

Opencv 棋盘定位(源码调试2)
https://blog.csdn.net/b5w2p0/article/details/18446961
opencv棋盘格角点检测原理总结
https://blog.csdn.net/guo1988kui/article/details/79273282

绘制点数
https://blog.csdn.net/qq_47947920/article/details/140326409
在这里插入图片描述
1、 需要提前指定棋盘格尺寸。这在很多自动化应用中是很难做到的。
2、 鲁棒性差。棋盘格如果有干扰(比如轻微的遮挡)就会使得检测失败,而且棋盘倾斜角度较大也会检测失败。具体测试见上述链接。
3、 无法处理一张图片包含多张棋盘的情况。

bool cv::findChessboardCorners ( InputArray image, Size patternSize, OutputArray corners, int flags = CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE)

这个函数用来检测一幅图像中是否含有棋盘格,如果图像中不含有指定数目的棋盘格角点(即黑色方块相交的点为角点,因此制作标定板时,最好选用大一点白色或浅色木板作为棋盘格背景)或对它们排序失败时,函数返回0; 如果图像中棋盘格内部角点都被确定了位置并准确排列的话,该函数将它们按制定行列排序并存储在corners向量中。该函数确定的角点的大概位置(approximate),如果想更准确地确定角点的位置,你可以该函数检测角点成功后继续调用cornerSubPix函数。

方法二:libcbdetect中生长法
https://www.cvlibs.net/software/libcbdetect/
Opencv之—棋盘格角点检测算法源码解析-matlab
https://blog.csdn.net/yohnyang/article/details/124516902
基于生长的棋盘格角点检测方法
https://blog.csdn.net/electech6/article/details/52770010

参考论文《Automatic Camera and Range Sensor Calibration using a single Shot》。可以针对性地解决上述问题。
优点:
1、 不需要提前指定棋盘格数目。
2、 鲁棒性好。因为是基于生长的算法,所以如果出现干扰,就会绕过干扰,生长出最大的棋盘。
3、 可以检测一个图片里包含多张棋盘的情况。
缺点:
1、受棋盘的矩形形状约束,只能生长出矩形的棋盘。严格的说也不能算缺点,因为本身棋盘就是矩形的,真的长出三头六臂还能叫棋盘吗。不过以后我会介绍一种不依赖棋盘约束的方法。
2、计算量较大。主要集中在棋盘生长部分。

在这里插入图片描述

其他 相关链接
使用opencv对棋盘格标定板进行角点检测并输出像素坐标 棋盘格标定板怎么用
https://blog.51cto.com/u_16213632/10354113
棋盘格角点检测
https://blog.csdn.net/qq_48034474/article/details/123252164
opencv角点检测、棋盘格检测、亚像素cvFindCornerSubPix()
https://www.cnblogs.com/sunniflyer/p/5442082.html
用python跑相机标定程序
https://bbs.csdn.net/topics/392561780
opencv
https://docs.opencv.org/4.3.0/

OpenCV : 2d - 3d point correspondence using chessboard
https://stackoverflow.com/questions/19024695/opencv-2d-3d-point-correspondence-using-chessboard

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值