雙目測距與三維重建的OpenCV實現問題集錦(一)影象獲取與單目定標

查志強發表於2016-01-27

【原文:http://blog.csdn.net/chenyusiyuan/article/details/5961769

雙目測距的基本原理

clip_image002[10]

如上圖所示,雙目測距主要是利用了目標點在左右兩幅檢視上成像的橫向座標直接存在的差異(即視差clip_image004 )與目標點到成像平面的距離Z存在著反比例的關係:Z=fT/d。“@scyscyao :在OpenCV中,f的量綱是畫素點,T的量綱由定標板棋盤格的實際尺寸和使用者輸入值確定,一般是以毫米為單位(當然為了精度提高也可以設定為0.1毫米量級),d=xl-xr的量綱也是畫素點。因此分子分母約去,Z的量綱與T相同。 

clip_image006

假設目標點在左檢視中的座標為(x,y),在左右檢視上形成的視差為d,目標點在以左攝像頭光心為原點的世界座標系中的座標為(X,Y,Z),則存在上圖所示的變換矩陣Q,使得 Q*[x y d 1]’ = [X Y Z W]’。

@scyscyao :為了精確地求得某個點在三維空間裡的距離Z,我們需要獲得的引數有焦距f、視差d、攝像頭中心距Tx。如果還需要獲得X座標和Y座標的話,那麼還需要額外知道左右像平面的座標系與立體座標系中原點的偏移cx和cy。其中f, Tx, cx和cy可以通過立體標定獲得初始值,並通過立體校準優化,使得兩個攝像頭在數學上完全平行放置,並且左右攝像頭的cx, cy和f相同(也就是實現圖2中左右檢視完全平行對準的理想形式)。而立體匹配所做的工作,就是在之前的基礎上,求取最後一個變數:視差d(這個d一般需要達到亞畫素精度)。從而最終完成求一個點三維座標所需要的準備工作。在清楚了上述原理之後,我們也就知道了,所有的這幾步:標定、校準和匹配,都是圍繞著如何更精確地獲得 f, d, Tx, cx 和cy 而設計的 。 ”

 

一、影象的獲取

1. 如何開啟兩個或多個攝像頭?

可以通過OpenCV的capture類函式或者結合DirectShow來實現雙攝像頭的捕獲,具體可見我的讀書筆記《OpenCV學習筆記(6)基於 VC+OpenCV+DirectShow 的多個攝像頭同步工作 》。文中曾提及不能用cvCreateCameraCapture 同時讀取兩個攝像頭,不過後來一位研友來信討論說只要把攝像頭指標的建立程式碼按照攝像頭序號降序執行,就可以順利開啟多個攝像頭 ,例如:

 

[c-sharp] view plaincopy
  1. CvCapture* capture2 = cvCreateCameraCapture( 1 );  
  2. CvCapture* capture1 = cvCreateCameraCapture( 0 );  

採用DirectShow的方式讀入時:

 

[c-sharp] view plaincopy
  1. camera2.OpenCamera(1, false, 640,480);  
  2. camera1.OpenCamera(0, false, 640,480);  

 

這樣就可以同時採集兩個攝像頭。我也驗證過這種方法確實有效,而且還解決了我遇到的cvSetCaptureProperty調整幀畫面大小速度過慢的問題。當攝像頭的開啟或建立程式碼按照攝像頭序號從0開始以升序編寫執行時,使用cvSetCaptureProperty就會出現第一個攝像頭(序號為0)的顯示視窗為灰色(即無影象)、且程式執行速度緩慢的現象。而改為降序編寫執行後,則能正常、實時地顯示各攝像頭的畫面。具體原因有待分析討論。

 

2. 如何實現多個攝像頭幀畫面的同步抓取?

在單攝像頭情況下用 cvQueryFrame 即可抓取一幀畫面,實際上這個函式是由兩個routine組成的:cvGrabFrame和cvRetrieveFrame(詳見Learning OpenCV第103頁)。cvGrabFrame將攝像頭幀畫面即時複製到內部快取中,然後通過cvRetrieveFrame把我們預定義的一個IplImage型空指標指向快取內的幀資料。注意這時我們並沒有真正把幀資料取出來,它還儲存在OpenCV的內部快取中,下一次讀取操作就會被覆蓋掉。所以一般我們要另外定義一個IplImage來複制所抓取的幀資料,然後對這個新IplImage進行操作。

由上面的解釋也可以看出,cvGrabFrame的作用就是儘可能快的將攝像頭畫面資料複製到計算機快取,這個功能就方便我們實現對多個攝像頭的同步抓取,即首先用cvGrabFrame依次抓取各個CvCapture*,然後再用cvRetrieveFrame把幀資料取出來。例如:

 

[c-sharp] view plaincopy
  1. cvGrabFrame( lfCam );  
  2. cvGrabFrame( riCam );  
  3. frame1 = cvRetrieveFrame( lfCam );  
  4. frame2 = cvRetrieveFrame( riCam );  
  5. if( !frame1|| !frame2) break;  
  6. cvCopyImage(frame1, image1);   
  7. cvCopyImage(frame2, image2);  

二、攝像頭定標

攝像頭定標一般都需要一個放在攝像頭前的特製的標定參照物(棋盤紙),攝像頭獲取該物體的影象,並由此計算攝像頭的內外引數。標定參照物上的每一個特徵點相對於世界座標系的位置在製作時應精確測定,世界座標系可選為參照物的物體座標系。在得到這些已知點在影象上的投影位置後,可計算出攝像頭的內外引數。

clip_image008

