相机标定实战之双目标定

news2024/11/26 22:41:02

相机标定原理


文章目录

  • 相机标定原理
  • 前言
  • 一、采集图像
  • 二、基于Matlab单双目标定流程
    • 采集棋盘图
  • 三、基于OpenCV-Python双目标定流程
    • 检测棋盘格角点
    • 对角点进行亚像素精细化
    • 单目标定
    • 双目标定
    • 双目校正
    • 保存标定参数
    • 读取标定参数
    • 代码示例
  • 参考


前言

相机标定可以说是计算机视觉/机器视觉的基础,也是面试过程中经常出现的问题。相机标定涉及的知识面很广,成像几何、镜头畸变、单应矩阵、非线性优化等。在双目测距系统中,相机标定能消除畸变,进行立体校正,从而提高视差计算的准确性,这样才能得到精确的深度图。

首先需要准备一张棋盘,如下图所示。对于标定不同测距范围相机所用的棋盘方格宽度会有所不同。对于短焦双目相机(测距范围在20m以内),棋盘中方格的宽度达到20mm即可;对于长焦双目相机(测距范围在40m左右),棋盘中方格的宽度需要尽量大,否则会影响标定的精度,一般至少达到60mm。
在这里插入图片描述尽量拍摄多组照片,这样可以提高标定效果,标定效果的好坏直接影响到测距的精度。对于短焦相机通常拍摄40组照片即可;长焦相机通常会需要更多组照片,标定长焦相机时拍摄了60组。

一、采集图像

如果使用的是左右图像拼接1个USB接口输出的双目摄像头,在采集图像时需要注意其图像拼接的顺序,例如我使用的双目摄像头拼接顺序从左至右为右目——左目(不知道是不是都是这么拼接的,还是厂家搞错了),这个问题处理不好可能会导致后面的处理结果都不对,所以这里特别说明一下。
在这里插入图片描述

import os
import argparse
import cv2

