生活随笔
收集整理的這篇文章主要介紹了
摄像机高精度标定的一些方法
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
攝像機標定從標定板類型可以分為:一維標定物,二維標定物和三維標定物(哈哈哈)
1 一維標定物
一維標定物標定算法是利用攝像機投影過程中的交比不變性的原理,常用來標定攝像機陣列(多個攝像機)。具體原理可參考:基于一維標定物的多攝像機標定。一維標定物見下圖所示,一個直桿上有3個點,三個點之間的距離需要實現精確測量。在標定過程中,只需要在各個攝像機的視場空間不斷揮動標定桿,就可以精確標定攝像機的內外參數。一維標定物在多攝像機標定中使用起來非常靈活方便,非常建議在該種場景下使用。 (在這里特別感謝一下吳福朝,張廣軍等人在早期對傳統的計算機視覺(也可以叫幾何視覺吧)的貢獻,我是從計算機視覺中的數學方法(吳福朝),機器視覺(張廣軍),計算機視覺中的多視圖幾何(韋穗翻譯)這三本書入門機器視覺的。)
一維標定物,圖片來自“基于一維標定物的多攝像機標定”
2 三維標定物
三維標定物主要利用已知的三維標志點坐標和圖像點標定攝像機內外參數,原理類似與二維標定物的標定方法。三維標定物制作起來成本比較高,在張正友標定法之前應用的比較廣泛,現在基本上已經不在使用了。
3 二維標定物
二維標定物是現在主流的攝像機標定方法,主要優點是,標定板成本較低,標定操作簡單方便,標定精度高(好處多多)。二維標定物是從張正友標定法出來之后流行起來的。大部分的標定算法都是two stage:1是解方程,就是通過幾何約束求解未知參數;2,迭代優化,基于1中求解的參數,建立目標函數,通過迭代優化的方式增加參數的精度。只有經過這兩部得到的標定參數才是最優。張正友標定法也是按照這兩步進行,下面也會講解相機和激光間的標定也是這樣進行的。當然,在一些高精度三維測量中(三維結構光掃描儀等),還會加入其它的優化手段。
3.1 標定方法
二維標定物的攝像機標定方法基本上都是用張正友標定法,詳細原理現在網上已經有太多了這里不展開敘述,簡單來說可以分成以下步驟:
提取標定圖像上的標志點,聯合已知的標定板三維點坐標,通過DLT方法,求解單應矩陣H。 單應矩陣中,利用旋轉矩陣的正交性,增加約束方程,求解相機的內部參數A(相機的焦距和中心點坐標) 通過已知的內部參數和H矩陣,求解外部參數(R,t)。需要注意的是這里的外部參數是指相機坐標系和標定板坐標系之間的旋轉平移變換 建立相機畸變模型(radtan) 建立目標函數,優化標定參數(A,R,t,畸變參數),目標函數是反投影殘差參數。這一步也就是paper中所說的最大似然估計。 如果是雙目標定的情況,在兩個相機分別進行上述標定后,求解兩個相機之間的外部參數(R,t)。 建立目標函數,優化內外參。
需要注意的是:
針對雙目攝像機標定,雖然opencv里面直接有雙目標定函數,但是通常意義下,最好先使用單目標定分別標定左右相機,然后使用雙目標定函數標定外參。 畸變參數問題,針對小視角相機,畸變比較小,可以先忽略畸變參數求解相機內參,然后通過優化方式求解畸變參數。但是對于魚眼相機,畸變比較大,是不是不能這么做。需要先估計畸變參數,或者是畸變參數和相機內參一起估計。例如opencv中的equidistant模型標定。這一塊需要再繼續研究一下。
3.2 標定板類型
事先說明,在高精度相機標定中,標定板的選擇至關重要,標定板的精度也非常的高,在高精度三維重建和測量領域,使用的相機和鏡頭都是工業級別的。但是針對VSLAM行業,使用的相機都是相機模組,標定精度要求不是那么高,使用opencv,kalibr等標定代碼已經可以完全滿足標定需求。 目前二維標定板可以分為兩種類型:角點標定板和圓形標定板。如下圖所示,其中角點標定板有我們常用的棋盤格(chessboard)標定板(下圖左上),kalibr標定所使用二維標識碼(Apriltag)標定板(下圖右上);圓形標定板可以自己設計(如下圖左下,歡迎拍磚),還可以使用halcon中自帶的標定板(下圖右下)。值得一提的是,halcon工具在相機標定領域已經遙遙領先。現在很多做三維測量的公司都在使用這個機器視覺庫。 在特征點提取精度方面圓形標定的提取精度高于棋盤格類型的平面標定板。這也是為什么在高精度三維測量,三維重建領域,常常使用圓形標定板的原因。這里值得注意的是,由于相機幾何投影的原因,標定板投影到圖像上,圓形會變成橢圓。因此我們在圖像上是需要提取橢圓的圓心的。因此提取橢圓圓心大致分為一下過程:
提取圓形輪廓,將每個橢圓劃分開。可以利用opencv輪廓提取函數完成 利用邊緣檢測算子提取橢圓的邊緣 亞像素邊緣提取(二次曲線擬合法,灰度矩法等) 橢圓擬合(hough變換,最小二乘法),得到橢圓圓心 指的注意的是,亞像素邊緣提取對圓心提取的精度至關重要。但由于圖像噪聲的關系,邊緣提取方法,橢圓擬合方法需要斟酌使用。
在上圖的圓形標定中,我們在中間設置了不對稱(既不是軸對稱,也不是中心對稱)的五個大圓,有兩個目的:一是無論我們怎么擺設標定板,都不會改變圓的排序序號,即在相機標定時,在標定板上建立世界坐標系是唯一的,這在一定程度會提升相機標定的精度。二是在標定時,由于相機視角原因,即使沒有將標定板拍攝完全,也可以根據中間五個大圓,將圖像上的圓正確排序。 在使用圓形標定板的時候,還需要特別注意一個問題,就是所謂的偏心差(也叫透視偏差),英文名:perspective bias,即標定板上的圓心投影到圖像上,投影點并不是圖像上的橢圓圓心(是不是有很多問號???)。如下圖所示,有一圓半徑為r,圓心C,投影到圖像平面IP上,圓心的真正投影點應該為C’,然而由于透視偏差的存在,我們通過圖像橢圓擬合得到的圓心為B’,投影偏差就是B’C’。需要注意,透視偏差是視覺幾何客觀存在的差異,并不是圖像提取等噪聲產生的誤差,因此這個偏差叫做bias,而不是error。是不是有點類似與IMU bias。如何解決該bias,2000年左右就已經有人把該個圓形透視偏差模型用幾何公式的方式完全建模出來, 我們直接可以使用[1]。關于這個偏差這里列舉一些文獻,有興趣的同學可以詳細研究一下 [1] [2][3][4][5]。
偏心差示意圖,圖片來自“圓形標志投影偏心差補償算法[2]”
3.3 提升標定精度
這里再次強調,如果是在SLAM等環境下標定相機,我們常用的opencv,matlab標定方法就已經可以滿足標定精度的需求。因為SLAM的定位精度是厘米級別(有做的更好精度的,歡迎拍磚)的。但是對于高精度的三維重建,重建精度達到0.02mm,甚至于微米級別的,那么提升標定精度就是必須的。在高精度三維重建領域,例如三維掃描儀,主要是主動打結構光的方式,通過解碼(通常是解相位),利用三角測量原理重建三維模型的。因此相機的標定參數和圖像的匹配精度是影響三維重建的主要因素。這里主要是對如何提升相機標定參數,談一些自己的想法。 除了上面所提的使用圓形標定板,并且需要進行圓心透視偏差的補償外,還有一些其它方法:
提高標定板的精度,我們所使用的標定板精度是否真正精確,這需要第三方高精度測量設備測試。這里有兩個方面需要注意,一是標定板上圓與圓之間的距離是否準確;二是我們所使用的標定的平面性如何,一般我們使用的標定板有鋁制的,陶瓷的,玻璃的。隨著加工工藝的提升,標定板的精度越來越好,但是標定板的幾何參數我們需要進一步精確。一般來講,標定板的精度比我們的三維重建精度要高一個數量級。因此,在三維測量領域,高精度標定板的成本是很高的。 算法優化。在進行單目和雙目標定后,我們需要再加一部迭代優化方法,目標函數仍然是反投影殘差,優化變量依舊是相機的內外參數,但是這里我們使用的標定板3D點坐標是更精確的坐標(因為張氏標定法的原因,標定板是平面的,但是由于制作誤差,其實并不是平的),這里我們在優化的時候可以使用真實的3D點,并且加入透視偏差模型,得到更加精確的參數。這方面有需求的話,后面專門寫個博客。 在標定的過程中,擺放標定板的位置也至關重要。一般來說,對于小視角相機,擺放7-10步就可以精確標定出相機的內參,分別是:標準正方,向下傾斜,向上傾斜,向左傾斜,向右傾斜, 遠離攝像頭,靠近攝像頭.,如果視野大,在遠離攝像頭的位置上也擺設幾步標定板。這也是有人做過大量實驗驗證過的。當然針對魚眼相機等大視角相機,需要擺放更多的位置。其實在我們使用kalibr標定工具標定時,很多圖像是冗余的,不知道kalibr里面有沒有挑選圖像的步驟。 圓形標定板,盡量不要使用大圓。比如上圖的第二排第一個標定板,為了標定板圓心排序設計了5個大圓,但在實際應用中會把5個大圓剔除掉。以前自己做實驗的時候,發現大圓的圓心提取會拉低整個標定精度。所以halcon設計的標定板挺好的。大圓圓心拉低標定精度的原因,可能是因為我以前沒有做橢圓偏心矯正吧。這一條寫在這里,有待相關同學進一步驗證。
3.4 相機標定評價標準
一般,評價攝像機標定精度是使用反投影殘差的。在高精度相機標定中,反投影殘差可以達到0.02pixel左右的精度。在VSLAM領域,相機標定反投影殘差精度在0.5pixel就可以了,甚至對于fisheye相機,1-2pixel也可以接受。當然對fisheye相機,用到了其它的相機模型(Omni等),這個在后面專門出個博客,介紹相機模型介紹。 但是使用反投影殘差作為相機標定評判標準并不是很嚴格。我們上面介紹的,在相機標定過程中,需要經過很多次的迭代優化,迭代優化的目標函數都是使反投影殘差最小。在優化的過程中也許會陷入局部最小,或者由于這次一些其他原因(光線原因,擺放標定板原因等),噪聲比較大,標定參數也許不是最優,但是反投影殘差也可能比較小。因此我們需要尋求更好的評判標準。 針對雙目相機。雙目相機最大優勢是可以三角化重建標志點。因此在標定結束后,可以將標定板放置在攝像頭前,對標定板的標志點進行三角化,得到三維坐標。然后求解標定板上圓心之間的距離,和真實數據對比距離差。代碼如下:
vector
< float > v3D_error
; vector
< double > disparity_aver
; std
:: cout
<< "******** stereo calibration error ********" << std
:: endl
; for ( int index
= calib_imgs_num
; index
< calib_imgs_num
+ evalu_imgs_num
; ++ index
) { char left_img
[ 100 ] , right_img
[ 100 ] ; sprintf ( left_img
, "%s%s%d.%s" , img_dir
. c_str ( ) , leftimg_filename
. c_str ( ) , index
, images_format
. c_str ( ) ) ; sprintf ( right_img
, "%s%s%d.%s" , img_dir
. c_str ( ) , rightimg_filename
. c_str ( ) , index
, images_format
. c_str ( ) ) ; cv
:: Mat img1
= imread ( left_img
, CV_LOAD_IMAGE_COLOR
) ; cv
:: Mat img2
= imread ( right_img
, CV_LOAD_IMAGE_COLOR
) ; if ( img1
. empty ( ) || img2
. empty ( ) ) { std
:: cout
<< index
<< ": NAN" << std
:: endl
; continue ; } cv
:: Mat recImg1
, recImg2
, gray1
, gray2
; remap ( img1
, recImg1
, rmap
[ 0 ] [ 0 ] , rmap
[ 0 ] [ 1 ] , CV_INTER_LINEAR
) ; remap ( img2
, recImg2
, rmap
[ 1 ] [ 0 ] , rmap
[ 1 ] [ 1 ] , CV_INTER_LINEAR
) ; cv
:: cvtColor ( recImg1
, gray1
, CV_BGR2GRAY
) ; cv
:: cvtColor ( recImg2
, gray2
, CV_BGR2GRAY
) ; bool found1
= false , found2
= false ; Size board_size
= Size ( board_width
, board_height
) ; vector
< Point2f
> corners1
, corners2
; vector
< cv
:: Point3f
> v3DPoints
; found1
= cv
:: findChessboardCorners ( recImg1
, board_size
, corners1
, CV_CALIB_CB_ADAPTIVE_THRESH
| CV_CALIB_CB_FILTER_QUADS
) ; found2
= cv
:: findChessboardCorners ( recImg2
, board_size
, corners2
, CV_CALIB_CB_ADAPTIVE_THRESH
| CV_CALIB_CB_FILTER_QUADS
) ; if ( found1
&& found2
) { cv
:: cornerSubPix ( gray1
, corners1
, cv
:: Size ( 5 , 5 ) , cv
:: Size ( - 1 , - 1 ) , cv
:: TermCriteria ( CV_TERMCRIT_EPS
| CV_TERMCRIT_ITER
, 30 , 0.1 ) ) ; cv
:: drawChessboardCorners ( gray1
, board_size
, corners1
, found1
) ; cv
:: cornerSubPix ( gray2
, corners2
, cv
:: Size ( 5 , 5 ) , cv
:: Size ( - 1 , - 1 ) , cv
:: TermCriteria ( CV_TERMCRIT_EPS
| CV_TERMCRIT_ITER
, 30 , 0.1 ) ) ; cv
:: drawChessboardCorners ( gray2
, board_size
, corners2
, found2
) ; } else { std
:: cout
<< index
<< ": NAN" << std
:: endl
; continue ; } double disparity_error
= 0.0 ; for ( int i
= 0 ; i
< board_width
* board_height
; ++ i
) { double disparity_tmp
= abs ( corners1
[ i
] . y
- corners2
[ i
] . y
) ; disparity_error
+= disparity_tmp
; } disparity_error
= disparity_error
/ ( board_width
* board_height
) ; disparity_aver
. push_back ( disparity_error
) ; double f
= P1
. at < double > ( 0 , 0 ) ; double tx
= T
. at < double > ( 0 , 0 ) ; double ty
= T
. at < double > ( 1 , 0 ) ; double tz
= T
. at < double > ( 2 , 0 ) ; for ( int i
= 0 ; i
< board_width
* board_height
; ++ i
) { double y_1
= corners1
[ i
] . x
- P1
. at < double > ( 0 , 2 ) ; double x_1
= corners1
[ i
] . y
- P1
. at < double > ( 1 , 2 ) ; double y_2
= corners2
[ i
] . x
- P2
. at < double > ( 0 , 2 ) ; double x_2
= corners2
[ i
] . y
- P2
. at < double > ( 1 , 2 ) ; double w_z
= ( f
* tx
- x_2
* tz
) / ( x_2
- x_1
) ; double w_x
= w_z
* x_1
/ f
; double w_y
= w_z
* y_1
/ f
; v3DPoints
. push_back ( cv
:: Point3f ( w_x
, w_y
, w_z
) ) ; } float dis_error
= 0.0 ; int err_num
= 0 ; for ( int i
= 0 ; i
< board_height
; ++ i
) for ( int j
= 0 ; j
< board_width
- 1 ; ++ j
) { float err_t
= abs ( sqrt ( ( v3DPoints
[ i
* board_width
+ j
] . x
- v3DPoints
[ i
* board_width
+ j
+ 1 ] . x
) * ( v3DPoints
[ i
* board_width
+ j
] . x
- v3DPoints
[ i
* board_width
+ j
+ 1 ] . x
) + ( v3DPoints
[ i
* board_width
+ j
] . y
- v3DPoints
[ i
* board_width
+ j
+ 1 ] . y
) * ( v3DPoints
[ i
* board_width
+ j
] . y
- v3DPoints
[ i
* board_width
+ j
+ 1 ] . y
) + ( v3DPoints
[ i
* board_width
+ j
] . z
- v3DPoints
[ i
* board_width
+ j
+ 1 ] . z
) * ( v3DPoints
[ i
* board_width
+ j
] . z
- v3DPoints
[ i
* board_width
+ j
+ 1 ] . z
) ) - square_size
) ; dis_error
+= err_t
; err_num
++ ; std
:: cout
<< "error " << i
* board_width
+ j
<< ": " << err_t
<< std
:: endl
; } for ( int i
= 0 ; i
< board_width
; ++ i
) { for ( int j
= 0 ; j
< board_height
- 1 ; ++ j
) { float err_t
= abs ( sqrt ( ( v3DPoints
[ j
* board_width
+ i
] . x
- v3DPoints
[ ( j
+ 1 ) * board_width
+ i
] . x
) * ( v3DPoints
[ j
* board_width
+ i
] . x
- v3DPoints
[ ( j
+ 1 ) * board_width
+ i
] . x
) + ( v3DPoints
[ j
* board_width
+ i
] . y
- v3DPoints
[ ( j
+ 1 ) * board_width
+ i
] . y
) * ( v3DPoints
[ j
* board_width
+ i
] . y
- v3DPoints
[ ( j
+ 1 ) * board_width
+ i
] . y
) + ( v3DPoints
[ j
* board_width
+ i
] . z
- v3DPoints
[ ( j
+ 1 ) * board_width
+ i
] . z
) * ( v3DPoints
[ j
* board_width
+ i
] . z
- v3DPoints
[ ( j
+ 1 ) * board_width
+ i
] . z
) ) - square_size
) ; dis_error
+= err_t
; err_num
++ ; } } v3D_error
. push_back ( dis_error
/ err_num
) ; std
:: cout
<< "image." << index
<< "--stereo disparity error: " << disparity_error
<< " pixel" << "---" << "3D reconstruction erro: " << dis_error
/ err_num
<< " mm" << std
:: endl
;
針對單目相機,使用反投影殘差應該也許是評價相機標定很好的方法了。
以上主要是針對高精度標定怎么去做,相機標定原理核心是張正友標定法,但是需要做一些改進和優化。這也是筆者以前的工作,現在主要做VSLAM方面的事情,對這方面沒有太大的要求,但相機標定精度當然是越高越好。并且相機標定是一個最好的幾何視覺入門課程。
這里有個自己寫的針對單目相機,單目魚眼相機,雙目相機,雙目魚眼相機的opencv標定代碼,魚眼相機使用的是equidistant畸變模型。代碼是基于別人上修改的,只加入了雙目重建誤差評價方法。至于高精度相機標定方面的代碼,由于方方面面的原因,沒法公開。現在一些基于雙目視覺的高精度重建設備已經可以達到微米級別,大多出自國外,這里我輩還需努力啊。 opencv標定代碼地址:https://github.com/GaoJunqiang/camera_calibration
后面再出個博客專門介紹相機和線激光的標定(線結構光高精度三維重建領域,也是以前在研究生時的一個課題),相機和單線激光雷達標定(多傳感器融合定位)。感覺這兩個之間有很多共同之處。
參考
[1] Ahn S J, Warnecke H J, Kotowski R. Systematic geometric image measurement errors of circular object targets: Mathematical formulation and correction[J]. The Photogrammetric Record, 1999, 16(93): 485-502. [2] 吳建霖, 蔣理興, 王安成,等. 圓形標志投影偏心差補償算法[J]. 中國圖象圖形學報, 2018, 023(010):1549-1557. [3] 黃道明. 近景攝影測量中圓形目標的投影偏心差及模擬分析[J]. 工程建設, 2011(02):14-17. [4] 廖祥春, 馮文灝. 圓形標志及其橢圓構像間中心偏差的確定[J]. 武漢大學學報:信息科學版, 1999, 024(003):235-239. [5] Heikkila J, Silven O. A four-step camera calibration procedure with implicit image correction[C]//Proceedings of IEEE computer society conference on computer vision and pattern recognition. IEEE, 1997: 1106-1112.
總結
以上是生活随笔 為你收集整理的摄像机高精度标定的一些方法 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。