http://blog.csdn.net/chenyusiyuan/article/details/5961769
雙目測距的基本原理
如上圖所示,雙目測距主要是利用了目標點在左右兩幅視圖上成像的橫向坐標直接存在的差異(即視差)與目標點到成像平面的距離Z存在著反比例的關系:Z=fT/d。「@scyscyao :在OpenCV中,f的量綱是像素點,T的量綱由定標板棋盤格的實際尺寸和用戶輸入值確定,一般是以毫米為單位(當然為了精度提高也可以設置為0.1毫米量級),d=xl-xr的量綱也是像素點。因此分子分母約去,Z的量綱與T相同。 」
假設目標點在左視圖中的坐標為(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 同時讀取兩個攝像頭,不過後來一位研友來信討論說只要把攝像頭指針的創建代碼按照攝像頭序號降序執行,就可以順利打開多個攝像頭 ,例如:
- CvCapture* capture2 = cvCreateCameraCapture( 1 );
- CvCapture* capture1 = cvCreateCameraCapture( 0 );
采用DirectShow的方式讀入時:
- camera2.OpenCamera(1, false, 640,480);
- 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把幀數據取出來。例如:
- cvGrabFrame( lfCam );
- cvGrabFrame( riCam );
- frame1 = cvRetrieveFrame( lfCam );
- frame2 = cvRetrieveFrame( riCam );
- if( !frame1|| !frame2) break;
- cvCopyImage(frame1, image1);
- cvCopyImage(frame2, image2);
二、攝像頭定標
攝像頭定標一般都需要一個放在攝像頭前的特制的標定參照物(棋盤紙),攝像頭獲取該物體的圖像,並由此計算攝像頭的內外參數。標定參照物上的每一個特征點相對於世界坐標系的位置在制作時應精確測定,世界坐標系可選為參照物的物體坐標系。在得到這些已知點在圖像上的投影位置後,可計算出攝像頭的內外參數。
如上圖所示,攝像頭由於光學透鏡的特性使得成像存在著徑向畸變,可由三個參數k1,k2,k3確定;由於裝配方面的誤差,傳感器與光學鏡頭之間並非完全平行,因此成像存在切向畸變,可由兩個參數p1,p2確定。單個攝像頭的定標主要是計算出攝像頭的內參(焦距f和成像原點cx,cy、五個畸變參數(一般只需要計算出k1,k2,p1,p2,對於魚眼鏡頭等徑向畸變特別大的才需要計算k3))以及外參(標定物的世界坐標)。 OpenCV 中使用的求解焦距和成像原點的算法是基於張正友的方法( pdf ),而求解畸變參數是基於 Brown 的方法( pdf )。
1. 圖像坐標系、攝像頭坐標系和世界坐標系的關系
攝像頭成像幾何關系,其中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標定工具箱來進行定標,再將定標結果取回來用於立體匹配和視差計算。工具箱的使用官方主頁 有圖文並茂的詳細說明,此外,有兩篇博文也進行了不錯的總結,推薦閱讀: