草庐IT

4、OpenCV-Python双目标定流程

机器人涮火锅 2023-04-07 原文

文章目录


1、双目标定

   双目标定的目的是获取左右目相机的内参矩阵、畸变向量、旋转矩阵和平移矩阵。

   除了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的畸变。

2、双目校正

   双目校正的目的是得到立体校正所需的映射矩阵,然后对图像进行畸变校正和立体校正(极线校正)。
   立体校正最常见的校正方法就是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进行变换后,我们将得到修正后的图像。对于右相机,我们会用相同的步骤做一遍。至此双目校正部分就完成了。
   标定效果如下所示。可以看到原图中左右图像存在畸变,并且棋盘格位置明显未对齐;校正后左右图像畸变被消除,且极线对齐效果明显。
原图:

校正后:

4、参数保存

4.1 保存参数

   为方便查看和读取,首先将需要保存的参数放到一个空字典中,然后保存为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)

4.2 读取参数

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

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

5、代码示例

#-*- 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!")

有关4、OpenCV-Python双目标定流程的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

  3. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  4. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  5. Vscode+Cmake配置并运行opencv环境(Windows和Ubuntu大同小异) - 2

    之前在培训新生的时候,windows环境下配置opencv环境一直教的都是网上主流的vsstudio配置属性表,但是这个似乎对新生来说难度略高(虽然个人觉得完全是他们自己的问题),加之暑假之后对cmake实在是爱不释手,且这样配置确实十分简单(其实都不需要配置),故斗胆妄言vscode下配置CV之法。其实极为简单,图比较多所以很长。如果你看此文还配不好,你应该思考一下是不是自己的问题。闲话少说,直接开始。0.CMkae简介有的人到大二了都不知道cmake是什么,我不说是谁。CMake是一个开源免费并且跨平台的构建工具,可以用简单的语句来描述所有平台的编译过程。它能够根据当前所在平台输出对应的m

  6. python - 如何读取 MIDI 文件、更改其乐器并将其写回? - 2

    我想解析一个已经存在的.mid文件,改变它的乐器,例如从“acousticgrandpiano”到“violin”,然后将它保存回去或作为另一个.mid文件。根据我在文档中看到的内容,该乐器通过program_change或patch_change指令进行了更改,但我找不到任何在已经存在的MIDI文件中执行此操作的库.他们似乎都只支持从头开始创建的MIDI文件。 最佳答案 MIDIpackage会为您完成此操作,但具体方法取决于midi文件的原始内容。一个MIDI文件由一个或多个音轨组成,每个音轨是十六个channel中任何一个上的

  7. 「Python|Selenium|场景案例」如何定位iframe中的元素? - 2

    本文主要介绍在使用Selenium进行自动化测试或者任务时,对于使用了iframe的页面,如何定位iframe中的元素文章目录场景描述解决方案具体代码场景描述当我们在使用Selenium进行自动化测试的时候,可能会遇到一些界面或者窗体是使用HTML的iframe标签进行承载的。对于iframe中的标签,如果直接查找是无法找到的,会抛出没有找到元素的异常。比如近在咫尺的例子就是,CSDN的登录窗体就是使用的iframe,大家可以尝试通过F12开发者模式查看到的tag_name,class_name,id或者xpath来定位中的页面元素,会抛出NoSuchElementException异常。解决

  8. python ffmpeg 使用 pyav 转换 一组图像 到 视频 - 2

    2022/8/4更新支持加入水印水印必须包含透明图像,并且水印图像大小要等于原图像的大小pythonconvert_image_to_video.py-f30-mwatermark.pngim_dirout.mkv2022/6/21更新让命令行参数更加易用新的命令行使用方法pythonconvert_image_to_video.py-f30im_dirout.mkvFFMPEG命令行转换一组JPG图像到视频时,是将这组图像视为MJPG流。我需要转换一组PNG图像到视频,FFMPEG就不认了。pyav内置了ffmpeg库,不需要系统带有ffmpeg工具因此我使用ffmpeg的python包装p

  9. Python 刷Leetcode题库,顺带学英语单词(31) - 2

    ValidPalindromeGivenastring,determineifitisapalindrome,consideringonlyalphanumericcharactersandignoringcases. [#125]Example:"Aman,aplan,acanal:Panama"isapalindrome."raceacar"isnotapalindrome.Haveyouconsiderthatthestringmightbeempty?Thisisagoodquestiontoaskduringaninterview.Forthepurposeofthisproblem

  10. python - 是否可以使用 Ruby 或 Python 禁用 anchor /引用来发出有效的 YAML? - 2

    是否可以在PyYAML或Ruby的Psych引擎中禁用创建anchor和引用(并有效地显式列出冗余数据)?也许我在网上搜索时遗漏了一些东西,但在Psych中似乎没有太多可用的选项,而且我也无法确定PyYAML是否允许这样做.基本原理是我必须序列化一些数据并将其以可读的形式传递给一个不是真正的技术同事进行手动验证。有些数据是多余的,但我需要以最明确的方式列出它们以提高可读性(anchor和引用是提高效率的好概念,但不是人类可读性)。Ruby和Python是我选择的工具,但如果有其他一些相当简单的方法来“展开”YAML文档,它可能就可以了。 最佳答案

随机推荐