Opencv - 来自未校准立体声系统的深度图

时间:2016-03-23 08:17:54

标签: python opencv stereo-3d disparity-mapping

我试图从未校准的方法中获取深度图。 我可以通过SIFT方法和cv2.findFundamentalMat的不同对应点获得基本矩阵。然后用cv2.stereoRectifyUncalibrated我可以得到整流矩阵。最后,我可以使用cv2.warpPerspective来纠正和计算差异,但后者并没有进行到良好的深度图。这些值非常高,所以我想知道是否必须使用warpPerspective或者我必须使用stereoRectifyUncalibrated计算出的单应矩阵计算旋转矩阵。

所以我不确定用stereoRectifyUncalibrated得到的单应矩阵情形的投影矩阵来纠正。

代码的一部分:

#Obtainment of the correspondent point with SIFT
sift = cv2.SIFT()

###find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(dst1,None)
kp2, des2 = sift.detectAndCompute(dst2,None)

###FLANN parameters
FLANN_INDEX_KDTREE = 0
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50)

flann = cv2.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1,des2,k=2)

good = []
pts1 = []
pts2 = []

###ratio test as per Lowe's paper
for i,(m,n) in enumerate(matches):
    if m.distance < 0.8*n.distance:
        good.append(m)
        pts2.append(kp2[m.trainIdx].pt)
        pts1.append(kp1[m.queryIdx].pt)


pts1 = np.array(pts1)
pts2 = np.array(pts2)

#Computation of the fundamental matrix
F,mask= cv2.findFundamentalMat(pts1,pts2,cv2.FM_LMEDS)


# Obtainment of the rectification matrix and use of the warpPerspective to transform them...
pts1 = pts1[:,:][mask.ravel()==1]
pts2 = pts2[:,:][mask.ravel()==1]

pts1 = np.int32(pts1)
pts2 = np.int32(pts2)

p1fNew = pts1.reshape((pts1.shape[0] * 2, 1))
p2fNew = pts2.reshape((pts2.shape[0] * 2, 1))

retBool ,rectmat1, rectmat2 = cv2.stereoRectifyUncalibrated(p1fNew,p2fNew,F,(2048,2048))

dst11 = cv2.warpPerspective(dst1,rectmat1,(2048,2048))
dst22 = cv2.warpPerspective(dst2,rectmat2,(2048,2048))

#calculation of the disparity
stereo = cv2.StereoBM(cv2.STEREO_BM_BASIC_PRESET,ndisparities=16*10, SADWindowSize=9)
disp = stereo.compute(dst22.astype(uint8), dst11.astype(uint8)).astype(np.float32)
plt.imshow(disp);plt.colorbar();plt.clim(0,400)#;plt.show()
plt.savefig("0gauche.png")

#plot depth by using disparity focal length `C1[0,0]` from stereo calibration and `T[0]` the distance between cameras

plt.imshow(C1[0,0]*T[0]/(disp),cmap='hot');plt.clim(-0,500);plt.colorbar();plt.show()

这里使用未校准方法(和warpPerspective)修正了图片: enter image description here

这里使用校准方法校正图片: enter image description here

我不知道这两种图片之间的差异是如此重要......对于校准方法,它似乎没有对齐......奇怪 未校准方法的视差图:

enter image description here

深度图的计算方法为:C1[0,0]*T[0]/(disp) 来自stereoCalibrate的T,但值非常高......

-------- EDIT LATER ------------

我试图&#34; mount&#34;重建矩阵( [Devernay97] [Garcia01] ),使用&#34; stereoRectifyUncalibrated&#34;但结果并不好......我的使用是否正确?

Y=np.arange(0,2048)
X=np.arange(0,2048)
(XX_field,YY_field)=np.meshgrid(X,Y)

#I mount the X, Y and disparity in a same 3D array 
stock = np.concatenate((np.expand_dims(XX_field,2),np.expand_dims(YY_field,2)),axis=2)
XY_disp = np.concatenate((stock,np.expand_dims(disp,2)),axis=2)

