双目相机求解三维坐标详述

第七节、双目视觉之空间坐标计算 - 大奥特曼打小怪兽 - 博客园 (cnblogs.com)icon-default.png?t=N7T8https://www.cnblogs.com/zyly/p/9373991.html#_label1_4

1、reprojectImageTo3D函数求三维坐标

该函数将视差图,通过投影矩阵Q,得到一副映射图,图像大小与视差图相同,且每个像素具有三个通道,分别存储了该像素位置在相机坐标系下的三维点坐标在x, y, z三个轴上的值,即每个像素的在相机坐标系下的三维坐标。

void cv::reprojectImageTo3D( 
	InputArray disparity, 	//视差图像
	OutputArray _3dImage,	//映射后存储三维坐标的图像
	InputArray Q,			//重投影矩阵 通过stereoRectify得到
	bool handleMissingValues = false, //计算得到的非正常值是否给值,如果为true则给值10000
	int ddepth = -1			//输出类型 -1 即默认为CV_32FC3 还可以是 CV_16S, CV_32S, CV_32F
)

1.1 函数原理 

相机坐标系下的三维坐标求解原理:

深度Z=f*B/d(f是焦距,B是基线也就是Tx,d是视差) 

 X=x*Z/f

 Y=y*Z/f(x,y是相机坐标)

\large {\color{Magenta} {\color{Green} }}matrix = [[img_x], [img_y], [disp[img_y, img_x]], [1]]

matrix中的imgx,imgy是校正后像素坐标,通过计算Q*matrix,然后进行归一化,满足上式。

1.2 投影矩阵Q的求解

此时的投影矩阵Q是通过stereoRectify函数求得
 

# cameraMatrix1-输入矩阵,matlab双目标定后得到的左相机内参
# distCoeffs1-输入矩阵,matlab双目标定后得到的左相机畸变矩阵
# cameraMatrix2-输入矩阵,matlab双目标定后得到的右相机内参
# distCoeffs2-输入矩阵,matlab双目标定后得到的右相机畸变矩阵

# R,T-输入矩阵, 以左相机坐标系为参考系,右相机相对于左相机的旋转和平移
# R1-输出矩阵,第一个摄像机的校正变换矩阵(旋转变换)
# R2 - 输出矩阵,第二个摄像机的校正变换矩阵(旋转矩阵)

R1,R2是左右相机坐标系校正到同一极线下的旋转矩阵
# P1 - 输出矩阵,第一个摄像机在新坐标系下的投影矩阵
# P2 - 输出矩阵,第二个摄像机在新坐标系下的投影矩阵

P1,P2两个矫正畸变后左右相机坐标系的相机投影矩阵
# Q - 4 * 4的深度差异映射矩阵
# alpha: -1或者不设置则使用自动剪切,0的时候没有黑边,1的时候保留所有原图像素,会有黑边。

CV_EXPORTS_W void stereoRectify( InputArray cameraMatrix1, InputArray distCoeffs1,
                                 InputArray cameraMatrix2, InputArray distCoeffs2,
                                 Size imageSize, InputArray R, InputArray T,
                                 OutputArray R1, OutputArray R2,
                                 OutputArray P1, OutputArray P2,
                                 OutputArray Q, int flags = CALIB_ZERO_DISPARITY,
                                 double alpha = -1, Size newImageSize = Size(),
                                 CV_OUT Rect* validPixROI1 = 0, CV_OUT Rect* validPixROI2 = 0 );

注意:R,T,R1,R2是不同的。

由下面世界坐标系到像素坐标系的求解可以看出,投影矩阵P=K*[R,T],K是相机内参,R,T是相机外参。

        所以对于未校正的原图,R,T是以左相机坐标系为参考系,右相机相对于左相机的旋转和平移,左右相机的投影矩阵分别是:

                                 [1 0 0 0]
 PL=cameraMatrix1*[0 1 0 0]
                                 [0 0 1 0]

                               
 PR=cameraMatrix1*[R,T]


   

       对于校正后的图像,R1,R2是左右相机坐标系校正到同一极线下的旋转矩阵,左右相机的投影矩阵分别是:                                             

P1,P2是通过stereoRectify函数求得矫正畸变后左右相机坐标系的相机投影矩阵

                                                       PL=P1

                                                       PR=P2

             