class StereoCamera(object):
    """采集双目标定图片,按键盘【c】或【s】保存图片"""
 
    def __init__(self, chess_width, chess_height, detect=False):
        """
        :param chess_width: chessboard width size,即棋盘格宽方向黑白格子相交点个数,
        :param chess_height: chessboard height size,即棋盘格长方向黑白格子相交点个数
        :param detect: 是否实时检测棋盘格,方便采集数据
        """
        self.chess_width = chess_width
        self.chess_height = chess_height
        self.detect = detect
        self.criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
 
    def detect_chessboard(self, image):
        """检测棋盘格并显示"""
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        ret, corners = cv2.findChessboardCorners(gray, (self.chess_width, self.chess_height), None)
        if ret:
            # 角点精检测
            corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), self.criteria)
            # Draw and display the corners
            image = cv2.drawChessboardCorners(image, (self.chess_width, self.chess_height), corners2, ret)
        return image
 
    def capture2(self, left_video, right_video, save_dir):
        """
        用于采集双USB连接线的双目摄像头
        :param left_video:int or str,左路视频路径或者摄像头ID
        :param right_video:int or str,右视频路径或者摄像头ID
        :param save_dir: str,保存左右图片的路径
        :return:
        """
        self.create_file(save_dir)
        capL = cv2.VideoCapture(left_video)
        capR = cv2.VideoCapture(right_video)
        widthL, heightL, numFramesL, fpsL = self.get_video_info(capL)
        widthR, heightR, numFramesR, fpsR = self.get_video_info(capR)
        print("capL:\n", widthL, heightL, numFramesL, fpsL)
        print("capR:\n", widthR, heightR, numFramesR, fpsR)
        save_videoL = self.create_file(save_dir, "video", "left_video.avi")
        save_videoR = self.create_file(save_dir, "video", "right_video.avi")
        writerL = self.get_video_writer(save_videoL, widthL, heightL, fpsL)
        writerR = self.get_video_writer(save_videoR, widthR, heightR, fpsR)
        i = 0
        while True:
            isuccessL, frameL = capL.read()
            isuccessR, frameR = capR.read()
            if not (isuccessL and isuccessR):
                print("No more frames")
                break
            if self.detect:
                l = self.detect_chessboard(frameL.copy())
                r = self.detect_chessboard(frameR.copy())
            else:
                l = frameL.copy()
                r = frameR.copy()
            cv2.imshow('left', l)
            cv2.imshow('right', r)
            key = cv2.waitKey(10)
            if key == ord('q'):
                break
            elif key == ord('c') or key == ord('s'):
                print("save image:{:0=3d}".format(i))
                cv2.imwrite(os.path.join(save_dir+"/left", "left_{:0=3d}.png".format(i)), frameL)
                cv2.imwrite(os.path.join(save_dir+"/right", "right_{:0=3d}.png".format(i)), frameR)
                i += 1
            writerL.write(frameL)
            writerR.write(frameR)
        capL.release()
        capR.release()
        cv2.destroyAllWindows()
 
    def capture1(self, video, save_dir):
        """
        用于采集单USB连接线的双目摄像头(左右摄像头被拼接在同一个视频中显示)
        :param video:int or str,视频路径或者摄像头ID
        :param save_dir: str,保存左右图片的路径
        """
        self.create_file(save_dir)
        cap = cv2.VideoCapture(video)


        width, height, numFrames, fps = self.get_video_info(cap)
        print("capL:\n", width, height, numFrames, fps)
        save_videoL = self.create_file(save_dir, "video", "left_video.avi")
        save_videoR = self.create_file(save_dir, "video", "right_video.avi")
        writerL = self.get_video_writer(save_videoL, int(width / 2), height, fps)
        writerR = self.get_video_writer(save_videoR, int(width / 2), height, fps)
        i = 0
        while True:
            isuccess, frame = cap.read()
            if not isuccess:
                print("No more frames")
                break
            # 分离左右摄像头
            # 注意:需要根据摄像头画面分割左右图像
            frameL = frame[:, int(width / 2):, :]
            frameR = frame[:, :int(width / 2), :]


            if self.detect:
                l = self.detect_chessboard(frameL.copy())
                r = self.detect_chessboard(frameR.copy())
            else:
                l = frameL.copy()
                r = frameR.copy()
            cv2.imshow('left', l)
            cv2.imshow('right', r)
            key = cv2.waitKey(10)
            if key == ord('q'):
                break
            elif key == ord('c') or key == ord('s'):
                print("save image:{:0=3d}".format(i))
                cv2.imwrite(os.path.join(save_dir+"/left", "left_{:0=3d}.png".format(i)), frameL)
                cv2.imwrite(os.path.join(save_dir+"/right", "right_{:0=3d}.png".format(i)), frameR)
                i += 1
            writerL.write(frameL)
            writerR.write(frameR)
        cap.release()
        cv2.destroyAllWindows()
 
    @staticmethod
    def get_video_info(video_cap):
        width = int(video_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        height = int(video_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        numFrames = int(video_cap.get(cv2.CAP_PROP_FRAME_COUNT))
        fps = int(video_cap.get(cv2.CAP_PROP_FPS))
        return width, height, numFrames, fps
 
    @staticmethod
    def get_video_writer(save_path, width, height, fps):
        if not os.path.exists(os.path.dirname(save_path)):
            os.makedirs(os.path.dirname(save_path))
        fourcc = cv2.VideoWriter_fourcc(*'XVID')
        frameSize = (int(width), int(height))
        video_writer = cv2.VideoWriter(save_path, fourcc, fps, frameSize)
        print("video:width:{},height:{},fps:{}".format(width, height, fps))
        return video_writer
 
    @staticmethod
    def create_file(parent_dir, dir1=None, filename=None):
        out_path = parent_dir
        if dir1:
            out_path = os.path.join(parent_dir, dir1)
        if not os.path.exists(out_path):
            os.makedirs(out_path)
        if filename:
            out_path = os.path.join(out_path, filename)
        return out_path
 
 
def get_parser():
    width = 12
    height = 8
    left_video = 0
    right_video = -1
    save_dir = "data/"
    parser = argparse.ArgumentParser(description='Camera calibration')
    parser.add_argument('--detect', type=bool, default=True, help='detect corner')
    parser.add_argument('--width', type=int, default=width, help='chessboard width size')
    parser.add_argument('--height', type=int, default=height, help='chessboard height size')
    parser.add_argument('--left_video', type=int, default=left_video, help='left video file or camera ID')
    parser.add_argument('--right_video', type=int, default=right_video, help='right video file or camera ID')
    parser.add_argument('--save_dir', type=str, default=save_dir, help='YML file to save calibrate matrices')
    return parser
 
 
if __name__ == '__main__':
    args = get_parser().parse_args()
    stereo = StereoCamera(args.width, args.height, detect=args.detect)
    if args.left_video > -1 and args.right_video > -1:
        # 双USB连接线的双目摄像头
        stereo.capture2(left_video=args.left_video, right_video=args.right_video, save_dir=args.save_dir)
    elif args.left_video > -1:
        # 单USB连接线的双目摄像头(左右摄像头被拼接在同一个视频中显示)
        stereo.capture1(video=args.left_video, save_dir=args.save_dir)
    else:
        raise Exception("Error: Check your camera{}".format(args.left_video, args.right_video))

二、基于Matlab单双目标定流程

采集棋盘图

尽量让棋盘占据照片中最多的画面(不小于1\3)
尽量让棋盘格出现在图像中的各个位置(尤其图像的四个角)
拍摄的棋盘图是具有多个角度的(前倾,后倾,左倾,右倾,斜倾等)
拍摄的棋盘图要清晰可辩,最好是在最清晰的范围内拍摄
拍摄照片数量要多一点,推荐20张以上

https://blog.csdn.net/leonardohaig/article/details/81254179

三、基于OpenCV-Python双目标定流程

双目标定的目的是获取左右目相机的内参矩阵、畸变向量、旋转矩阵和平移矩阵。除了Matlab的标定工具箱之外,OpenCV同样也实现了张友正标定法,而我们只需要调用相关的函数即可对相机进行标定。
双目相机标定步骤:

检测棋盘格角点

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)  # 提取右图每一张图片的角点

对角点进行亚像素精细化

criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
cv2.cornerSubPix(ChessImaL, cornersL, (11, 11), (-1, -1), criteria)  # 亚像素精确化,对粗提取的角点进行精确化
cv2.cornerSubPix(ChessImaR, cornersR, (11, 11), (-1, -1), criteria)  # 亚像素精确化,对粗提取的角点进行精确化

单目标定

#   左侧相机单独标定
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)