XY_disp_reshape = XY_disp.reshape(XY_disp.shape[0]*XY_disp.shape[1],3)

Ts = np.hstack((np.zeros((3,3)),T_0)) #i use only the translations obtained with the rectified calibration...Is it correct?


# I etablish the projective matrix with the homography matrix
P11 = np.dot(rectmat1,C1)
P1 = np.vstack((np.hstack((P11,np.zeros((3,1)))),np.zeros((1,4))))
P1[3,3] = 1

# P1 = np.dot(C1,np.hstack((np.identity(3),np.zeros((3,1)))))

P22 = np.dot(np.dot(rectmat2,C2),Ts)
P2 = np.vstack((P22,np.zeros((1,4))))
P2[3,3] = 1

lambda_t = cv2.norm(P1[0,:].T)/cv2.norm(P2[0,:].T)


#I define the reconstruction matrix
Q = np.zeros((4,4))

Q[0,:] = P1[0,:].T
Q[1,:] = P1[1,:].T
Q[2,:] = lambda_t*P2[1,:].T - P1[1,:].T
Q[3,:] = P1[2,:].T

#I do the calculation to get my 3D coordinates
test = []
for i in range(0,XY_disp_reshape.shape[0]):
    a = np.dot(inv(Q),np.expand_dims(np.concatenate((XY_disp_reshape[i,:],np.ones((1))),axis=0),axis=1))
    test.append(a)

test = np.asarray(test)

XYZ = test[:,:,0].reshape(XY_disp.shape[0],XY_disp.shape[1],4)

2 个答案:

答案 0 :(得分:12)

TLDR;对边缘更平滑的图像使用StereoSGBM(半全局块匹配),如果您希望它更平滑,则使用一些后期过滤

OP没有提供原始图像,因此我正在使用 Middlebury data set中的Tsukuba

使用常规StereoBM的结果

stereobm

使用StereoSGBM的结果(已调整)

stereosgbm

我能在文学中找到最好的结果

enter image description here

有关详情,请参见出版物here

后置过滤示例(请参见下面的链接)

post filter example

OP的理论/其他考虑因素

校正后的校正图像中较大的黑色区域使我相信,对于这些校正而言,校正效果不是很好。可能有多种原因在起作用,例如物理设置,校准时可能亮起的照明等等,但是为此有很多相机校准教程,我的理解是,您正在寻找一种方法来从未经校准的设置中可以获得更好的深度图(这不是100%清晰的,但标题似乎支持这一点,我认为这就是人们来这里寻找的目的)。

您的基本方法是正确的,但结果肯定可以改善。深度映射的这种形式不在产生最高质量的映射的那些之中(尤其是未经校准的)。最大的改进可能来自使用其他立体声匹配算法。照明也可能会产生重大影响。正确的图像(至少对我的肉眼而言)似乎光线不足,可能会干扰重建。您可以先尝试将其亮度提高到另一个水平,或者在可能的情况下收集新图像。从这里开始,我将假定您无权使用原始相机,因此,我将考虑收集新图像,更改设置或执行校准超出范围。 (如果您确实可以访问设置和摄像机,那么我建议您检查校准并使用校准的方法,因为这样会更好地工作。)

您使用StereoBM来计算有效的视差(深度图),但是StereoSGBM更适合此应用程序(它可以更好地处理更平滑的边缘)。您可以在下面看到区别。

This article更深入地解释了差异:

块匹配集中于高纹理图像(例如一棵树的图片),半全局块匹配将集中于子像素级匹配和纹理更平滑的图片(例如走廊的图片)。

