使用opencv做雙目測距(相機標定+立體匹配+測距)

轉載自:     http://www.cnblogs.com/daihengchen/p/5492729.html  


     最近在做雙目測距,覺得有必要記錄點東西,所以我的第一篇博客就這麼誕生啦~

     雙目測距屬於立體視覺這一塊,我覺得應該有很多人踩過這個坑了,但網上的資料依舊是雲裏霧裏的,要麼是理論講一大堆,最後發現還不知道怎麼做,要麼就是直接代碼一貼,讓你懵逼。 所以今天我想做的,是儘量給大家一個明確的闡述,並且能夠上手做出來。

一、 標定

    首先我們要對攝像頭做標定,具體的公式推導在learning opencv中有詳細的解釋,這裏順帶提一句,這本書雖然確實老,但有些理論、算法類的東西里面還是講的很不錯的,必要的時候可以去看看。

   Q1:爲什麼要做攝像頭標定?

      A:  標定的目的是爲了消除畸變以及得到內外參數矩陣,內參數矩陣可以理解爲焦距相關,它是一個從平面到像素的轉換,焦距不變它就不變,所以確定以後就可以重複使用,而外參數矩陣反映的是攝像機座標系與世界座標系的轉換,至於畸變參數,一般也包含在內參數矩陣中。從作用上來看,內參數矩陣是爲了得到鏡頭的信息,並消除畸變,使得到的圖像更爲準確,外參數矩陣是爲了得到相機相對於世界座標的聯繫,是爲了最終的測距。

     ps1:關於畸變,大家可以看到自己攝像頭的拍攝的畫面,在看矩形物體的時候,邊角處會有明顯的畸變現象,而矯正的目的就是修復這個。

     ps2:我們知道雙目測距的時候兩個相機需要平行放置,但事實上這個是很難做到的,所以就需要立體校正得到兩個相機之間的旋轉平移矩陣,也就是外參數矩陣。  

                

   Q2:如何做攝像頭的標定?

      A:這裏可以直接用opencv裏面的sample,在opencv/sources/sample/cpp裏面,有個calibration.cpp的文件,這是單目的標定,是可以直接編譯使用的,這裏要注意幾點:

               1.棋盤

       棋盤也就是標定板是要預先打印好的,你打印的棋盤的樣式決定了後面參數的填寫,具體要求也不是很嚴謹,清晰能用就行。之所用棋盤是因爲他檢測角點很方便,and..你沒得選。。

               2. 參數

       一般設置爲這個樣子:-w 6 -h 8 -s 2 -n 10 -o camera.yml -op -oe [<list_of_views.txt>]  ,這是幾個重要參數的含義:

                   -w <board_width>         # 圖片某一維方向上的交點個數
                   -h <board_height>        # 圖片另一維上的交點個數
                   [-n <number_of_frames>]  # 標定用的圖片幀數
                   [-s <square_size>]       # square size in some user-defined units (1 by default)
                   [-o <out_camera_params>] # the output filename for intrinsic [and extrinsic] parameters
                   [-op]                    # write detected feature points
                   [-oe]                    # write extrinsic parameters



可以發現 -w -h是棋盤的長和高,也就是有幾個黑白交點,-s是每個格子的長度,單位是cm 
長和高一定要數對,不然程序在識別角點的時候會識別不出來的。

                      最終得到的yml文件,就是單目標定的參數矩陣,之後使用它就可以得到校正後的圖像啦。                                                            

           3. 需要對程序做一些修改,這是我遇到的問題,就是他的讀取攝像頭的代碼在我這邊沒有用,所以我自己重新修改了,不知道大家會  不會碰到這個問題。

             

               然後就是雙目標定了,同樣的地方,找到stereo_calib.cpp,這個參數比較簡單,只要確定長、寬和輸入的一個xml文件(在之前          的文件夾裏面),這個文件是爲了讀取圖片用的,你需要自己用固定好的雙目攝像頭拍14對棋盤圖片,命名爲 left01,right01......這樣          一系列的名字,另外,最簡單的方法就是把自己拍的照片放到相應的工程下,以及stereo開頭的那個xml文件也複製過去這個程序代碼            並不複雜,可以稍微研究一下,工程向的代碼確實嚴謹,各種情況都考慮到了,比起自己之前做的那個小項目不知道高到哪裏去了

        這裏也有幾個注意點(坑):

              1.老生常談的問題,長寬一定要寫對!!! 這個不多說了,都是淚。

 

 2.代碼的核心函數 static void  StereoCalib(const vector<string>& imagelist, Size boardSize, bool useCalibrated=true, bool  showRectified=true),注意搞清楚參數的意義,因爲我是用的單目標定好的攝像頭拍攝的圖片,不需要再校正了,所以第三個參數要用false,這樣最後的結果才能看,不說了,都是淚...

 

              3.另外注意到計算rms誤差的時候,結束條件的幾個參數是可以調整的,