1.3 三维坐标求解 

思考1:

        因为reprojectImageTo3D函数是通过视差图和Q求解三维坐标,而视差图是通过校正后的左右视图得到的,所以想要通过人机交互系统完成对三维坐标求取,就需要得到的是校正后的左右视图的像素坐标,而不是原图的像素坐标。

# coding:utf-8

import cv2
import time
from PIL import Image
import numpy as np
from cv2 import FileStorage
from scipy.linalg import lstsq
# //左相机内参矩阵
from matplotlib import pyplot as plt

global Left_rectified, Right_rectified
global imgL
global imgR

global cameraMatrix1
# //左相机畸变系数矩阵
global distCoeffs1
# //右相机内参矩阵
global cameraMatrix2
# //右相机畸变系数矩阵
global distCoeffs2
# Mat R, T, E, F; //R 旋转矢量 T平移矢量 E本征矩阵 F基础矩阵 Q重投影矩阵
global R
global T
global E
global F
global Q
global PL
global PR
# 图像宽度
global width
# 图像高度
global height
# 标定板的宽度
global board_width
# 标定板的高度
global board_height


def readFile(filename):
    global cameraMatrix1
    global cameraMatrix2
    global distCoeffs1
    global distCoeffs2
    global R
    global T
    global E
    global F
    global width
    global height
    global board_width
    global board_height
    fs = FileStorage(filename, cv2.FileStorage_READ)
    width = fs.getNode("width").real()
    height = fs.getNode("height").real()
    board_width = fs.getNode("board_width").real()
    board_height = fs.getNode("board_height").real()
    cameraMatrix1 = fs.getNode("cameraMatrix1").mat()
    distCoeffs1 = fs.getNode("distCoeffs1").mat()
    cameraMatrix2 = fs.getNode("cameraMatrix2").mat()
    distCoeffs2 = fs.getNode("distCoeffs2").mat()
    R = fs.getNode("R").mat()
    T = fs.getNode("T").mat()
    E = fs.getNode("E").mat()
    F = fs.getNode("F").mat()
    fs.release()


def rectify(imgL, imgR):
    """
    校正图像(显示图像时窗口名不能是中文名)
    :param imgL: 左视图(灰度图像)
    :param imgR: 右视图(灰度图像)
    :return: 返回原始图像和校正后的图像
    """
    # 双目相机的标定
    # 设置标志位为cv2.CALIB_FIX_INTRINSIC,这样就会固定输入的cameraMatrix和distCoeffs不变,只求解𝑅,𝑇,𝐸,𝐹
    flags = 0
    flags |= cv2.CALIB_ZERO_DISPARITY
    # 利用stereoRectify()计算立体校正的映射矩阵
    rectify_scale = -1  # 设置为0的话,对图片进行剪裁,设置为1则保留所有原图像像素
    global Q
    global PL
    global PR
    # R,T: 是由相机1变换到相机2的变换矩阵,按照视觉这边的习惯,一般世界坐标系下的点变换到相机坐标系下的点
    # R1-输出矩阵,第一个摄像机的校正变换矩阵(旋转变换)
    # R2 - 输出矩阵,第二个摄像机的校正变换矩阵(旋转矩阵)
    # P1 - 输出矩阵,第一个摄像机在新坐标系下的投影矩阵
    # P2 - 输出矩阵,第二个摄像机在新坐标系下的投影矩阵 两个矫正畸变后的相机投影矩阵,相对的坐标系还是原来的相机所在坐标系
    # Q - 4 * 4的深度差异映射矩阵
    # rectify_scale: -1或者不设置则使用自动剪切,0的时候没有黑边,1的时候保留所有原图像素,会有黑边
    RL, RR, PL, PR, Q, roiL, roiR = cv2.stereoRectify(cameraMatrix1, distCoeffs1, cameraMatrix2, distCoeffs2,
                                                      imgL.shape[::-1], R, T,
                                                      rectify_scale, (0, 0))
    # 利用initUndistortRectifyMap函数计算畸变矫正和立体校正的映射变换,实现极线对齐。
    Left_Stereo_Map = cv2.initUndistortRectifyMap(cameraMatrix1, distCoeffs1, RL, PL,
                                                  imgL.shape[::-1], cv2.CV_16SC2)
    Right_Stereo_Map = cv2.initUndistortRectifyMap(cameraMatrix2, distCoeffs2, RR, PR,
                                                   imgL.shape[::-1], cv2.CV_16SC2)
    Left_rectified = cv2.remap(imgL, Left_Stereo_Map[0], Left_Stereo_Map[1], cv2.INTER_LANCZOS4, cv2.BORDER_CONSTANT,
                               0)  # 使用remap函数完成映射

    Right_rectified = cv2.remap(imgR, Right_Stereo_Map[0], Right_Stereo_Map[1], cv2.INTER_LANCZOS4, cv2.BORDER_CONSTANT,
                                0)
    imgRectify = np.hstack([Left_rectified, Right_rectified])
    imgOriginal = np.hstack([imgL, imgR])
    # 在已经极线对齐的图片上均匀画线
    for i in range(1, int(imgL.shape[0] / 20)):
        cv2.line(imgRectify, (0, 20 * i), (2 * imgL.shape[1], 20 * i), (0, 0, 0), 1, cv2.LINE_8)
        cv2.line(imgOriginal, (0, 20 * i), (2 * imgL.shape[1], 20 * i), (0, 0, 0), 1, cv2.LINE_8)
    # cv2.namedWindow("Rectify", cv2.WINDOW_FREERATIO)
    # cv2.namedWindow("Original", cv2.WINDOW_FREERATIO)
    # cv2.imshow("Rectify", imgRectify)
    # cv2.imshow("Original", imgOriginal)
    return Left_rectified, Right_rectified


