Próbuję uzyskać mapę głębi metodą nieskalibrowaną. Mogę uzyskać podstawową macierz, znajdując punkty odpowiadające za pomocą SIFT, a następnie używając cv2.findFundamentalMat
. Następnie używam cv2.stereoRectifyUncalibrated
do uzyskania macierzy homografii dla każdego obrazu. Na koniec używam cv2.warpPerspective
do korygowania i obliczania rozbieżności, ale to nie tworzy dobrej mapy głębi. Wartości są bardzo wysokie, więc zastanawiam się, czy muszę użyć, warpPerspective
czy mam obliczyć macierz rotacji z macierzy homografii, które otrzymałem stereoRectifyUncalibrated
.
Nie jestem pewien macierzy projekcyjnej w przypadku macierzy homografii uzyskanej za pomocą stereoRectifyUncalibrated
rektyfikacji.
Część kodu:
#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()
Oto rektyfikowane zdjęcia metodą nieskalibrowaną (i warpPerspective
):
Oto zdjęcia rektyfikowane metodą kalibracji:
Nie wiem, jak ważna jest różnica między tymi dwoma rodzajami zdjęć. W przypadku metody skalibrowanej wydaje się, że nie jest wyrównany.
Mapa rozbieżności metodą nieskalibrowaną:
Głębokości są obliczane za pomocą: C1[0,0]*T[0]/(disp)
z T z stereoCalibrate
. Wartości są bardzo wysokie.
------------ EDYTUJ PÓŹNIEJ ------------
Próbowałem „zamontować” macierz rekonstrukcji ( [Devernay97] , [Garcia01] ) z macierzą homografii uzyskaną za pomocą „stereoRectifyUncalibrated”, ale wynik nadal nie jest dobry. Czy robię to poprawnie?
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 establish 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)
źródło
StereoBM
nie jest to najlepszy algorytm dopasowujący ... Możesz znaleźć pewne ulepszenia przy użyciuStereoSGBM
. Chciałbym pomóc, ale nie do końca zrozumiałem Twoje pytanie ...Odpowiedzi:
TLDR; Użyj StereoSGBM (Semi Global Block Matching) dla obrazów z gładszymi krawędziami i użyj trochę filtrowania postów, jeśli chcesz, aby było jeszcze gładsze
PO nie przedstawiła oryginalne obrazy, więc używam
Tsukuba
od zbioru danych Middlebury .Wynik ze zwykłym StereoBM
Wynik z StereoSGBM (dostrojony)
Najlepszy wynik, jaki mogłem znaleźć w literaturze
Szczegółowe informacje można znaleźć w publikacji tutaj .
Przykład filtrowania postów (patrz link poniżej)
Teoria / inne rozważania z pytania OP
Duże czarne obszary skalibrowanych, rektyfikowanych obrazów doprowadziłyby mnie do wniosku, że w ich przypadku kalibracja nie została wykonana zbyt dobrze. Istnieje wiele powodów, które mogą być w grze, na przykład konfiguracja fizyczna, może oświetlenie podczas kalibracji itp., Ale istnieje wiele samouczków dotyczących kalibracji kamery i rozumiem, że pytasz o sposób uzyskać lepszą mapę głębi z nieskalibrowanej konfiguracji (nie jest to w 100% jasne, ale tytuł wydaje się to wspierać i myślę, że ludzie będą tu przyjeżdżać, aby spróbować znaleźć).
Twoje podstawowe podejście jest poprawne, ale wyniki można zdecydowanie poprawić. Ta forma mapowania głębi nie należy do tych, które dają mapy najwyższej jakości (zwłaszcza niekalibrowane). Największa poprawa będzie prawdopodobnie wynikać z zastosowania innego algorytmu dopasowania stereo. Oświetlenie również może mieć znaczący wpływ. Obraz po prawej (przynajmniej gołym okiem) wydaje się być gorzej oświetlony, co mogłoby przeszkadzać w rekonstrukcji. Możesz najpierw spróbować rozjaśnić go do tego samego poziomu, co inne lub zebrać nowe obrazy, jeśli to możliwe. Odtąd zakładam, że nie masz dostępu do oryginalnych kamer, więc rozważę zebranie nowych obrazów, zmianę konfiguracji lub przeprowadzenie kalibracji, aby znaleźć się poza zakresem. (Jeśli masz dostęp do konfiguracji i kamer,
Użyłeś
StereoBM
do obliczenia różnicy (mapa głębi), która działa, aleStereoSGBM
jest znacznie lepiej dostosowana do tej aplikacji (lepiej radzi sobie z gładszymi krawędziami). Różnicę widać poniżej.W tym artykule dokładniej wyjaśniono różnice:
Bez żadnych wyraźnych wewnętrznych parametrów kamery, szczegółów dotyczących konfiguracji kamery (takich jak odległość ogniskowa, odległość między kamerami, odległość od obiektu itp.), Znanego wymiaru obrazu lub ruchu (aby wykorzystać strukturę z ruchu ), możesz uzyskać tylko rekonstrukcję 3D aż do transformacji rzutowej; nie będziesz mieć poczucia skali ani koniecznie rotacji, ale nadal możesz wygenerować mapę względnej głębokości. Prawdopodobnie będziesz cierpieć z powodu niektórych beczułek i innych zniekształceń, które można usunąć przy odpowiedniej kalibracji aparatu, ale bez tego możesz uzyskać rozsądne wyniki, o ile kamery nie są straszne (system obiektywów nie jest zbyt zniekształcony) i są dość ustawione blisko konfiguracji kanonicznej(co w zasadzie oznacza, że są zorientowane tak, że ich osie optyczne są jak najbliżej równoległości, a ich pola widzenia w wystarczającym stopniu pokrywają się). Nie wydaje się to jednak być problemem PO, ponieważ udało mu się uzyskać poprawione obrazy za pomocą metody nieskalibrowanej.
Procedura podstawowa
findFundamentalMat
stereoRectifyUncalibrated
iwarpPerspective
StereoSGBM
Wyniki są znacznie lepsze:
Mecze z ORB i FLANN
Niezniekształcone obrazy (po lewej, potem po prawej)
Różnica
StereoBM
Ten wynik wygląda podobnie do problemów PO (plamki, luki, niewłaściwe głębokości w niektórych obszarach).
StereoSGBM (dostrojony)
Ten wynik wygląda znacznie lepiej i używa mniej więcej tej samej metody co PO, bez ostatecznego obliczenia rozbieżności, co sprawia, że myślę, że OP zobaczyłby podobne ulepszenia na swoich zdjęciach, gdyby zostały dostarczone.
Filtrowanie postów
W dokumentacji OpenCV jest dobry artykuł na ten temat. Polecam przyjrzeć się temu, jeśli potrzebujesz naprawdę gładkich map.
Przykładowe zdjęcia powyżej są klatką 1 ze sceny
ambush_2
w zestawie danych MPI Sintel .Pełny kod (testowany na 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. /programming/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()
źródło
Może być kilka możliwych problemów powodujących niską jakość
Depth Channel
iDisparity Channel
co prowadzi nas do niskiej jakości sekwencji stereo. Oto 6 z tych problemów:Możliwy problem I
Jak
uncalibrated
sugeruje słowo ,stereoRectifyUncalibrated
metoda instancji oblicza transformacje rektyfikacyjne dla ciebie, na wypadek, gdybyś nie wiedział lub nie mógł znać wewnętrznych parametrów twojej stereo pary i jej względnej pozycji w środowisku.gdzie:
# 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
Twoja metoda wygląda następująco:
cv2.StereoRectifyUncalibrated(p1fNew, p2fNew, F, (2048, 2048))
Tak więc, nie biorą pod uwagę trzy parametry:
rhm1
,rhm2
ithres
. Jeśli athreshold > 0
, wszystkie pary punktów, które nie są zgodne z geometrią epipolarną, są odrzucane przed obliczeniem homografii. W przeciwnym razie wszystkie punkty są uważane za nieliniowe. Ta formuła wygląda następująco:(pts2[i]^t * fm * pts1[i]) > thres # t –> translation vector between coordinate systems of cameras
Dlatego uważam, że niedokładności wizualne mogą pojawić się z powodu niepełnego obliczenia wzoru.
Możesz przeczytać Kalibrację aparatu i rekonstrukcję 3D w oficjalnych zasobach.
Możliwy problem II
interaxial distance
Musi istnieć solidność między lewym i prawym obiektywem aparatunot greater than 200 mm
. Gdyinteraxial distance
jest większy niżinterocular
odległość, efekt jest nazywanyhyperstereoscopy
lubhyperdivergence
i powoduje nie tylko wyolbrzymienie głębi sceny, ale także fizyczne niedogodności dla widza. Przeczytaj artykuł przeglądowy Autodesk dotyczący stereoskopowych filmów, aby dowiedzieć się więcej na ten temat.Możliwy problem III
Wynikowe niedokładności wizualne
Disparity Map
mogą wynikać z nieprawidłowego obliczenia trybu aparatu. Wielu stereografów woli,Toe-In camera mode
ale na przykład Pixar woliParallel camera mode
.Możliwy problem IV
W stereoskopii, jeśli nastąpi przesunięcie w pionie (nawet jeśli jeden z widoków zostanie przesunięty w górę o 1 mm), zrujnuje to solidne wrażenia stereo. Tak więc przed wygenerowaniem
Disparity Map
musisz się upewnić, że lewy i prawy widok pary stereo są odpowiednio wyrównane. Zobacz artykuł Technicolor Sterreoscopic Whitepaper dotyczący 15 typowych problemów w stereo.Macierz rektyfikacji stereo:
┌ ┐ | f 0 cx tx | | 0 f cy ty | # use "ty" value to fix vertical shift in one image | 0 0 1 0 | └ ┘
Oto
StereoRectify
metoda: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)
Możliwy problem V
Zniekształcenie obiektywu to bardzo ważny temat w komponowaniu stereo. Przed wygenerowaniem
Disparity Map
należy cofnąć zniekształcenie lewego i prawego widoku, po tym wygenerować kanał rozbieżności, a następnie ponownie złożyć oba widoki.Możliwy problem VI
Aby stworzyć wysokiej jakości
Disparity Map
, potrzebujesz lewej i prawej strony,Depth Channels
które muszą być wstępnie wygenerowane. Kiedy pracujesz w pakiecie 3D, możesz renderować wysokiej jakości kanał głębi (z ostrymi krawędziami) za pomocą jednego kliknięcia. Ale generowanie wysokiej jakości kanału głębi z sekwencji wideo nie jest łatwe, ponieważ para stereo musi poruszać się w twoim środowisku, aby wytworzyć początkowe dane dla przyszłego algorytmu głębi z ruchu. Jeśli w klatce nie ma ruchu, kanał głębokości będzie wyjątkowo słaby.Fragment kodu kanału rozbieżności:
Tutaj chciałbym przedstawić szybkie podejście do generowania
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()
źródło