Ich versuche, eine Tiefenkarte mit einer nicht kalibrierten Methode zu erhalten. Ich kann die Grundmatrix erhalten, indem ich mit SIFT entsprechende Punkte finde und dann benutze cv2.findFundamentalMat
. Ich benutze dann cv2.stereoRectifyUncalibrated
, um die Homographiematrizen für jedes Bild zu erhalten. Schließlich verwende ich cv2.warpPerspective
, um die Disparität zu korrigieren und zu berechnen, aber dies erzeugt keine gute Tiefenkarte. Die Werte sind sehr hoch, daher frage ich mich, ob ich warpPerspective
eine Rotationsmatrix aus den Homografiematrizen verwenden muss, mit denen ich sie erhalten habe stereoRectifyUncalibrated
.
Ich bin mir nicht sicher über die projektive Matrix mit dem Fall der Homographiematrix, die mit dem stereoRectifyUncalibrated
zu korrigieren ist.
Ein Teil des Codes:
#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()
Hier sind die korrigierten Bilder mit der nicht kalibrierten Methode (und warpPerspective
):
Hier sind die korrigierten Bilder mit der kalibrierten Methode:
Ich weiß nicht, wie wichtig der Unterschied zwischen den beiden Arten von Bildern ist. Und für die kalibrierte Methode scheint sie nicht ausgerichtet zu sein.
Die Disparitätskarte mit der nicht kalibrierten Methode:
Die Tiefen werden berechnet mit: C1[0,0]*T[0]/(disp)
mit T aus dem stereoCalibrate
. Die Werte sind sehr hoch.
------------ SPÄTER BEARBEITEN ------------
Ich habe versucht, die Rekonstruktionsmatrix ( [Devernay97] , [Garcia01] ) mit der mit "stereoRectifyUncalibrated" erhaltenen Homographiematrix zu "mounten", aber das Ergebnis ist immer noch nicht gut. Mache ich das richtig
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)
quelle
StereoBM
Es ist nicht der beste Matching-AlgorithmusStereoSGBM
. Ich würde gerne helfen, aber ich habe Ihre Frage nicht vollständig verstanden ...Antworten:
TLDR; Verwenden Sie StereoSGBM (Semi Global Block Matching) für Bilder mit glatteren Kanten und verwenden Sie eine Nachfilterung, wenn Sie möchten, dass es noch glatter wird
OP hat keine Originalbilder bereitgestellt, daher verwende ich
Tsukuba
aus dem Middlebury-Datensatz .Ergebnis mit normalem StereoBM
Ergebnis mit StereoSGBM (abgestimmt)
Das beste Ergebnis, das ich in der Literatur finden konnte
Einzelheiten finden Sie in der Veröffentlichung hier .
Beispiel für eine Nachfilterung (siehe Link unten)
Theorie / Andere Überlegungen aus der Frage von OP
Die großen schwarzen Bereiche Ihrer kalibrierten korrigierten Bilder lassen mich glauben, dass die Kalibrierung für diese nicht sehr gut durchgeführt wurde. Es gibt eine Vielzahl von Gründen, die im Spiel sein könnten, vielleicht die physische Einrichtung, vielleicht die Beleuchtung, als Sie die Kalibrierung durchgeführt haben usw., aber es gibt viele Tutorials zur Kamerakalibrierung, und ich verstehe, dass Sie nach einem Weg fragen Holen Sie sich eine bessere Tiefenkarte aus einem nicht kalibrierten Setup (dies ist nicht 100% klar, aber der Titel scheint dies zu unterstützen, und ich denke, das werden die Leute hierher kommen, um zu versuchen, es zu finden).
Ihr grundlegender Ansatz ist korrekt, aber die Ergebnisse können definitiv verbessert werden. Diese Form der Tiefenabbildung gehört nicht zu denjenigen, die Karten mit der höchsten Qualität erzeugen (insbesondere wenn sie nicht kalibriert sind). Die größte Verbesserung wird wahrscheinlich durch die Verwendung eines anderen Stereo-Matching-Algorithmus erzielt. Die Beleuchtung kann auch einen signifikanten Effekt haben. Das richtige Bild (zumindest für mein bloßes Auge) scheint weniger gut beleuchtet zu sein, was die Rekonstruktion beeinträchtigen könnte. Sie können zuerst versuchen, es auf das gleiche Niveau wie das andere aufzuhellen, oder neue Bilder sammeln, wenn dies möglich ist. Von hier aus gehe ich davon aus, dass Sie keinen Zugriff auf die Originalkameras haben. Daher werde ich erwägen, neue Bilder zu sammeln, das Setup zu ändern oder eine Kalibrierung durchzuführen, um außerhalb des Bereichs zu liegen. (Wenn Sie Zugriff auf das Setup und die Kameras haben,
Sie haben
StereoBM
für die Berechnung Ihrer Disparität (Tiefenkarte) verwendet, was zwar funktioniert, aberStereoSGBM
für diese Anwendung viel besser geeignet ist (es behandelt glattere Kanten besser). Sie können den Unterschied unten sehen.Dieser Artikel erklärt die Unterschiede ausführlicher:
Ohne explizite intrinsische Kameraparameter, Einzelheiten zum Kamera-Setup (wie Brennweite, Entfernung zwischen den Kameras, Entfernung zum Motiv usw.), eine bekannte Dimension im Bild oder Bewegung (um Struktur aus Bewegung zu verwenden ) können Sie Erhalten Sie nur eine 3D-Rekonstruktion bis zu einer projektiven Transformation. Sie haben auch keinen Sinn für Skalierung oder notwendigerweise Rotation, können aber dennoch eine relative Tiefenkarte erstellen. Sie werden wahrscheinlich unter einigen Fass- und anderen Verzerrungen leiden, die mit einer ordnungsgemäßen Kamerakalibrierung beseitigt werden könnten, aber Sie können ohne diese vernünftige Ergebnisse erzielen, solange die Kameras nicht schrecklich sind (das Objektivsystem ist nicht zu verzerrt) und hübsch eingerichtet sind nahe an der kanonischen Konfiguration(was im Grunde bedeutet, dass sie so ausgerichtet sind, dass ihre optischen Achsen so nahe wie möglich an der Parallele liegen und sich ihre Sichtfelder ausreichend überlappen). Dies scheint jedoch nicht das Problem des OP zu sein, da er es geschafft hat, mit der nicht kalibrierten Methode in Ordnung zu kommen, um korrigierte Bilder zu erhalten.
Grundlegende Vorgehensweise
findFundamentalMat
stereoRectifyUncalibrated
undwarpPerspective
StereoSGBM
Die Ergebnisse sind viel besser:
Stimmt mit ORB und FLANN überein
Unverzerrte Bilder (links, dann rechts)
Ungleichheit
StereoBM
Dieses Ergebnis ähnelt den OP-Problemen (Fleckenbildung, Lücken, falsche Tiefen in einigen Bereichen).
StereoSGBM (abgestimmt)
Dieses Ergebnis sieht viel besser aus und verwendet ungefähr die gleiche Methode wie das OP, abzüglich der endgültigen Disparitätsberechnung, was mich glauben lässt, dass das OP ähnliche Verbesserungen an seinen Bildern sehen würde, wenn sie bereitgestellt worden wären.
Nachfilterung
Es gibt einen guten Artikel darüber in den OpenCV-Dokumenten. Ich würde empfehlen, es sich anzusehen, wenn Sie wirklich glatte Karten benötigen.
Die obigen Beispielfotos sind Bild 1 aus der Szene
ambush_2
im MPI Sintel-Datensatz .Vollständiger Code (getestet unter 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()
quelle
Möglicherweise gibt es mehrere mögliche Probleme, die zu einer schlechten Qualität führen,
Depth Channel
undDisparity Channel
was uns zu einer Stereosequenz von geringer Qualität führt. Hier sind 6 dieser Probleme:Mögliches Problem I.
Wie ein Wort
uncalibrated
schon sagt,stereoRectifyUncalibrated
berechnet die Instanzmethode eine Gleichrichtungstransformation für Sie, falls Sie die intrinsischen Parameter Ihres Stereopaars und seine relative Position in der Umgebung nicht kennen oder nicht kennen.wo:
# 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
Und Ihre Methode sieht so aus:
cv2.StereoRectifyUncalibrated(p1fNew, p2fNew, F, (2048, 2048))
Also, Sie berücksichtigen nicht die drei Parameter:
rhm1
,rhm2
undthres
. Wenn athreshold > 0
, werden alle Punktpaare, die keiner epipolaren Geometrie entsprechen, vor der Berechnung der Homografien verworfen. Ansonsten gelten alle Punkte als Lieferanten. Diese Formel sieht folgendermaßen aus:(pts2[i]^t * fm * pts1[i]) > thres # t –> translation vector between coordinate systems of cameras
Daher glaube ich, dass visuelle Ungenauigkeiten aufgrund der Berechnung einer unvollständigen Formel auftreten können.
Sie können die Kamerakalibrierung und 3D-Rekonstruktion in der offiziellen Ressource lesen .
Mögliches Problem II
Ein robustes
interaxial distance
zwischen linkem und rechtem Kameraobjektiv muss seinnot greater than 200 mm
. Wenn dasinteraxial distance
größer als dieinterocular
Entfernung ist, wird der Effekt aufgerufenhyperstereoscopy
oderhyperdivergence
und führt nicht nur zu einer Tiefenübertreibung in der Szene, sondern auch zu physischen Unannehmlichkeiten für den Betrachter. Lesen Sie das Whitepaper zum stereoskopischen Filmemachen von Autodesk , um mehr zu diesem Thema zu erfahren.Mögliches Problem III
Visuelle Ungenauigkeiten
Disparity Map
können aufgrund einer falschen Berechnung des Kameramodus auftreten. Viele Stereographen bevorzugen,Toe-In camera mode
aber Pixar zum Beispiel bevorzugtParallel camera mode
.Mögliches Problem IV
Wenn in der Stereoskopie eine vertikale Verschiebung auftritt (selbst wenn eine der Ansichten um 1 mm nach oben verschoben ist), wird ein robustes Stereoerlebnis ruiniert. Vor dem Generieren
Disparity Map
müssen Sie also sicherstellen, dass die linke und die rechte Ansicht Ihres Stereopaars entsprechend ausgerichtet sind. Schauen Sie sich das Technicolor Sterreoscopic Whitepaper über 15 häufig auftretende Probleme in Stereo an.Stereo-Gleichrichtungsmatrix:
┌ ┐ | f 0 cx tx | | 0 f cy ty | # use "ty" value to fix vertical shift in one image | 0 0 1 0 | └ ┘
Hier ist eine
StereoRectify
Methode: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)
Mögliches Problem V.
Objektivverzerrung ist ein sehr wichtiges Thema in der Stereokomposition. Vor dem Generieren einer müssen
Disparity Map
Sie die linke und rechte Ansicht entzerren, danach einen Disparitätskanal generieren und dann beide Ansichten erneut verzerren.Mögliches Problem VI
Um eine hohe Qualität zu erstellen
Disparity Map
, benötigen Sie links und rechtsDepth Channels
, die vorgeneriert werden müssen. Wenn Sie in einem 3D-Paket arbeiten, können Sie mit nur einem Klick einen hochwertigen Tiefenkanal (mit scharfen Kanten) rendern. Das Generieren eines hochwertigen Tiefenkanals aus einer Videosequenz ist jedoch nicht einfach, da sich das Stereopaar in Ihrer Umgebung bewegen muss, um erste Daten für den zukünftigen Tiefen-aus-Bewegung-Algorithmus zu erstellen. Wenn sich in einem Frame keine Bewegung befindet, ist ein Tiefenkanal extrem schlecht.Disparity Channel Code Snippet:
Hier möchte ich einen schnellen Ansatz darstellen, um Folgendes zu generieren
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()
quelle