python-opencv双目图像矫正

python-opencv双目图像矫正

最近在搞双目视觉矫正,采用的是张征友标定法。主要步骤包括:获取相机1和相机2的标定图片,对标定图片进行预处理 (裁剪、分辨率匹配)、然后利用opencv的函数库对图像进行矫正
核心代码主要是参考这篇博文 ,关于张征友标定法的理论大家可以去看刚才上面那篇博文,讲的很详细
本人在原有的基础根据自己的需求进行了一些改动以及注释的补充,直接上代码:

import numpy as np
import cv2
import os
import datetime

def cam_calib_find_corners(img,col_num, row_num):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    ret, corners2 = cv2.findChessboardCorners(gray ,(row_num ,col_num), None)
    # 为了得到稍微精确一点的角点坐标,进一步对角点进行亚像素寻找
    # corners2 = cv2.cornerSubPix(gray, corners, (5, 5), (-1, -1), (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_COUNT, 10, 0.001))
    return (ret, corners2)


def stereo_calib_calibrate(img_dir_L ,img_dir_R ,row_num ,col_num , square_sz):
    """
    对图片进行处理并返回双目相机相关参数
    :param img_dir_L: 相机1图像路径 
    :param img_dir_R: 相机2图像路径
    :param row_num:   行数
    :param col_num:   列数
    :param square_sz: 单个格子实际物理尺寸 单位:mm
    :param square_sz: 单个格子实际物理尺寸 单位:mm
    :return: 以字典形式返回双目相机矫正参数以及处理图像文件保存路径
    retval, cameraMatrix1, distCoeffs1, cameraMatrix2, distCoeffs2, R, T, E, F
    """
    w = 640
    h = 480
    all_cornersL = []
    all_cornersR = []
    patterns = []

    # 标定相机,先搞这么一个假想的板子,标定就是把图像中的板子往假想的板子上靠,靠的过程就是计算参数的过程
    # 比如说(0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
    pattern_points = np.zeros(( row_num * col_num, 3), np.float32)
    pattern_points[:, :2] = np.mgrid[0:row_num, 0:col_num].T.reshape(-1, 2)
    pattern_points *= square_sz

    png_list_r = os.listdir(img_dir_R)
    png_list_l = os.listdir(img_dir_L)
    png_list_l = sorted(png_list_l)
    png_list_r = sorted(png_list_r)

    folder = "./out/{}".format(datetime.datetime.now().strftime("%Y%m%d_%H%M%S"))

    os.makedirs(folder, exist_ok=True)
    for i in range(len(png_list_l)):
        png_path_r = img_dir_R + "/" + png_list_r[i]
        png_path_l = img_dir_L + "/" + png_list_l[i]
        print(png_path_l,png_path_r)
        img_r = cv2.imread(png_path_r)
        img_l = cv2.imread(png_path_l)
        img_l = img_l[135:413, 169:498]
        img_l = cv2.resize(img_l, (w,h))
        # 保存预处理后的图像
        pre_folder = folder +"/pre"
        os.makedirs(pre_folder, exist_ok=True)
        pre_png = pre_folder + "/"  + png_list_l[i]
        cv2.imwrite(pre_png,img_l)


        # 获取角点
        ret_r, corners_r = cam_calib_find_corners(img_r, col_num, row_num)
        ret_l, corners_l = cam_calib_find_corners(img_l, col_num, row_num)

        # 合并所有角点
        all_cornersL.append(corners_l)
        all_cornersR.append(corners_r)
        patterns.append(pattern_points)

    # rms表示的是重投影误差;
    # cameraMatrix是相机的内参矩阵;
    # distCoeffs表述的相机畸变参数;
    # rvecs表示标定棋盘格世界坐标系到相机坐标系的旋转参数:rotation vectors,需要进行罗德里格斯转换;
    # tvecs表示translation vectors,主要是平移参数。
    # https://blog.csdn.net/m0_49332456/article/details/121011500
    rmsL, cameraMatrixL, distCoeffsL, rvecsL, tvecsL = cv2.calibrateCamera(patterns, all_cornersL, (w, h), None, None)
    rmsR, cameraMatrixR, distCoeffsR, rvecsR, tvecsR = cv2.calibrateCamera(patterns, all_cornersR, (w, h), None, None)

    # 计算两个相机之间的关系矩阵模型
    flags = 0
    flags |= cv2.CALIB_FIX_FOCAL_LENGTH

    """
          flag
     "    CV_CALIB_FIX_INTRINSIC            如果该标志被设置,那么就会固定输入的cameraMatrix和distCoeffs不变,只求解R,T,E,F\n"
     "    CV_CALIB_USE_INTRINSIC_GUESS      根据用户提供的cameraMatrix和distCoeffs为初始值开始迭代\n"
     "    CV_CALIB_FIX_PRINCIPAL_POINT      迭代过程中不会改变主点的位置\n"
     "    CV_CALIB_FIX_FOCAL_LENGTH         迭代过程中不会改变焦距 \n"
     "    CV_CALIB_SAME_FOCAL_LENGTH        强制保持两个摄像机的焦距相同 CV_CALIB_ZERO_TANGENT_DIST 切向畸变保持为零\n"
     "    CV_CALIB_FIX_K1,…,CV_CALIB_FIX_K6 迭代过程中不改变相应的值。如果设置了CV_CALIB_USE_INTRINSIC_GUESS 将会使用用户提供的初始值,否则设置为零\n"
     "    CV_CALIB_RATIONAL_MODEL           畸变模型的选择,如果设置了该参数,将会使用更精确的畸变模型,distCoeffs的长度就会变成8\n"
     "    原文链接:https://blog.csdn.net/weixin_51229250/article/details/120218209\n"
    """
    stereocalib_criteria = (cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS, 1, 1e-5)
    # return :retval, cameraMatrix1, distCoeffs1, cameraMatrix2, distCoeffs2, R, T, E, F
    ret, Ml, dl, Mr, dr, R, T, E, F = cv2.stereoCalibrate(  patterns,
                                                            all_cornersL,
                                                            all_cornersR,
                                                            cameraMatrixL,
                                                            distCoeffsL,
                                                            cameraMatrixR,
                                                            distCoeffsR,
                                                            (w, h),
                                                            criteria=stereocalib_criteria,
                                                            flags=flags)

    print('Intrinsic_mtx_l', Ml)
    print('dist_l', dl)
    print('Intrinsic_mtx_r', Mr)
    print('dist_r', dr)
    print('R', R)
    print('T', T)
    print('E', E)
    print('F', F)

    camera_model = dict([('Ml', Ml), ('Mr', Mr), ('dl', dl),
                         ('dr', dr), ('rl', rvecsL),
                         ('rr', rvecsR), ('R', R), ('T', T),
                         ('E', E), ('F', F)])
    return camera_model,folder