双目标定

criteria_stereo = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-5)

flags = 0
flags |= cv2.CALIB_FIX_INTRINSIC

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

我们要注意函数中的flags

CV_CALIB_FIX_INTRINSIC:固定K和D矩阵。这是默认标志。如果你校准好你的相机,那就只求解𝑅,𝑇,𝐸,𝐹。
CV_CALIB_USE_INTRINSIC_GUESS: K和D个矩阵将被优化。对于这个计算,你应该给出经过良好校准的矩阵,以便(可能)得到更好的结果。
CV_CALIB_FIX_PRINCIPAL_POINT: 修复K矩阵中的参考点。
CV_CALIB_FIX_FOCAL_LENGTH: 在K矩阵中固定焦距。
CV_CALIB_FIX_ASPECT_RATIO: 固定长宽比。
CV_CALIB_SAME_FOCAL_LENGTH: 校准焦距,并设置Fx和Fy相同的校准结果。
CV_CALIB_ZERO_TANGENT_DIST: 去掉畸变。
CV_CALIB_FIX_K1,, CV_CALIB_FIX_K6: 移除K1到K6的畸变。

双目校正

双目校正的目的是得到立体校正所需的映射矩阵,然后对图像进行畸变校正和立体校正(极线校正)。立体校正最常见的校正方法就是Bouguet极线校正方法。
Bouguet极线校正方法:左右相机成像平面各旋转一半,使得左右图像重投影造成的误差最小,左右视图的共同面积最大。

# 左校正变换矩阵、右校正变换矩阵、左投影矩阵、右投影矩阵、深度差异映射矩阵
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.9)

在这个函数中,只有一个标志CALIB_ZERO_DISPARITY,它用于匹配图像之间的y轴。alpha值用于转换后的黑色部分,因为图像会旋转,而显示的图像大小不会改变,所以一些图像边缘部分会是黑色的,而原始图像会小得多:

alpha= -1: 让 OpenCV 优化黑色部分。
alpha= 0 : 旋转和裁切图像,使没有黑色的部分。这个选项在大多数情况下会严重削减图像,你不会得到一个像样的高质量的图像,但可以一试。
alpha= 1 : 进行变换,但不要裁切任何部分。
alpha= experimental: 尝试设置不同的值,在某个特定的alpha值,可能有一些黑色的区域,但图像质量整体高。
# 计算畸变矫正和立体校正的映射变换。
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)

# 得到畸变校正和立体校正后的图像
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)