没有任何明确的固有摄像机参数,有关摄像机设置的详细信息(例如焦距,摄像机之间的距离,到被摄物体的距离等),图像中的已知尺寸或运动(使用{{3} }),您最多只能获得3D重建,直到投影变换;您也不会有比例感或旋转感,但是仍然可以生成相对深度图。您可能会遭受一些镜筒变形和其他变形的困扰,这些变形可以通过适当的相机校准来消除,但是只要相机不可怕(镜头系统不会太扭曲)并且设置得相当漂亮,您就可以获得合理的结果。接近structure from motion(这基本上意味着它们的方向应使其光轴尽可能接近平行,并且它们的视场充分重叠)。但是,这似乎不是OP的问题,因为他确实设法使用了未校准的方法来获取可以校正的图像。

基本过程

  1. 在两个图像中至少找到5个匹配良好的点,可以用来计算基本矩阵(可以使用任何喜欢的检测器和匹配器,我保留了FLANN,但是使用ORB进行了检测,因为SIFT不在主要范围内版本的OpenCV for 4.2.0)
  2. 使用findFundamentalMat
  3. 计算基本矩阵F
  4. stereoRectifyUncalibratedwarpPerspective使图像不失真
  5. 使用StereoSGBM计算视差(深度图)

结果要好得多

与ORB和FLANN匹配

canonical configuration

未失真的图像(先左后右)

Matches
undistorted left

差异

StereoBM

此结果看起来与OP的问题(斑点,间隙,某些区域的错误深度)相似。

undistorted right

StereoSGBM(已调整)

这个结果看起来要好得多,并且使用与OP大致相同的方法,减去最终的视差计算,这让我认为,只要提供OP,他的图像就会得到类似的改进。

stereobm

发布过滤

OpenCV文档中有stereosgbm。如果您需要非常平滑的地图,我建议您看看。

上面的示例照片是a good article about this中场景ambush_2的第1帧。

MPI Sintel Dataset

完整代码(在OpenCV 4.2.0上测试):

import cv2
import numpy as np
import matplotlib.pyplot as plt

imgL = cv2.imread("tsukuba_l.png", cv2.IMREAD_GRAYSCALE)  # left image
imgR = cv2.imread("tsukuba_r.png", cv2.IMREAD_GRAYSCALE)  # right image


def get_keypoints_and_descriptors(imgL, imgR):
    """Use ORB detector and FLANN matcher to get keypoints, descritpors,
    and corresponding matches that will be good for computing
    homography.
    """
    orb = cv2.ORB_create()
    kp1, des1 = orb.detectAndCompute(imgL, None)
    kp2, des2 = orb.detectAndCompute(imgR, None)

    ############## Using FLANN matcher ##############
    # Each keypoint of the first image is matched with a number of
    # keypoints from the second image. k=2 means keep the 2 best matches
    # for each keypoint (best matches = the ones with the smallest
    # distance measurement).
    FLANN_INDEX_LSH = 6
    index_params = dict(
        algorithm=FLANN_INDEX_LSH,
        table_number=6,  # 12
        key_size=12,  # 20
        multi_probe_level=1,
    )  # 2
    search_params = dict(checks=50)  # or pass empty dictionary
    flann = cv2.FlannBasedMatcher(index_params, search_params)
    flann_match_pairs = flann.knnMatch(des1, des2, k=2)
    return kp1, des1, kp2, des2, flann_match_pairs


def lowes_ratio_test(matches, ratio_threshold=0.6):
    """Filter matches using the Lowe's ratio test.

    The ratio test checks if matches are ambiguous and should be
    removed by checking that the two distances are sufficiently
    different. If they are not, then the match at that keypoint is
    ignored.

    https://stackoverflow.com/questions/51197091/how-does-the-lowes-ratio-test-work
    """
    filtered_matches = []
    for m, n in matches:
        if m.distance < ratio_threshold * n.distance:
            filtered_matches.append(m)
    return filtered_matches


