使用iPhone相机和OpenCV来完成3D重建(第三部分)
正文字數:4509 ?閱讀時長:2分鐘
歡迎來到本教程的第三部分,也是最后一部分關于立體重建的教程。
Posted by?Omar Padierna?
url :?https://medium.com/@omar.ps16/stereo-3d-reconstruction-with-opencv-using-an-iphone-camera-part-iii-95460d3eddf0
快速回顧:
在第一部分中,我們簡要介紹了立體三維重建所需的步驟以及立體重建工作的原理和要點。
在第二部分中,我們分析了一個腳本來計算攝像機矩陣和失真系數。這些都是三維重建過程中相機的固有參數。
一旦我們的相機被校準,我們就可以利用來自同一個物體的一對照片完成重建。在大多數立體聲應用程序中,你會發現每張照片都是從兩個單獨的攝像頭拍攝的,如下圖所示
用于三維重建的典型雙攝像頭系統
人們這樣做的原因是因為兩個攝像頭在同一高度(比如我們的眼睛)是非常重要的。我們在這個教程中,我們只是使用了手機的攝像頭,沒有使用這種相機,因此我們不需要進行類似的設置。如果你想制作自己的雙攝像頭系統以獲得更好的效果,那么就你可以去嘗試閱讀Daniel Lee的博客。
我們仍然需要一對圖片來生成視差圖。在這種情況下,我讓別人給我拍了兩張照片,同時小心地水平移動相機,確保沒有垂直移動(說起來容易做起來難)。如果用手或是自己動手太困難,你可以用三腳架。
我胖乎乎的樣子
一旦我們有了照片,我們就要花一些時間開始寫一些代碼。我們將從加載攝像機矩陣和現實上面得到的圖片開始。作為一個友好的提醒,請記住完整的腳本可以在這里(https://github.com/OmarPadierna/3DReconstruction)找到。
#========================================================= # Stereo 3D reconstruction #========================================================= #Load camera parameters ret = np.load('./camera_params/ret.npy') K = np.load('./camera_params/K.npy') dist = np.load('./camera_params/dist.npy') #Specify image paths img_path1 = './reconstruct_this/left2.jpg' img_path2 = './reconstruct_this/right2.jpg' #Load pictures img_1 = cv2.imread(img_path1) img_2 = cv2.imread(img_path2) #Get height and width. Note: It assumes that both pictures are the same size. They HAVE to be same size h,w = img_2.shape[:2]然后,基于自由縮放參數,計算出最佳攝像機矩陣。實際上,該算法需要算出一種新的攝像機矩陣,如果我們改變圖像大小的話。雖然我們沒有實際改變它,但我注意到,通過該算法得到的攝像機矩陣在消除失真方面會得到更好的結果。
#Get optimal camera matrix for better undistortion new_camera_matrix, roi = cv2.getOptimalNewCameraMatrix(K,dist,(w,h),1,(w,h)) #Undistort images img_1_undistorted = cv2.undistort(img_1, K, dist, None, new_camera_matrix) img_2_undistorted = cv2.undistort(img_2, K, dist, None, new_camera_matrix) #Downsample each image 3 times (because they're too big) img_1_downsampled = downsample_image(img_1_undistorted,3) img_2_downsampled = downsample_image(img_2_undistorted,3)最后,一旦圖像沒有失真,我們就對他們進行降采樣。
降采樣有兩個功能:1)提高圖像處理速度? 2)在計算視差圖時幫助調整參數
在關于特征匹配算法中,了解圖像的大小是非常重要的。這是因為對于我們使用的算法,我們需要指定一個窗口大小。窗口大小越大,相對應的需要計算時間越長。
如果窗口大小不夠大,那么視差將無法正確計算,您將得到一個包含各種噪聲的深度圖(或不完整的深度圖)。這對我們的目標是不利的,所以最好對圖像進行降采樣。本教程中暫時不討論用于對圖像進行降采樣的函數,但它會在完整腳本(https://github.com/OmarPadierna/3DReconstruction/blob/master/Reconstruction/disparity.py)的頂部進行聲明。
必須指出的是,通過對圖像進行降采樣,我們不可避免地會丟失信息,因此我們的深度精度也會受到影響。在我看來,如果深度精度對你很重要,那么你最好使用基于激光或紅外傳感器來繪制深度圖。眾所周知,立體深度圖并不是十分準確。
一旦圖像準備好進行處理,我們就可以使用特征匹配算法。根據《學習opencv3》(http://shop.oreilly.com/product/0636920044765.do)一書,立體匹配的標準典型技術是塊匹配。opencv提供了兩種塊匹配實現:立體塊匹配和半全局塊匹配(SGBM)。兩種算法相似,但有區別。
塊匹配的關鍵是在可視區域重疊的兩幅圖像之間尋找強匹配點。通俗地說,這意味著算法將在捕獲同一對象(即相同的事物)的兩張圖片中尋找相同的像素。
塊匹配側重于高紋理圖像(比如樹的圖片),而半全局塊匹配則側重于子像素級的匹配和紋理更平滑的圖片(比如走廊的圖片)。
在本教程中,我們使用SGBM,因為這些照片是在室內拍攝的,而且其中有許多平滑的紋理。該算法有三個重要的步驟需要理解。
如果沒有對這些步驟的直觀的理解,使用SGBM算法將非常困難,因為它接收到的參數取決于對它正在做什么的理解(即使是表面和膚淺的)。
實際上,該算法有3個步驟:
????1. 預過濾圖像,用于歸一化亮度,增強紋理
????2. 使用SAD窗口沿水平極線執行相對應的搜索
??? 3. 后過濾圖像,以消除不良的相關匹配。
為了完成亮度歸一化并增強紋理操作,我們在圖像上運行一個窗口(至少5x5,最大21x21)。修改這個窗口大小的參數在代碼中稱之為win_size。
然后通過滑動SAD窗口來計算相關性。在繼續執行之前,從概念上理解什么是極線是很重要的。OpenCV有一個很好的教程,教你如何編寫一些代碼來可視化它們。
為了更好地理解極線,我們可以做以下練習。手放在臉中間,閉上左眼。然后做對位操作(即閉上右眼,睜開左眼)。你會注意到你手的位置有輕微的變化。
一個我理解下的直觀解釋
因為你的眼睛處于不同的位置,一只眼睛可以看到另一只眼睛看不到的東西。只睜開一只眼,你就看不見你手上的3D點,因為所有的點都投射到你臉上相同的同一圖像平面上(即你看不到背后是什么東西)。
然而,另一只眼睛既可以看到它的相對部分在看什么,也可以看到由于它們之間的分離而隱藏的一些東西。再試一次,用你的眼睛親眼看看,你會注意到,用一只眼睛你能看到某些東西(尤其是在背景中),而另一只眼睛卻看不到。
好吧,那又怎樣?好吧,當你改變哪只眼睛睜開,哪只眼睛閉上時,你會無意識地把焦點轉移到你感興趣的東西上(在這個例子中是你的手),你可以通過跟隨一條線來實現。這條線被稱為“極線”。
通過合并雙眼的信息,你就可以對你所看到的東西的三維坐標進行三角測量,這就是你理解深度的方法。
相機的原理是一樣的,當你用兩個平行的相機拍一張照片(或者在一種情況下,兩張照片用同一個相機移動才能夠得到時),你知道一張照片將包含另一張沿極線的點。
OpenCV對極線幾何有一個更正式(也更好)(https://docs.opencv.org/3.4.4/da/de9/tutorial_py_epipolar_geometry.html)的解釋圖片壽命。點進去看看不是件壞事。
對極幾何的解釋。紫色的線是興趣點x所在的極線
為什么極線相關?好吧,因為在對圖像進行去失真處理后,極線是水平的,而且由于我們確定興趣點將沿著極線找到,這樣,通過SGBM算法遍歷它們,就能可以找到匹配項。
這就是第二步的全部內容。然而,我們需要告訴它在什么程度上視差(即偏移量)是可以接受的。為此,我們必須規定最小和最大的差距。這里的目標是通過減去它們來計算差異的數量,這是一種指定圖像中像素可以移動的可接受范圍的方法。
解釋最小和最大差異。布拉德斯基和卡勒的《學習OpenCV3》
最后一步是做一些后處理。在進行特征匹配后,有可能出現誤報和假證樣本(即錯誤匹配)。為了糾正這些錯誤,OpenCV有一個唯一性比率,它是匹配值的閾值。
最后,基于塊的匹配可能在目標邊界附近存在問題(因為一張圖片可以看到“后面”,而另一張則看不到,還記得嗎?)這就形成了一個由許多微小差異組成的區域,稱為“斑點”。為了保護它們,我們必須設置一個斑點窗口,接受這些“斑點”的區域。?
在SGBM算法的特定情況下,有一個名為disp12MaxDiff的參數,它指定從左到右計算的差異與從右到左計算的差異之間允許的最大差異。
如果差異之間的差異超過該閾值,則像素將被宣布為未知。
如果你想知道更多更好的解釋這些算法的內容,建議閱讀Gari Bradski和Adrian Kaehler合著的《Learning Open CV 3》一書。它還有c++版本的3D重建。
在代碼方面,這意味著我們必須定義SGBM對象并設置參數,然后計算視差,如下所示:
#Set disparity parameters #Note: disparity range is tuned according to specific parameters obtained through trial and error. win_size = 5 min_disp = -1 max_disp = 63 #min_disp * 9 num_disp = max_disp - min_disp # Needs to be divisible by 16 #Create Block matching object. stereo = cv2.StereoSGBM_create(minDisparity= min_disp,numDisparities = num_disp,blockSize = 5,uniquenessRatio = 5,speckleWindowSize = 5,speckleRange = 5,disp12MaxDiff = 1,P1 = 8*3*win_size**2,#8*3*win_size**2,P2 =32*3*win_size**2) #32*3*win_size**2) #Compute disparity map print ("\nComputing the disparity map...") disparity_map = stereo.compute(img_1_downsampled, img_2_downsampled)#Show disparity map before generating 3D cloud to verify that point cloud will be usable. plt.imshow(disparity_map,'gray') plt.show()請注意,這些參數和我拍攝的圖片非常匹配。在實踐中,這將需要手動微調,并進行大量的嘗試和錯誤。這就是為什么在將視差圖轉換為點云之前,將其可視化非常方便的原因。
經過多次的嘗試和錯誤,我的視差圖最終是這樣的。
我自己的視差圖
如你所見,這個視差圖在我襯衫的區域有很多死點和斑點。而且,我的嘴不見了,似乎噪聲很多。這是因為我沒有很好地調整SBGM參數。
當圖片被適當地扭曲和SGBM算法被很好地調整,你將得到平滑的視差圖,如下所示。這個視差圖來自于cones dataset(http://vision.middlebury.edu/stereo/data/)。
光滑的差距地圖
優化視差圖的最佳方法是在算法的基礎上構建一個GUI,并實時優化視差圖,以獲得更平滑的圖像。在未來我將上傳一個GUI,以便實時微調,同時我們將使用這個視差圖。
一旦我們計算出視差圖,我們就必須得到圖像中使用的顏色數組。因為我們減少了圖像的采樣,所以我們需要得到圖像的高度和寬度。
更重要的是我們需要得到變換矩陣。這個矩陣負責將深度和顏色重新投影到三維空間中。opencv的文檔中有一個轉換矩陣的例子。
大多數例子將使用OpenCV文檔中的轉換矩陣。在我的情況下,事情并不是那么順利。環顧四周,我發現了一個更通用的矩陣,我的矩陣就是以這個為基礎的。
轉換矩陣——來自于Didier Stricker教授
#Generate point cloud. print ("\nGenerating the 3D map...") #Get new downsampled width and height h,w = img_2_downsampled.shape[:2] #Load focal length. focal_length = np.load('./camera_params/FocalLength.npy') #Perspective transformation matrix #This transformation matrix is from the openCV documentation, didn't seem to work for me. Q = np.float32([[1,0,0,-w/2.0],[0,-1,0,h/2.0],[0,0,0,-focal_length],[0,0,1,0]]) #This transformation matrix is derived from Prof. Didier Stricker's power point presentation on computer vision. #Link : https://ags.cs.uni-kl.de/fileadmin/inf_ags/3dcv-ws14-15/3DCV_lec01_camera.pdf Q2 = np.float32([[1,0,0,0],[0,-1,0,0],[0,0,focal_length*0.05,0], #Focal length multiplication obtained experimentally. [0,0,0,1]]) #Reproject points into 3D points_3D = cv2.reprojectImageTo3D(disparity_map, Q2) #Get color points colors = cv2.cvtColor(img_1_downsampled, cv2.COLOR_BGR2RGB) #Get rid of points with value 0 (i.e no depth) mask_map = disparity_map > disparity_map.min() #Mask colors and points. output_points = points_3D[mask_map] output_colors = colors[mask_map] #Define name for output file output_file = 'reconstructed.ply' #Generate point cloud print ("\n Creating the output file... \n") create_output(output_points, output_colors, output_file)實際生成點云的算法與我在OpenCV示例中找到的算法完全相同。它是在實際腳本中聲明的,不在本教程的范圍之內。本質上,它會重塑顏色和頂點的形狀,然后將它們一個一個地堆疊起來。
結果生成的數組被寫入一個帶有特定頭文件的文本文件中,該頭文件保存為.ply文件。這個文件可以用meshlab可視化。就我而言,這是我的結果。
Point cloud of?myself
如您所見,圖像看起來有噪聲和畸變,與視差圖的外觀非常相似。根據經驗,如果你的視差圖看起來含有噪聲,那么你的點云就會有點失真。
一個好的視差圖會產生這樣的結果:
平滑視差圖的點云
差不多就是這樣。你可以通過改進你的拍照方式,你的校準方式和微調SGBM算法中的參數來改善結果。
如果您想要一個更完整的點云,那么您應該在感興趣的對象周圍拍攝幾對圖像,并將所有三維點連接起來,以獲得更密集的點云。
我希望這對你的計算機視覺實驗有幫助。
灣區最原汁原味的技術,全球最前沿的應用實踐
無需漂洋過海,我們在線上等您!
LiveVideoStackCon 2020?美國站
2020年12月10日-12月11日
點擊【閱讀原文】了解更多詳細信息
總結
以上是生活随笔為你收集整理的使用iPhone相机和OpenCV来完成3D重建(第三部分)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AMD收购Xilinx、Zoom为全体用
- 下一篇: WebRTC安全体系架构的8个组件