double rms = stereoCalibrate(objectPoints, imagePoints[0], imagePoints[1],
cameraMatrix[0], distCoeffs[0],
cameraMatrix[1], distCoeffs[1],
imageSize, R, T, E, F,
TermCriteria(CV_TERMCRIT_ITER+CV_TERMCRIT_EPS, 100, 1e-5),
CV_CALIB_FIX_ASPECT_RATIO +CV_CALIB_ZERO_TANGENT_DIST +CV_CALIB_SAME_FOCAL_LENGTH +CV_CALIB_RATIONAL_MODEL +CV_CALIB_FIX_K3 + CV_CALIB_FIX_K4 + CV_CALIB_FIX_K5)

下面這段話是某度百科上的:

        這個函數計算了兩個攝像頭進行立體像對之間的轉換關係。如果你有一個立體相機的相對位置,並且兩個攝像頭的方向是固定的,以及你計算了物體相對於第一照相機和第二照相機的姿態,(R1,T1)和(R2,T2),各自(這個可以通過solvepnp()做到)通過這些姿態確定。你只需要知道第二相機相對於第一相機的位置和方向。
        除了立體的相關信息,該函數也可以兩個相機的每一個做一個完整的校準。然而,由於在輸入數據中的高維的參數空間和噪聲的,可能偏離正確值。如果每個單獨的相機內參數可以被精確估計(例如,使用calibratecamera()),建議這樣做,然後在本徵參數計算之中使CV_CALIB_FIX_INTRINSIC的功能。否則,如果一旦計算出所有的參數,它將會合理的限制某些參數,例如,傳CV_CALIB_SAME_FOCAL_LENGTH and CV_CALIB_ZERO_TANGENT_DIST,這通常是一個合理的假設。
        
      Q3:標定之後做什麼呢?
       
      A: 寫到這我發現把單目和雙目的一起寫確實有點亂...不過,開弓沒有回頭箭!(不是因爲懶!!)
          首先還是單目,單目的使用很簡單,使用標定得到的參數進行校正就行了,代碼如下:

void loadCameraParams(Mat &cameraMatrix, Mat &distCoeffs)
{
FileStorage fs("camera.yml", FileStorage::READ);//這個名字就是你之前校正得到的yml文件

fs["camera_matrix"] >> cameraMatrix;
fs["distortion_coefficients"] >> distCoeffs;
}

Mat calibrator(Mat &view)//需要校正處理的圖片
{
vector<string> imageList;
static bool bLoadCameraParams = false;
static Mat cameraMatrix, distCoeffs, map1, map2;
Mat rview;
Size imageSize, newImageSize;

if (!view.data)
return Mat();

imageSize.width = view.cols;
imageSize.height = view.rows;

newImageSize.width = imageSize.width;
newImageSize.height = imageSize.height;

if (bLoadCameraParams == false)
{
loadCameraParams(cameraMatrix, distCoeffs);
bLoadCameraParams = true;
initUndistortRectifyMap(cameraMatrix, distCoeffs, Mat(),
getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1, newImageSize, 0), newImageSize, CV_16SC2, map1, map2);
}

//undistort( view, rview, cameraMatrix, distCoeffs, cameraMatrix );
remap(view, rview, map1, map2, INTER_LINEAR);

imshow("左圖", rview);
//int c = 0xff & waitKey();

rview.copyTo(view);