def draw_matches(imgL, imgR, kp1, des1, kp2, des2, flann_match_pairs):
    """Draw the first 8 mathces between the left and right images."""
    # https://docs.opencv.org/4.2.0/d4/d5d/group__features2d__draw.html
    # https://docs.opencv.org/2.4/modules/features2d/doc/common_interfaces_of_descriptor_matchers.html
    img = cv2.drawMatches(
        imgL,
        kp1,
        imgR,
        kp2,
        flann_match_pairs[:8],
        None,
        flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS,
    )
    cv2.imshow("Matches", img)
    cv2.imwrite("ORB_FLANN_Matches.png", img)
    cv2.waitKey(0)


def compute_fundamental_matrix(matches, kp1, kp2, method=cv2.FM_RANSAC):
    """Use the set of good mathces to estimate the Fundamental Matrix.

    See  https://en.wikipedia.org/wiki/Eight-point_algorithm#The_normalized_eight-point_algorithm
    for more info.
    """
    pts1, pts2 = [], []
    fundamental_matrix, inliers = None, None
    for m in matches[:8]:
        pts1.append(kp1[m.queryIdx].pt)
        pts2.append(kp2[m.trainIdx].pt)
    if pts1 and pts2:
        # You can play with the Threshold and confidence values here
        # until you get something that gives you reasonable results. I
        # used the defaults
        fundamental_matrix, inliers = cv2.findFundamentalMat(
            np.float32(pts1),
            np.float32(pts2),
            method=method,
            # ransacReprojThreshold=3,
            # confidence=0.99,
        )
    return fundamental_matrix, inliers, pts1, pts2


############## Find good keypoints to use ##############
kp1, des1, kp2, des2, flann_match_pairs = get_keypoints_and_descriptors(imgL, imgR)
good_matches = lowes_ratio_test(flann_match_pairs, 0.2)
draw_matches(imgL, imgR, kp1, des1, kp2, des2, good_matches)


############## Compute Fundamental Matrix ##############
F, I, points1, points2 = compute_fundamental_matrix(good_matches, kp1, kp2)


############## Stereo rectify uncalibrated ##############
h1, w1 = imgL.shape
h2, w2 = imgR.shape
thresh = 0
_, H1, H2 = cv2.stereoRectifyUncalibrated(
    np.float32(points1), np.float32(points2), F, imgSize=(w1, h1), threshold=thresh,
)

############## Undistort (Rectify) ##############
imgL_undistorted = cv2.warpPerspective(imgL, H1, (w1, h1))
imgR_undistorted = cv2.warpPerspective(imgR, H2, (w2, h2))
cv2.imwrite("undistorted_L.png", imgL_undistorted)
cv2.imwrite("undistorted_R.png", imgR_undistorted)

############## Calculate Disparity (Depth Map) ##############

# Using StereoBM
stereo = cv2.StereoBM_create(numDisparities=16, blockSize=15)
disparity_BM = stereo.compute(imgL_undistorted, imgR_undistorted)
plt.imshow(disparity_BM, "gray")
plt.colorbar()
plt.show()

# Using StereoSGBM
# Set disparity parameters. Note: disparity range is tuned according to
#  specific parameters obtained through trial and error.
win_size = 2
min_disp = -4
max_disp = 9
num_disp = max_disp - min_disp  # Needs to be divisible by 16
stereo = cv2.StereoSGBM_create(
    minDisparity=min_disp,
    numDisparities=num_disp,
    blockSize=5,
    uniquenessRatio=5,
    speckleWindowSize=5,
    speckleRange=5,
    disp12MaxDiff=2,
    P1=8 * 3 * win_size ** 2,
    P2=32 * 3 * win_size ** 2,
)
disparity_SGBM = stereo.compute(imgL_undistorted, imgR_undistorted)
plt.imshow(disparity_SGBM, "gray")
plt.colorbar()
plt.show()

答案 1 :(得分:6)

可能存在一些可能导致低质量Depth ChannelDisparity Channel的问题,这导致我们获得低质量立体声序列。以下是其中的6个问题:

可能的问题

  • 公式不完整

uncalibrated一词的含义是,stereoRectifyUncalibrated实例方法可以为您计算整流变换,以防您不知道或不知道立体声对的固有参数及其相对位置的情况。环境。

