我试图从未校准的方法中获取深度图。
我可以通过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
)修正了图片:
这里使用校准方法校正图片:
我不知道这两种图片之间的差异是如此重要......对于校准方法,它似乎没有对齐......奇怪 未校准方法的视差图:
深度图的计算方法为: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)
答案 0 :(得分:12)
OP没有提供原始图像,因此我正在使用 Middlebury data set中的Tsukuba
。
有关详情,请参见出版物here。
校正后的校正图像中较大的黑色区域使我相信,对于这些校正而言,校正效果不是很好。可能有多种原因在起作用,例如物理设置,校准时可能亮起的照明等等,但是为此有很多相机校准教程,我的理解是,您正在寻找一种方法来从未经校准的设置中可以获得更好的深度图(这不是100%清晰的,但标题似乎支持这一点,我认为这就是人们来这里寻找的目的)。
您的基本方法是正确的,但结果肯定可以改善。深度映射的这种形式不在产生最高质量的映射的那些之中(尤其是未经校准的)。最大的改进可能来自使用其他立体声匹配算法。照明也可能会产生重大影响。正确的图像(至少对我的肉眼而言)似乎光线不足,可能会干扰重建。您可以先尝试将其亮度提高到另一个水平,或者在可能的情况下收集新图像。从这里开始,我将假定您无权使用原始相机,因此,我将考虑收集新图像,更改设置或执行校准超出范围。 (如果您确实可以访问设置和摄像机,那么我建议您检查校准并使用校准的方法,因为这样会更好地工作。)
您使用StereoBM
来计算有效的视差(深度图),但是StereoSGBM
更适合此应用程序(它可以更好地处理更平滑的边缘)。您可以在下面看到区别。
This article更深入地解释了差异:
块匹配集中于高纹理图像(例如一棵树的图片),半全局块匹配将集中于子像素级匹配和纹理更平滑的图片(例如走廊的图片)。
没有任何明确的固有摄像机参数,有关摄像机设置的详细信息(例如焦距,摄像机之间的距离,到被摄物体的距离等),图像中的已知尺寸或运动(使用{{3} }),您最多只能获得3D重建,直到投影变换;您也不会有比例感或旋转感,但是仍然可以生成相对深度图。您可能会遭受一些镜筒变形和其他变形的困扰,这些变形可以通过适当的相机校准来消除,但是只要相机不可怕(镜头系统不会太扭曲)并且设置得相当漂亮,您就可以获得合理的结果。接近structure from motion(这基本上意味着它们的方向应使其光轴尽可能接近平行,并且它们的视场充分重叠)。但是,这似乎不是OP的问题,因为他确实设法使用了未校准的方法来获取可以校正的图像。
findFundamentalMat
stereoRectifyUncalibrated
和warpPerspective
使图像不失真StereoSGBM
计算视差(深度图)结果要好得多
此结果看起来与OP的问题(斑点,间隙,某些区域的错误深度)相似。
这个结果看起来要好得多,并且使用与OP大致相同的方法,减去最终的视差计算,这让我认为,只要提供OP,他的图像就会得到类似的改进。
OpenCV文档中有。如果您需要非常平滑的地图,我建议您看看。
上面的示例照片是a good article about this中场景ambush_2
的第1帧。
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 Channel
和Disparity 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))
因此,您无需考虑三个参数:rhm1
,rhm2
和thres
。如果为threshold > 0
,则在计算单应性之前,将拒绝所有不符合对极几何的点对。否则,所有点均视为内点。该公式如下所示:
(pts2[i]^t * fm * pts1[i]) > thres
# t –> translation vector between coordinate systems of cameras
因此,我认为由于公式计算不完整,可能会出现视觉错误。
您可以在官方资源上阅读Camera Calibration and 3D Reconstruction。
左右镜头之间的坚固interaxial distance
必须为 not greater than 200 mm
。当interaxial distance
大于interocular
距离时,该效果称为hyperstereoscopy
或hyperdivergence
,不仅会导致场景中的深度夸张,还会给观看者带来身体上的不便。阅读Autodesk的Stereoscopic Filmmaking Whitepaper,以了解有关此主题的更多信息。
由于相机模式计算不正确,导致出现Disparity Map
的视觉错误。许多立体学家更喜欢Toe-In camera mode
,但例如皮克斯喜欢Parallel camera mode
。
在立体视觉中,如果发生垂直偏移(即使其中一个视图向上偏移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)
镜头失真是立体声合成中非常重要的主题。在生成Disparity Map
之前,您需要先取消左右视图的失真,然后再生成视差通道,然后再次对这两个视图进行重新扭曲。
要创建高质量的Disparity Map
,您需要左右Depth Channels
必须预先生成。在3D封装中工作时,只需单击一下即可渲染高质量的深度通道(边缘清晰)。但是从视频序列中生成高质量的深度通道并不容易,因为立体声对必须在您的环境中移动才能为将来的深度运动算法生成初始数据。如果一帧中没有动作,则深度通道将非常差。
此外,
Depth
通道本身还有一个缺点-它的边缘与RGB的边缘不匹配,因为它没有抗锯齿。
在这里,我想代表一种生成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()