教程:使用iPhone相機和openCV來完成3D重建
本文為 AI 研習社編譯的技術博客,原標題 :
Tutorial: Stereo 3D reconstruction with openCV using an iPhone camera. Part II.
作者 |Omar Padierna
翻譯 | Disillusion、yaya牙牙、AIfresher
校對 | Disillusion 審核 | 鄧普斯?傑弗 整理 | 菠蘿妹
https://medium.com/@omar.ps16/stereo-3d-reconstruction-with-opencv-using-an-iphone-camera-part-ii-77754b58bfe0
教程:使用iPhone相機和openCV來完成3D重建
(第二部分)
歡迎來到關於立體重建三部曲的第2部。在本節中,我們將討論如何校準您的相機。
就像之前所提到的,照相機的鏡頭使你拍的照片失真。這在三維重建中是很麻煩的,所以我們需要糾正這個問題。在校正之前,我們需要知道我們所使用的相機的內部參數。
有時這些參數是未知的,但幸運的是,OpenCV有一個專門針對這個的演算法, 我們可以應用該演算法開始我們的3D重建。
在整個設計過程中,我們會用到Python 3.7.1, OpenCV 3.4.4. 還有一些python第三方庫: Numpy, Glob,tqdm和Pillow。因此,首先確保這些工具都已經安裝好。
OpenCV中攝像機的標定過程是讓計算機用棋盤圖形掃描一幅圖像,用不同的圖像多次識別內部的角。
OpenCV提供的模型校準示例
大多數關於相機校準的教程都是關於網路攝像機或其他攝像機的,因此它們是為分析每一個單獨的幀(或多個幀)而定製的。
在我們的情況下,我們想校準的是手機攝像頭,所以我們不能這樣做。為了讓計算機正確地校準手機攝像頭,它需要幾個相同模式的例子。
在處理視頻流時,我們可以分析用戶在攝像機前移動時的每一幀,直到移動的模型被多次檢測到(這通常是一個任意的測量,但一般是至少有10次檢測)。
因為我們不處理視頻流,所以我們必須為相同的模型拍多張照片。
步驟1. 得到一個棋盤圖案.
前往這個鏈接,把這個棋盤圖案列印在一張紙上。為求完美結果,確保每個正方形都是30毫米長(儘管這不是特別重要)。
步驟2. 把棋盤圖案掛在白色的牆上.
當我運行這個演算法時,我注意到背景中的東西越多,計算相機矩陣的時間就越長。所以我用了一面白色的牆,把棋盤圖案貼在上面。確保它是完全平坦的。
根據棋盤圖案重新裝飾房子
步驟3. 拍幾張圖案的照片.
這一步很重要,確保你拍的照片有很好的光照,並且圖案是從不同的角度拍攝的。還要確保圖案位於屏幕的不同部分。
如果你只拍攝居中的照片,可能會發生錯誤的校準。確保你的照片有很多變化。
這是一個很好的關於如何拍攝圖案的例子。來自烏特卡什·西納。
在Utkarsh Sinah的博客(AI shack)中可以找到關於相機校準和如何捕捉圖案的豐富資源。如果你對C 校準相機的方法感興趣,你應該去看看。
需要指出的是,並不是所有的圖片都適合檢測模式。而你事先很難知道哪些照片會起作用,因此拍儘可能多的照片是一個好主意。我拍了64張。
步驟4:讓我們開始代碼部分
一旦你拍了足夠多的照片,是時候寫一些代碼了(記住整個代碼都在這裡)。第一步是選擇棋盤大小。雖然大小完全是任意的,但建議您選擇一個不對稱的大小(即矩形,而不是正方形)。此處,我選擇了7X5。
importcv2
importnumpyasnp
importglob
fromtqdmimporttqdm
importPIL.ExifTags
importPIL.Image
#============================================
# Camera calibration
#============================================
#Define size of chessboard target.
chessboard_size = (7,5)
第二步是定義一個網格來存儲所有點。存儲的點需要是有序的,如:(0,0,0),(1,0,0),(2,0,0)….,(6,5,0)
#Define arrays to save detected points
obj_points = []#3D points in real world space
img_points = []#3D points in image plane
#Prepare grid and points to display
objp = np.zeros((np.prod(chessboard_size),3),dtype=np.float32)
objp[:,:2] = np.mgrid[:chessboard_size[],
:chessboard_size[1]].T.reshape(-1,2)
因為我們要處理幾個圖像,所以我們可以使用glob迭代地打開它們。此外,由於OpenCv中的角點檢測演算法需要一些時間來處理,因此我們可以用tqdm包裹我們的循環,以了解距離處理上一個圖像已經有多長時間了,還剩下多少圖像沒有處理。
#read images
calibration_paths =glob.glob("./calibration_images/*")
#Iterate over imagestofindintrinsic matrix
forimage_path in tqdm(calibration_paths):
#Load image
image = cv2.imread(image_path)
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
print("Image loaded, Analizying...")
#find chessboard corners
ret,corners = cv2.findChessboardCorners(gray_image,
chessboard_size, None)
ifret== True:
print("Chessboard detected!")
print(image_path)
#define criteriaforsubpixel accuracy
criteria = (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER,
30,0.001)
#refine corner location (tosubpixel accuracy) basedoncriteria.
cv2.cornerSubPix(gray_image, corners, (5,5), (-1,-1), criteria)
obj_points.append(objp)
img_points.append(corners)
在這個循環里,所有的魔法都在發生。載入圖像後,我們必須將先其轉換為灰度圖,然後使用findchessboardcorners演算法。
此演算法將返回檢測到的角點和一個名為ret的標誌而且如果演算法能夠檢測到模式,則返回true。
為了提高標定演算法的精度,將角點位置細化到亞像素精度。在這種情況下,我們必須定義定位所需的標準。
我們採用了如果的標準定義:標準=(類型、迭代次數、精度)。在這個例子中,我們告訴演算法我們關心迭代次數和精度(cv2.term_criteria_eps cv2.term_criteria_max_iter),我們選擇了30次迭代,精度為0.001。
cv2.cornerSubPix是一種專註於重新定位點的演算法。它接收圖像、角點、窗口大小、零區域和實際條件作為輸入。窗口大小是搜索區域。
關注這個演算法不是很重要,我只是決定對參數進行評論,因為大多數教程只是對這個演算法進行了潤色。如果想要了解有關其工作原理的更多信息,請查看此處。
分析完所有圖片後,我們運行cv2.calibratecamera演算法。這是輸出相機參數的演算法。該演算法返回攝像機矩陣(k)畸變係數(dist)和旋轉和平移矢量(rvecs和tvecs)。
#Calibrate camera
ret, K, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points,gray_image.shape[::-1],None,None)
#Save parameters into numpy file
np.save("./camera_params/ret", ret)
np.save("./camera_params/K", K)
np.save("./camera_params/dist", dist)
np.save("./camera_params/rvecs", rvecs)
np.save("./camera_params/tvecs", tvecs)
注意,我們將這些值保存到不同的numpy文件中。我之所以選擇這樣做是因為實用性。由於運行這個腳本需要一段時間,所以每次我們想要重建某個東西時都要執行它是不方便的(也是不必要的)。
因此,只需將所有內容保存到一個numpy文件中,並在以後載入就更容易了。為什麼是numpy而不是xml或json?因為對於numpy文件,不需要解析數據。
要進行三維重建,我們真正關心的是3個參數:相機矩陣、畸變係數和焦距。焦距可以從相機矩陣中推導出來。
儘管如此,為了學習,我決定決定從EXIF數據中包含的圖像中獲取焦距信息。相機手機的焦距信息是保存在EXIF數據中的。
#Get exif datainorder togetfocal length.
exif_img = PIL.Image.open(calibration_paths[])
exif_data = {
PIL.ExifTags.TAGS[k]:v
fork, vinexif_img._getexif().items()
ifkinPIL.ExifTags.TAGS}
#Get focal lengthintuple form
focal_length_exif = exif_data["FocalLength"]
#Get focal lengthindecimal form
focal_length = focal_length_exif[]/focal_length_exif[1]
np.save("./camera_params/FocalLength", focal_length)
Exif 數據可以用pillow解析成字典形式,但是,鍵值將以數字形式給出,這也是我們必須使用ExifTags模塊才能將其轉換為可讀形式的原因。
最後,我們需要有一種方法來測量我們的校準有多精確。我們有兩種方法可以做到這一點。視覺方式和數字方式。
數值方法包括計算投影點的總誤差。如果您注意到,在腳本的開頭,我們聲明了兩個數組,對象點(3d點)和圖像點(2d點)。
這些是在校準過程中反覆獲得的。這裡的目標是使用在校準循環中計算的旋轉和平移向量將三維點投影到二維平面中。然後將這些新投影點(腳本中稱為img_points2)與在計算循環中獲得的圖像點進行比較。
然後我們計算每個點的誤差,得到平均值。此錯誤應儘可能接近0。在我的例子中,誤差是0.44,比隨機稍好。
視覺上的方法是對相機拍攝的圖像(最好是顯示一些曲線扭曲的模式之一)做畫面扭曲消除。目標是用演算法消除透鏡畸變,如果它做得正確,那麼你就有了一個好的標定。
Utkarsh Sinah做的畫面扭曲消除結果。由Ai Shack的Utkarsh Sinah提供。
如果您的錯誤太高,請確保您檢測到棋盤至少10次,並確保圖片是不同的。
我也必須警告你,你必須要有耐心,尤其是當涉及到大圖像時。在我的例子中,校準演算法需要1.5小時才能完成。
一旦標定完成,您就可以計算出視差圖了,這是我們將在下一部分討論的主題。
再見!


※雷鋒網正式揭曉「AI 最佳掘金案例年度榜單」
※對話系統中的自然語言生成技術
TAG:AI研習社 |