initUndistortRectifyMap函数可以同时实现图像的畸变校正和校准。对于左相机,我们使用K1(相机矩阵)和D1(失真矩阵)进行畸变校正,使用R1(从左到右旋转)和P1(从左到右投影矩阵)进行校正。在对remap进行变换后,我们将得到修正后的图像。对于右相机,我们会用相同的步骤做一遍。至此双目校正部分就完成了。
   标定效果如下所示。可以看到原图中左右图像存在畸变,并且棋盘格位置明显未对齐;校正后左右图像畸变被消除,且极线对齐效果明显。
在这里插入图片描述
在这里插入图片描述

保存标定参数

为方便查看和读取,首先将需要保存的参数放到一个空字典中,然后保存为json文件。如下所示:

params_dict = {}
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['left_map_x']  = map_lx.tolist()
params_dict['left_map_y']  = map_ly.tolist()
params_dict['right_map_x'] = map_rx.tolist()
params_dict['right_map_y'] = 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 open(file_path,"w") as f:
        json.dump(params_dict, f, indent=1)

读取标定参数

# 读取.json文件
with open(params_file, "r") as f:
    dict = json.load(f)

for d in dict:
    dict[d] = np.asarray(dict[d], "f")

代码示例

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

import json
import pickle



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)
        
        # 左内参矩阵、左畸变向量、右内参矩阵、右畸变向量、旋转矩阵、平移矩阵
        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=12, help='chessboard width size')
    parser.add_argument('--height', type=int, default=8, 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/", help='images path')
    parser.add_argument('--save_dir', type=str, default="config/", 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 open(file_path,"w") as f:
        json.dump(params_dict, f, indent=1)

    print("ALL Make Done!")

参考

https://ww2.mathworks.cn/help/vision/ug/stereo-camera-calibrator-app.html
https://blog.csdn.net/baidu_39231810/article/details/128628651

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/643429.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

哪个公司的 CEO 不想拥有一个自己的数字克隆?

⚠️ FBI Warning:本文纯属作者自娱自乐,数字人的观点不代表 CEO 本人的观点,请大家不要上当受骗!! 哪个公司的 CEO 不想拥有一个自己的数字克隆? 想象🤔一下,如果 CEO 数字克隆上线…

python基础语法总结

1.打印输出 print(“Hello World”) 在许多大众的编程语言中,需要在每个语句的末尾添加分号,但Python并非如此。Python是一种简洁的编程语言,你不需要添加不必要的字符和语法。在Python中,一条语句结束于一行的结尾(方括号&…

找不到msvcp120dll,无法继续执行代码的修复方法

本教程操作系统:Windows系统、 msvcp120.dll是电脑文件中的dll文件(动态链接库文件)。如果计算机中丢失了某个dll文件,可能会导致某些软件和游戏等程序无法正常启动运行,并且导致电脑系统弹窗报错。 msvcp120.dll文件…

IDEA整合GO并创建module工程

IDEA整合Go 安装包环境配置idea配置并创建test mode 安装包 1.去官网下载对应还的安装包 官网下载地址 我选择下载的window 版本: 直接按照对应的目录,然后点击下一步 环境配置 1.配置go环境变量 在高级环境变量PAHT中添加安装包的**/bin 目录&…

mysql数据库的基础

mysql数据库 一、数据库的基本概念二、关系数据库三、SQL语句增改查删 四、natvicat for mysql软件 一、数据库的基本概念 数据(data) 描述事物的符号记录包括数字、文字、图形、图像、声音、档案记录等以“记录”形式按统一的格式进行存储 表&#x…

基于Java少儿编程网上报名系统设计与实现(源码+lw+部署文档+讲解等)

博主介绍: ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ 🍅 文末获取源码联系 🍅 👇🏻 精…

sizeof 和 strlen !!!

定义 sizeof()是单目操作符,是可以求变量(类型)所占空间的大小,不在乎内存中存放的是什么,只在乎内存大小 strlen()是函数,是计算字符串的长度的 它从内存的某个位置(可以是字符串开头&#x…

进程管道:pipe调用

在看过高级的popen函数之后&#xff0c;我们再来看看底层的pipe函数。通过这个函数在两个程序之间传递数据不需要启动一个shell来解释请求的命令。它同时还提供了对读写数据的更多控制。pipe函数的原型如下所示&#xff1a; #include <unistd.h> int pipe(int pipefd[2])…

OS的事件机制-Event

Event在OSEK OS中&#xff0c;其实就是一个flag的作用&#xff0c;如果某个TASK执行了&#xff0c;就调用<SetEvent()>把flag就置起来&#xff0c;和这个task关联的另一个TASK也执行了&#xff0c;就可以把flag清掉<ClearEvent>&#xff0c;如果第一个TASK没有执行…

这才是你想了解的Redis

文章简介 redis作为一个基于内存的数据结构存储系统&#xff0c;由于它的灵活性和可拓展性强&#xff0c;在我们日常开发中经常被用作数据库、缓存或者消息代理。本文就从Redis的基本部署使用说到Redis的集群、锁和消息对列 Redis基本使用 一、安装 下载地址&#xff1a;htt…

mySql 储存过程 多个结果返回解析

当需要查询复杂的数据模型并返回多个结果集时&#xff0c;使用 MySQL 存储过程可以有效地优化性能。同时&#xff0c;在开发中使用 Mybatis 可以方便地调用 MySQL 存储过程并获取多个结果集。本文将介绍如何在 Mybatis 中调用 MySQL 存储过程&#xff0c;并获取多个结果集。 1、…

Java并发之原子类

一、原子类简介 1 什么是原子类 Java中提供了一些原子类&#xff0c;原子类包装了一个变量&#xff0c;并且提供了一系列对变量进行原子性操作的方法。原子性的意思是对于一组操作&#xff0c;要么全部执行成功&#xff0c;要么全部执行失败&#xff0c;不能只有其中某几个执…

基础知识学习---牛客网C++面试宝典(三)C/C++基础之面向对象

1、本栏用来记录社招找工作过程中的内容&#xff0c;包括基础知识学习以及面试问题的记录等&#xff0c;以便于后续个人回顾学习&#xff1b; 暂时只有2023年3月份&#xff0c;第一次社招找工作的过程&#xff1b; 2、个人经历&#xff1a; 研究生期间课题是SLAM在无人机上的应…

测试新手百科:Postman简介、安装、入门使用方法详细攻略!

本文关键词&#xff1a;Postman基础 目录 一、Postman背景介绍 二、Postman的操作环境 三、Postman下载安装 四、Postman的基础功能 五、接口请求流程 六、管理用例—Collections 七、身份验证Authentication 一、Postman背景介绍 用户在开发或者调试网络程序或者是网…

【答题】在线答卷-答题系统的微信小程序开发流程详解

用死记硬背的方法学习的学生&#xff0c;面对桌上堆积成厚厚的书本&#xff0c;是否感觉鸭梨山大呢&#xff0c;想着教育却面临着学习成本不小问题&#xff0c;是否感觉各种不便呢&#xff0c;如果对编程代码有感兴趣&#xff0c;不妨试试做一个自己的在线答题系统&#xff0c;…

有效性常见标志词

有效性常见标志词 混淆概念常见标志词 &#xff08; 1 &#xff09; 既然…那么… &#xff08; 2 &#xff09; 也就是说… &#xff08; 3 &#xff09; 很显然… &#xff08; 4 &#xff09; 因为A 就是B…所以… &#xff08; 5 &#xff09; 某主体A 是 &#xff0c;…

低代码01之构建项目框架

目录 低代码之构建框架11&#xff1a;项目初始化2&#xff1a;src / data.json 数据 &#xff08; 容器大小与渲染的表单数据 &#xff09;3&#xff1a;App.vue ( 导入editor组件传递data.json之中的数据与 向下提供组件配置数据config )4&#xff1a;src / packages / editor…

我记不住的那些C语言的二维数组的函数传参

背景&#xff1a; 最近在复习数据结构和算法&#xff0c;顺带刷刷题&#xff0c;虽然很长时间不刷题了但还是原来熟悉的味道&#xff0c;每一次重学都是加深了上一次的理解。本次我们看一下如何将C语言的二维数组进行函数传参&#xff0c;C语言实现。 其实这个比较简单&#x…

springboot项目使用proguard配置代码混淆

springboot项目使用proguard配置代码混淆 代码混淆是一些软件开发过程中必不可少的步骤。 常用的代码混淆技术有 proguard maven plugin , yguard maven plugin, procyon maven plugin, dex maven plugin . 这些代码混淆技术大同小异&#xff0c;都是对maven打包生成class时进…

补充知识点

这里写目录标题 进制转换Java内置的进制转换介绍具体代码 有符号数据表示法整数强制转换之数据溢出浮点数进制转换浮点数储存 进制转换 Java内置的进制转换 介绍 也就是常用API里Integer的静态方法 具体代码 注意 最后一个方法&#xff0c;返回的是基于第二个参数为基数的第…