return view;
}

                     這樣最後就可以得到校正後消除畸變的圖片。

                     OK,接下來顯然就是雙目啦,雙目校正之後的工作就比較多了,我準備另開一節來說...                                  

 二、立體匹配

    這是一個很大的題目,網上的資料也很多,所以我想說的是我的一些理解。

       這裏最好的方法是從後往前說,我們首先需要理解測距的原理。這個很多人看了一大堆還不明白(其實只有我自己吧..),相似三角形測距,這種東西小學生都能搞清楚,但兩攝像頭到底怎麼做到的,就是我們需要搞清楚的。

      首先需要搞清楚一個非常重要的概念,視差,搞清楚視差,後面的就簡單了 ,老生常談的問題我不想多說,網上那些一大堆,我希望給大家的是一些明瞭的東西

                                                                                       

                                                                                        

                                                                                            

       這三幅圖看明白了就行,其實視差確實很簡單,但很多人都沒去理清楚,第一幅圖是三維世界的一個點在兩個相機的成像,我們可以相信的是,這兩個在各自相機的相對位置基本不可能是一樣的,而這種位置的差別,也正是我們眼睛區別3D和2D的關鍵,將右邊的攝像機投影到左邊,怎麼做呢?因爲他的座標和左邊相機的左邊相距Tx(標定測出來的外參數),所以它相當於在左邊的相機對三維世界內的(x-tx,y,z)進行投影,所以這時候,一個完美的形似三角形就出來,這裏視差就是d=x-x‘,

                  

              得到視差以後,再用相似三角形......也就得到了深度也就是距離啦。

       結束了麼??並沒有....這樣做確實很完美,但是問題來了:1.當我在左邊相機確定一個點的時候,我怎麼在右邊找到這個點? 2.我左邊點所在的行一點跟右邊點所在的行上的像素一定完全一樣麼?

             解決第一個問題的方法就是立體匹配了。

          Q1:立體匹配是什麼,怎麼進行立體匹配?

          A:簡單的回答就是:立體匹配就是解決上面問題的東西啦....其實我覺得這樣就是也夠了,有些成熟的算法,未必需要鑽研太深,畢竟我這種實在的菜雞,還是工程導向的..學術的事,日後再說! 

              opencv中提供了很多的立體匹配算法,類似於局部的BM,全局的SGBM等等,這些算法的比較大概是,速度越快的效果越差,如果不是很追究時效性,並且你的校正做的不是很好的話..推薦使用SGBM,算法的具體原理大家可以去百度,不難。這裏我想提一下的是爲什麼做立體匹配有用,原因就是極線約束,這也是個很重要的概念,理解起來並不難,左攝像機上的一個點,對應三維空間上的一個點,當我們要找這個點在右邊的投影點時,有必要把這個圖像都遍歷一邊麼,當然不用...

                                                               

             如上圖,顯然,PL對應的P這個點一定在一條極線上,只要在這條線上找就行了,更明顯的是下面這個圖:

                                                         

      最後,怎麼在opencv裏面實現呢..機智的我又找到了sample..找到stereo_match.cpp這個文件,命令行設置爲:left01.jpg right02.jpg --algorithm=hh --blocksize=5 --max-disparity=256  --scale=1.0 --no-display -i intrinsics.yml -e extrinsics.yml -o disparity.jpg同意給幾個建議:

              1.參數的意義:

-max-disparity 是最大視差,可以理解爲對比度,越大整個視差的range也就越大,這個要求是16的倍數

 --blocksize 一般設置爲5-29之間的奇數,應該是局部算法的窗口大小。

另,注意帶上參數-i intrinsics.yml -e extrinsics.yml,畢竟咱有校正參數...

              2.後面有兩行代碼:

                                   reprojectImageTo3D(disp, xyz, Q, true);

                                   saveXYZ(point_cloud_filename, xyz);

                      這個就是得到圖片的三維座標,Z也就是我們最終要求的深度啦。

           

       第二個問題,行和行是對應的麼?   之前我們說過,雙目校正的目的就是爲了得到兩個平行的攝像頭,所以當程序運行完畢以後,它會把兩幅圖像顯示出來,並作出一系列的平行線,這樣你會看到線上的點大致是呈對應關係,左邊的角點對應右邊的交點,所以,經過匹配和校正後,是對應的。

三、總結

   雙目拖了很久,一直沒做,最重要的原因就是...我沒有兩個一樣的攝像頭,所以最後也沒有貼出效果圖,因爲兩個不一樣的攝像頭,做出來的東西畫面太美我不敢看,不過最終搞清楚了整個流程和原理,還是比較開心的。這裏面像校正和匹配的算法,我只是有所理解,因爲以後不一定走3D這一塊,所以也沒有過去深入,如果用到在去研究,其實也不晚..總之,第一篇博客,完工啦~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章