def stereo_calib_correct(img_dir_L, new_img_dir_R, cameraModel):
    """
    :param crct_img_L_dir: 待矫正左相机图片路径
    :param crct_img_R_dir: 待矫正右相机图片路径
    :param cameraModel:    已获取相机参数
    :return:
    """
    w = 640
    h = 480
    png_list_r = os.listdir(new_img_dir_R)
    png_list_l = os.listdir(img_dir_L)


    png_list_l = sorted(png_list_l)
    png_list_r = sorted(png_list_r)

    for i in range(len(png_list_l)):
        png_path_r = new_img_dir_R + "/" + png_list_r[i]
        png_path_l = img_dir_L + "/" + png_list_l[i]
        print(png_path_l,png_path_r)
        img_r = cv2.imread(png_path_r)
        img_l = cv2.imread(png_path_l)
        grayL = cv2.cvtColor(img_l, cv2.COLOR_BGR2GRAY)
        grayR = cv2.cvtColor(img_r, cv2.COLOR_BGR2GRAY)

    # 极线矫正,也就是把两幅图的极线搞成水平,就像在博客中描述的那样,把任意位置的像平面,搞成两个平行的像平面
    """
        用于双目相机的立体校正环节
        https://blog.csdn.net/qq_41685265/article/details/105777229
        stereoRectify(cameraMatrix1, distCoeffs1, cameraMatrix2, distCoeffs2, imageSize, R, T[, R1[, R2[, P1[, P2[, Q
        [,  flags[, alpha[, newImageSize]]]]]]]])
         -> R1, R2, P1, P2, Q, validPixROI1, validPixROI2
        cameraMatrix1 第一个摄像机的摄像机矩阵,即左相机相机内参矩阵,矩阵第三行格式应该为 0 0 1
        distCoeffs1    第一个摄像机的畸变向量
        cameraMatrix2  第一个摄像机的摄像机矩阵,即右相机相机内参矩阵,矩阵第三行格式应该为 0 0 1
        distCoeffs2    第二个摄像机的畸变向量
        imageSize      图像大小
        R-             相机之间的旋转矩阵,这里R的意义是:相机1通过变换R到达相机2的位姿 划重点!!!
        T-             左相机到右相机的平移矩阵
        R1             输出矩阵,第一个摄像机的校正变换矩阵(旋转变换)
        R2             输出矩阵,第二个摄像机的校正变换矩阵(旋转矩阵)
        P1             输出矩阵,第一个摄像机在新坐标系下的投影矩阵
        P2             输出矩阵,第二个摄像机在想坐标系下的投影矩阵
        Q              4*4的深度差异映射矩阵
        flags          可选的标志有两种零或者 CV_CALIB_ZERO_DISPARITY ,如果设置 CV_CALIB_ZERO_DISPARITY 的话,该函数会让两幅校正后的图像的主点有相同的像素坐标。否则该函数会水平或垂直的移动图像,以使得其有用的范围最大
        alpha          拉伸参数。如果设置为负或忽略,将不进行拉伸。如果设置为0,那么校正后图像只有有效的部分会被显示(没有黑色的部分),如果设置为1,那么就会显示整个图像。设置为0~1之间的某个值,其效果也居于两者之间。
        newImageSize   校正后的图像分辨率,默认为原分辨率大小。
        validPixROI1   可选的输出参数,Rect型数据。其内部的所有像素都有效
        validPixROI2   可选的输出参数,Rect型数据。其内部的所有像素都有效
    """

    '''
        Rl, Rr, Pl, Pr, Q, validPixROIl, validPixROIr =  cv2.stereoRectify(cameraModel['Ml'], cameraModel['dl'], cameraModel['Mr'], cameraModel['dr'], (w ,h), cameraModel['R'], cameraModel['T'])
        mapl_1, mapl_2 = cv2.initUndistortRectifyMap(cameraModel['Ml'], cameraModel['dl'], Rl, Pl, (w,h), cv2.CV_32FC1)
        mapr_1, mapr_2 = cv2.initUndistortRectifyMap(cameraModel['Mr'], cameraModel['dr'], Rr, Pr, (w,h), cv2.CV_32FC1)
        rltl = cv2.remap(grayL, mapl_1, mapl_2, cv2.INTER_LINEAR)
        rltr = cv2.remap(grayR, mapr_1, mapr_2, cv2.INTER_LINEAR)
    '''

    """
        getOptimalNewCameraMatrix() 用于去除畸变矫正后图像四周黑色的区域
        getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, alpha[, newImgSize[, centerPrincipalPoint]]) -> retval, validPixROI
        InputArray 	cameraMatrix,                     // 原相机内参矩阵
        InputArray 	distCoeffs,                       // 原相机畸变参数
        Size 	        imageSize,                    // 图像尺寸
        double 	        alpha,                        // 缩放比例  //当alpha=1时,所有像素均保留,但存在黑色边框; //当alpha=0时,损失最多的像素,没有黑色边框。		            
        Size 	        newImgSize = Size(),          // 校正后的图像尺寸
        Rect * 	        validPixROI = 0,              // 输出感兴趣区域设置
        bool 	        centerPrincipalPoint = false  // 可选标志
        https://blog.csdn.net/weixin_48592526/article/details/120393764
        ------------------------------------------------------------------------------------------------------------------------------------------
        undistort(src, cameraMatrix, distCoeffs[, dst[, newCameraMatrix]]) -> dst
        对图像进行变换,以补偿径向和切向的镜头失真,该函数是 initUndistortRectifyMap 和 remap 函数的简单组合
        @INPUT
        @param src 输入(扭曲的)图像。
        @param cameraMatrix 输入相机矩阵A
        @param distCoeffs 输入失真系数的向量的4、5、8、12或14个元素。如果该向量为NULL/空,则假定失真系数为零。
        @param newCameraMatrix 扭曲图像的相机矩阵。默认情况下,它与cameraMatrix相同,但你可以通过使用不同的矩阵来对结果进行额外的缩放和移动。
        @OUTPUT
        @param dst 输出(修正的)图像,其大小和类型与 src 相同 .
        -------------------------------------------------------------------------------------------------------------------------------------------
        concatenate((a1, a2, ...), axis=0, out=None, dtype=None, casting="same_kind")
        沿着一个现有的轴连接一个数组序列
        a1, a2, ...    :   类似数组的序列,这些数组必须具有相同的形状,除了在对应的 "轴"(默认是第一个)
        axis (int, optional)      数组拼接方向选择 0-按列拼接 1-按行拼接 NONE-压缩成1×n的array,n为元素数量
        out  (ndarray, 可选)      如果提供,是放置结果的目的地。其形状必须是形状必须正确,与没有指定out参数时concatenate将返回的结果相匹配的out参数
        dtype : str或dtype如果提供,目标数组将具有该dtype。不能和`out`一起提供。
        casting : {'no', 'equiv', 'safe', 'same_kind', 'unsafe'}, optional    控制可能发生的数据转换。默认为'same_kind'。
    """
        # 图像输出输出尺寸保持不变 获取新左右相机的内参矩阵
        newcameramtxL ,roiL = cv2.getOptimalNewCameraMatrix(cameraModel['Ml'], cameraModel['dl'] ,(w ,h) ,1 ,(w ,h))
        newcameramtxR ,roiR = cv2.getOptimalNewCameraMatrix(cameraModel['Mr'], cameraModel['dr'] ,(w ,h) ,1 ,(w ,h))

		dstL = cv2.undistort(grayL, cameraModel['Ml'], cameraModel['dl'], None, newcameramtxL)
        dstR = cv2.undistort(grayR, cameraModel['Mr'], cameraModel['dr'], None, newcameramtxR)

        # 保存矫正图像
        recPath = folder + "/rec"
        os.makedirs(recPath, exist_ok=True)
        # 按行(行拓展)拼接两个数组
        rlt = np.concatenate((dstL, dstR), axis=1)
        # rlt[::40, :] = 0
        cv2.imwrite(recPath + "/" + png_list_l[i], rlt)
        # 生成深度图
        # stereo = cv2.StereoBM_create(numDisparities=16, blockSize=15)
        # disparity = stereo.compute(grayL, grayR)  # 用原始图像

        # disp = cv2.normalize(disparity, disparity, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
        # 将图片扩展至3d空间中,其z方向的值则为当前的距离
        # threeD = cv2.reprojectImageTo3D(disparity.astype(np.float32 ) /16., Q)
        # cv2.imwrite(rlt_dir + "\\depth.jpg", disp)

    return 1