cv.StereoRectifyUncalibrated(pts1, pts2, fm, imgSize, rhm1, rhm2, thres)

其中:

# pts1    –> an array of feature points in a first camera
# pts2    –> an array of feature points in a first camera
# fm      –> input fundamental matrix
# imgSize -> size of an image
# rhm1    -> output rectification homography matrix for a first image
# rhm2    -> output rectification homography matrix for a second image
# thres   –> optional threshold used to filter out outliers

您的方法如下:

cv2.StereoRectifyUncalibrated(p1fNew, p2fNew, F, (2048, 2048))

因此,您无需考虑三个参数:rhm1rhm2thres。如果为threshold > 0,则在计算单应性之前,将拒绝所有不符合对极几何的点对。否则,所有点均视为内点。该公式如下所示:

(pts2[i]^t * fm * pts1[i]) > thres

# t   –> translation vector between coordinate systems of cameras

因此,我认为由于公式计算不完整,可能会出现视觉错误。

您可以在官方资源上阅读Camera Calibration and 3D Reconstruction


可能的问题II

  • 轴间距离

左右镜头之间的坚固interaxial distance必须为 not greater than 200 mm 。当interaxial distance大于interocular距离时,该效果称为hyperstereoscopyhyperdivergence,不仅会导致场景中的深度夸张,还会给观看者带来身体上的不便。阅读Autodesk的Stereoscopic Filmmaking Whitepaper,以了解有关此主题的更多信息。

enter image description here


可能的问题III

  • 平行摄像头与Toed-In摄像头模式

由于相机模式计算不正确,导致出现Disparity Map的视觉错误。许多立体学家更喜欢Toe-In camera mode,但例如皮克斯喜欢Parallel camera mode

enter image description here

enter image description here


可能的问题IV

  • 垂直对齐

在立体视觉中,如果发生垂直偏移(即使其中一个视图向上偏移1毫米),也会破坏稳固的立体声体验。因此,在生成Disparity Map之前,必须确保立体声对的左右视图已相应对齐。看看Technicolor Sterreoscopic Whitepaper立体声方面的15个常见问题。

立体整流矩阵:

   ┌                  ┐
   |  f   0   cx  tx  |
   |  0   f   cy  ty  |   # use "ty" value to fix vertical shift in one image
   |  0   0   1   0   |
   └                  ┘

这是一种StereoRectify方法:

cv.StereoRectify(cameraMatrix1, cameraMatrix2, distCoeffs1, distCoeffs2, imageSize, R, T, R1, R2, P1, P2, Q=None, flags=CV_CALIB_ZERO_DISPARITY, alpha=-1, newImageSize=(0, 0)) -> (roi1, roi2)


可能的问题V

  • 镜头失真

镜头失真是立体声合成中非常重要的主题。在生成Disparity Map之前,您需要先取消左右视图的失真,然后再生成视差通道,然后再次对这两个视图进行重新扭曲。

enter image description here

enter image description here


可能的问题VI

  • 低质量深度通道,没有抗锯齿

要创建高质量的Disparity Map,您需要左右Depth Channels必须预先生成。在3D封装中工作时,只需单击一下即可渲染高质量的深度通道(边缘清晰)。但是从视频序列中生成高质量的深度通道并不容易,因为立体声对必须在您的环境中移动才能为将来的深度运动算法生成初始数据。如果一帧中没有动作,则深度通道将非常差。

enter image description here

此外,Depth通道本身还有一个缺点-它的边缘与RGB的边缘不匹配,因为它没有抗锯齿

enter image description here


差异渠道代码段:

在这里,我想代表一种生成Disparity Map的快速方法:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

imageLeft = cv.imread('paris_left.png', 0)
imageRight = cv.imread('paris_right.png', 0)
stereo = cv.StereoBM_create(numDisparities=16, blockSize=15)
disparity = stereo.compute(imageLeft, imageRight)
plt.imshow(disparity, 'gray')
plt.show()

enter image description here