如上圖所示,攝像頭由於光學透鏡的特性使得成像存在著徑向畸變,可由三個引數k1,k2,k3確定;由於裝配方面的誤差,感測器與光學鏡頭之間並非完全平行,因此成像存在切向畸變,可由兩個引數p1,p2確定。單個攝像頭的定標主要是計算出攝像頭的內參(焦距f和成像原點cx,cy、五個畸變引數(一般只需要計算出k1,k2,p1,p2,對於魚眼鏡頭等徑向畸變特別大的才需要計算k3))以及外參(標定物的世界座標)。 OpenCV 中使用的求解焦距和成像原點的演算法是基於張正友的方法( pdf ),而求解畸變引數是基於 Brown 的方法( pdf )。


1. 影象座標系、攝像頭座標系和世界座標系的關係

clip_image010 clip_image012

攝像頭成像幾何關係,其中Oc 點稱為攝像頭(透鏡)的光心,Xc 軸和Yc 軸與影象的x軸和Y軸平行,Zc 軸為攝像頭的光軸,它與影象平面垂直。光軸與影象平面的交點O1 ,即為影象座標系的原點。由點Oc 與Xc 、Yc 、Zc 軸組成的座標系稱為攝像頭座標系,Oc O1 的距離為攝像頭焦距,用f表示。

影象座標系是一個二維平面,又稱為像平面,“@scyscyao :實際上就是攝像頭的CCD感測器的表面。每個CCD感測器都有一定的尺寸,也有一定的解析度,這個就確定了毫米與畫素點之間的轉換關係。舉個例子,CCD的尺寸是8mm X 6mm,幀畫面的解析度設定為640X480,那麼毫米與畫素點之間的轉換關係就是80pixel/mm。”設CCD感測器每個畫素點的物理大小為dx*dy,相應地,就有 dx=dy=1/80。


2. 進行攝像頭定標時,棋盤方格的實際大小 square_size (預設為 1.0f )的設定對定標引數是否有影響?

@scyscyao :當然有。在標定時,需要指定一個棋盤方格的長度,這個長度(一般以毫米為單位,如果需要更精確可以設為0.1毫米量級)與實際長度相同,標 定得出的結果才能用於實際距離測量。一般如果尺寸設定準確的話,通過立體標定得出的Translation向量的第一個分量Tx的絕對值就是左右攝像頭的中心距。一般可以用這個來驗證立體標定的準確度。比如我設定的棋盤格大小為270 (27mm),最終得出的Tx大小就是602.8 (60.28mm),相當精確。”

 

3. 定標所得的攝像頭內引數,即焦距和原點座標,其數值單位都是一致的嗎?怎麼把焦距數值換算為實際的物理量?

@wobject :是的,都是以畫素為單位。假設畫素點的大小為k x l,單位為mm,則fx = f / k, fy = f / (l * sinA), A一般假設為 90°,是指攝像頭座標系的偏斜度(就是鏡頭座標和CCD是否垂直)。攝像頭矩陣(內參)的目的是把影象的點從影象座標轉換成實際物理的三維座標。因此其中的fx, fy, cx, cy 都是使用類似上面的綱量。同樣,Q 中的變數 f,cx, cy 也應該是一樣的。”

 

4. 棋盤影象數目應該取多少對攝像頭定標比較適宜?

OpenCV中文論壇上piao的帖子《在OpenCV中用cvCalibrateCamera2進行相機標定(附程式) 》中指出影響攝像頭定標結果的準確性和穩定性的因素主要有三個:

(1) 標定板所在平面與成像平面(image plane)之間的夾角;

(2) 標定時拍攝的圖片數目(棋盤影象數目);

(3) 影象上角點提取的不準確。

感覺OpenCV1.2以後對影象角點的提取準確度是比較高的,cvFindChessboardCorners 和 cvFindCornerSubPix結合可以獲得很好的角點檢測效果(hqhuang1在《[HQ]角點檢測(Corner Detection) cvFindCornerSubPix 使用範例 》中給出了相關的應用範例)。因此,影響定標結果較大的就是標定板與鏡頭的夾角和棋盤影象數目,在實際定標過程中,我感覺棋盤影象數目應該大於20張,每成功檢測一次完整的棋盤角點就要變換一下標定板的姿態(包括角度、距離) 

 

5. 單目定標函式cvCalibrateCamera2採用怎樣的 flags 比較合適?

由於一般鏡頭只需要計算k1,k2,p1,p2四個引數,所以我們首先要設定 CV_CALIB_FIX_K3;其次,如果所用的攝像頭不是高階的、切向畸變係數非常少的,則不要設定 CV_CALIB_ZERO_TANGENT_DIST,否則單目校正誤差會很大;如果事先知道攝像頭內參的大概數值,並且cvCalibrateCamera2函式的第五個引數intrinsic_matrix非空,則也可設定 CV_CALIB_USE_INTRINSIC_GUESS ,以輸入的intrinsic_matrix為初始估計值來加快內參的計算;其它的 flag 一般都不需要設定,對單目定標的影響不大。

 

P.S. 使用OpenCV進行攝像機定標雖然方便,但是定標結果往往不夠準確和穩定,最好是使用 Matlab標定工具箱 來進行定標,再將定標結果取回來用於立體匹配和視差計算。工具箱的使用官方主頁 有圖文並茂的詳細說明,此外,有兩篇博文也進行了不錯的總結,推薦閱讀:

(1)分享一些OpenCV實現立體視覺的經驗

(2)Matlab標定工具箱使用的一些注意事項


相關文章