# 视差计算
def disparity_SGBM(left_image, right_image, down_scale=False):
    """
    通过SGBM方法得到视差图
    :param left_image: 极线校正左图像
    :param right_image: 极线校正右图像
    :param down_scale:
    :return: 返回视差图
    """
    # SGBM匹配参数设置
    if left_image.ndim == 2:
        img_channels = 1
    else:
        img_channels = 3
    blockSize = 3
    param = {'minDisparity': 0,
             'numDisparities': 240,  # 视差搜索范围
             'blockSize': blockSize,  # blockSize,块匹配的大小,应该为奇数,在3~11的范围
             'P1': 8 * img_channels * 9 ** 2,  # 值越大越平滑
             'P2': 32 * img_channels * 9 ** 2,
             'disp12MaxDiff': 1,  # 左右一致性检测允许的最大误差值
             'preFilterCap': 32,  # 映射滤波器
             'uniquenessRatio': 1,  # 唯一性检测
             'speckleWindowSize': 100,  # 视差图连通区域像素点个数的大小,<speckleWindowSize则为噪声点
             'speckleRange': 32,  # >speckleRange:则为不连通
             'mode': cv2.STEREO_SGBM_MODE_SGBM
             }

    # 构建SGBM对象
    sgbm = cv2.StereoSGBM_create(**param)

    # 计算视差图
    size = (left_image.shape[1], left_image.shape[0])
    if down_scale == False:
        disparity_left = sgbm.compute(left_image, right_image)
        disparity_left = cv2.normalize(disparity_left, disparity_left, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
        disparity_left = cv2.medianBlur(disparity_left, 3)
        disparity_left = np.divide(disparity_left.astype(np.float32), 16.)  # 除以16得到真实视差图

        disparity_right = sgbm.compute(right_image, left_image)
        disparity_right = np.divide(disparity_right.astype(np.float32), 16.)  # 除以16得到真实视差图
    else:
        left_image_down = cv2.pyrDown(left_image)
        right_image_down = cv2.pyrDown(right_image)
        factor = size[0] / left_image_down.shape[1]
        disparity_left_half = sgbm.compute(left_image_down, right_image_down)
        disparity_right_half = sgbm.compute(right_image_down, left_image_down)
        disparity_left = cv2.resize(disparity_left_half, size, interpolation=cv2.INTER_AREA)
        disparity_right = cv2.resize(disparity_right_half, size, interpolation=cv2.INTER_AREA)
        disparity_left *= factor
        disparity_right *= factor

    return disparity_left, disparity_right


# 计算三维坐标,并删除错误点
def threeD(disp, img_x, img_y, Q):
    ""&
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小鹿( ﹡ˆoˆ﹡ )

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值