if __name__ == "__main__":

    row_num = 7
    col_num = 7
    # 相机1标定图片路径
    img_dir_L = r"./INR"
    # 相机2标定图片路径
    img_dir_R = r"./RGB"
    # 实际单个棋盘格物理尺寸
    square_sz = 15
    # 标定相机
    # 将相机1姿态变化为相机2
    cameraModel ,folder = stereo_calib_calibrate(img_dir_R ,img_dir_L ,row_num ,col_num , square_sz)
    new_img_dir_R = folder + "/pre"
    # 矫正图片
    ret = stereo_calib_correct(new_img_dir_R , img_dir_L, cameraModel,folder)

OpenCV中,可以使用双目立体视觉技术对图像进行深度估计,但是在进行深度估计之前,需要对双目相机进行矫正,以确保左右相机的图像具有相同的几何形状和位置关系。以下是基本的双目相机矫正步骤: 1. 执行相机标定,获取相机的内部参数和外部参数。 2. 使用标定结果计算左右相机之间的基础矩阵和极线约束。 3. 计算视差图,以获取深度信息。 4. 可选的步骤,进行后处理,例如图像去噪、插值、滤波等。 下面是基本的OpenCV代码示例,用于执行双目相机矫正: ```python # 标定相机,获取相机内参数和外参数(略) # 计算左右相机之间的基础矩阵和极线约束 F, mask = cv2.findFundamentalMat(pts_left, pts_right, cv2.FM_RANSAC) # 计算左右相机的校正映射 R_left, R_right, P_left, P_right, Q, _, _ = cv2.stereoRectify(cameraMatrix1=mtx_left, distCoeffs1=dist_left, cameraMatrix2=mtx_right, distCoeffs2=dist_right, imageSize=image_size, R=R, T=T) # 计算左右相机的映射矩阵 map_left_x, map_left_y = cv2.initUndistortRectifyMap(mtx_left, dist_left, R_left, P_left, image_size, cv2.CV_32FC1) map_right_x, map_right_y = cv2.initUndistortRectifyMap(mtx_right, dist_right, R_right, P_right, image_size, cv2.CV_32FC1) # 校正左右相机的图像 rectified_left = cv2.remap(img_left, map_left_x, map_left_y, cv2.INTER_LINEAR) rectified_right = cv2.remap(img_right, map_right_x, map_right_y, cv2.INTER_LINEAR) # 计算视差图 stereo = cv2.StereoSGBM_create(minDisparity=0, numDisparities=16*5, blockSize=5, P1=8*3*5**2, P2=32*3*5**2, disp12MaxDiff=1, uniquenessRatio=10, speckleWindowSize=100, speckleRange=32) disparity = stereo.compute(rectified_left, rectified_right) # 可选的后处理步骤,例如视差图滤波、插值、去噪等 ``` 其中,`mtx_left`、`dist_left`、`mtx_right`、`dist_right`分别是左右相机的内部参数和畸变系数,`img_left`、`img_right`分别是左右相机的图像,`pts_left`、`pts_right`是左右相机中提取的匹配点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值