單目相機標定-原理及實現
本文轉載自部落格:http://blog.csdn.net/before1993/article/details/51210595
一、 標定原理
相機標定的目的就是要獲得相機的內參數,得到二維平面畫素座標和三維世界座標的關係,從而進行三維重建。
1、幾個座標系及其變換
(1)影象座標系:是一個以畫素為單位的座標系,它的原點在左上方,每個畫素點的位置是以畫素為單位來表示的,所以這樣的座標系叫影象畫素座標系(u,v),u和v分別表示畫素在數字影象中的列數和行數,但是並沒有用物理單位表示畫素的位置,因此還需建立以物理單位表示的影象座標系,叫影象物理座標系(x,y),該座標系是以光軸與影象平面的交點為原點,該點一般位於影象中心,但是由於製造原因,很多情況下會偏移。以毫米為單位。兩個座標軸分別與影象畫素座標系平行。
若影象物理座標系的原點在影象畫素座標系中的座標為,每個畫素在影象物理座標系中的尺寸為dx,dy,則兩個座標系的關係為:
化為齊次座標和矩陣形式:
(2)相機座標系:是以相機的光心為原點Oc,Zc軸與光軸重合,與成像平面垂直,Xc和Yc軸與影象座標系的兩個座標軸平行。OOc為攝像機的焦距,即相機座標系的原點與影象物理座標系原點之間的距離。
(3)世界座標系:是由使用者定義的三維座標系,用於描述三維空間中的物體和相機的位置。用表示XwYwZw。
三者可用如下圖表示:
(1)從世界座標系到相機座標系的變換:可以由一個旋轉矩陣R和一個平移向量t來描述:
化為齊次座標:
(2)相機座標系到影象物理座標系:
其中f是焦距。化成齊次座標為:
所以可以得到從世界座標系到影象座標系的轉換:
其中是相機的內參數,是外引數。
2、透鏡畸變
實際的攝像機由於鏡頭製作工藝等原因,使攝像機獲取的原始影象存在畸變,畸變有兩種,徑向畸變和切向畸變。徑向畸變來自透鏡形狀,切向畸變來自於整個攝像機的組裝過程。徑向畸變可以由三個引數k1,k2,k3確定,切向畸變可由兩個引數p1,p2確定。
3、總結
相機標定的目的就是建立攝像機影象畫素位置與場景點位置之間的關係,即世界座標系與影象座標系之間的關係。方法就是根據攝像機模型,由已知特徵點的影象座標求解攝像機的模型引數,從而可以從影象出發恢復出空間點三維座標,即三維重建。所以要求解的引數包括4個內參數和5個畸變引數,對於外引數,即旋轉矩陣的三個旋轉引數和平移向量的三個引數。內參數直接與期盼所在空間的3D幾何相關,即外引數。而畸變引數則與點集如何畸變的2D集合相關。對於外引數,需要知道棋盤的位置,對棋盤的6個不同的視場影象,需要這6個引數,總之,在每個視場中,需要計算4個內參數和6個外引數。
4、opencv中要用到的函式
OpenCV中使用的求解焦距和偏移的演算法是基於zhang的方法,求解畸變引數是基於Brown的方法。Opencv不是使用基於3D構造物體的視場,而是使用平面物體的多個視場,使用黑白方塊交替排列的模式能保證在測量上任何一邊都沒有偏移,並且格線角點也讓亞畫素定位函式的使用更自然。
1、給定一個棋盤影象,可以使用opencv函式findChessboardCorner()來定位棋盤的角點:
int cvFindChessboardCorners(const void* image, CvSize patternSize, CvPoint2D32f* corners, nt* cornerCount=NULL, int flags=CV_CALIB_CB_ADAPTIVE_THRESH)
引數的意義為:
image:是一個輸入變數,包含棋盤的單幅影象,必須為8位灰度影象。
patternSize:表示棋盤的每行和每列有多少個角點,值應為cvSize(columns,rows).
corners:儲存角點位置的陣列指標。
注:如果函式成功找到所有的角點,則返回非0,反之,返回0.最後的flags變數可以用來定義額外的濾波步長以有助於尋找棋盤角點。
2、cvFindChessboardCorners()返回的角點僅僅是近似值,實際上位置的精度受限於影象裝置的精度,即小於一個畫素,必須單獨使用另外的函式來計算角點的精確位置以達到亞畫素精度,即函式cvFindCornerSubPix().
3、在影象上繪製找到的棋盤角點,這樣就可以看到投影的角點與觀察的角點是否匹配,opencv提供了函式cvDrawChessboardCorners()將發現的所有角點繪製到所提供的影象上,如果沒有發現所有的角點,那麼得到的角點將用紅色圓圈繪製,如果都找到就使用不同顏色繪製,並且把角點以一定的順序用線連線起來。
Void cvDrawChessboardCorners(CvArr* image, CvSize patternSize, CvPoint2D32f* corners, int count, int patternWasFound)
引數的意義:
image:要繪製的影象。必須是8位的彩色影象。就是findChessboardCorners()所使用的影象的複製品。
patternSize和corners與發現角點函式的引數意義一樣。
count:等於角點數目,是整數。
最後一個引數表示是否所有的棋盤模式都成功找到。
4、一旦有多個影象的角點,就可以呼叫函式函式cvCalibrateCamera2(),可以得到攝像機內參數矩陣、畸變係數、旋轉向量和平移向量。
double cvCalibrateCamera2(const CvMat* objectPoints, const CvMat* imagePoints, const CvMat* pointCounts,CvSize imageSize, CvMat* cameraMatrix, CvMat*distCoeffs, CvMat* rvecs=NULL, CvMat* tvecs=NULL, int flags=0 )
引數的意義:
object_points:是一個N*3的矩陣,包含物體的每k個點在每M個影象上的物理座標,N=K*M.這些點位於物體的座標
平面上。
Image_points:是一個N*2的矩陣,包含object_points所提供的所有點的畫素座標,就是由M此呼叫cvFindChessboardCorners()的返回值構成。
points_count:表示每個影象的點的個數,以M*1矩陣形式表示。
image_size:是以畫素衡量的影象尺寸。
cameraMatrix:3*3的相機內參數矩陣。
distCoeffs:5*1的畸變引數。
rvecs:旋轉引數,M*3,代表棋盤圍繞相機座標系統下三維空間的座標軸的旋轉,每個向量的長度表示逆時針旋轉的角度,可以通過呼叫cvRodrigues2()轉換為3*3的旋轉矩陣。
tvecs():平移引數,M*3。
程式碼:
- #include<opencv2\core\core.hpp>
- #include<opencv2\highgui\highgui.hpp>
- #include<opencv2\calib3d\calib3d.hpp>
- #include<opencv\cv.h>
- usingnamespace std;
- usingnamespace cv;
- class CameraCalibrator
- {
- int board_w;
- int board_h;
- int board_n;
- int N;
- CvSize board_sz;
- CvMat* image_points;
- CvMat* object_points;
- CvMat* point_counts;
- CvMat* intrinsic;
- CvMat* distortion;
- CvPoint2D32f* corners;
- int corner_count;
- int success;
- char filename[10];
- char windowname[20];
- IplImage* image;
- IplImage* gray_image;
- public:
- CameraCalibrator(void);
- CameraCalibrator(int w,int h,int n);
- ~CameraCalibrator(void);
- void FindCorners();
- void calibrate();
- };
類的實現:
- #include "CameraCalibrator.h"
- CameraCalibrator::CameraCalibrator(int w,int h,int n)
- {
- board_w=w;
- board_h=h;
- N=n;
- board_n=w*h;
- board_sz=cvSize(board_w,board_h);
- image_points=cvCreateMat(N*board_n,2,CV_32FC1);
- object_points=cvCreateMat(N*board_n,3,CV_32FC1);
- point_counts=cvCreateMat(N,1,CV_32SC1);
- intrinsic=cvCreateMat(3,3,CV_32FC1);
- distortion=cvCreateMat(5,1,CV_32FC1);
- corners=new CvPoint2D32f[board_n];
- success=0;
- }
- void CameraCalibrator::FindCorners()
- {
- int index=1;
- while(index<=N)
- {
- sprintf(filename,"chess%d.jpg",index);
- image=cvLoadImage(filename);
- gray_image=cvCreateImage(cvGetSize(image),8,1);
- int found=cvFindChessboardCorners(image,board_sz,corners,&corner_count,
- CV_CALIB_CB_ADAPTIVE_THRESH|CV_CALIB_CB_FILTER_QUADS);
- cvCvtColor(image,gray_image,CV_BGR2GRAY);
- cout<<corner_count<<endl;
- cvFindCornerSubPix(gray_image,corners,corner_count,cvSize(11,11),cvSize(-1,-1),
- cvTermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER,30,0.1));
- cvDrawChessboardCorners(image,board_sz,corners,corner_count,found);
- sprintf(windowname,"chesscalib%d",index);
- cvShowImage(windowname,image);
- cvWaitKey(0);
- if(found)
- {
- for(int i=success*board_n,j=0;j<board_n;++i,++j)
- {
- CV_MAT_ELEM(*image_points,float,i,0)=corners[j].x;
- CV_MAT_ELEM(*image_points,float,i,1)=corners[j].y;
- CV_MAT_ELEM(*object_points,float,i,0)=j/board_w;
- CV_MAT_ELEM(*object_points,float,i,1)=j%board_w;
- CV_MAT_ELEM(*object_points,float,i,2)=0.0f;
- }//endfor
- CV_MAT_ELEM(*point_counts,int,success,0)=board_n;
- success++;
- }//endif
- index++;
- }//endwhile
- }
- void CameraCalibrator::calibrate()
- {
- CV_MAT_ELEM(*intrinsic,float,0,0)=1.0f;
- CV_MAT_ELEM(*intrinsic,float,1,1)=1.0f;
- //進行標定 計算引數
- cvCalibrateCamera2(object_points,image_points,point_counts,
- cvGetSize(image),intrinsic,distortion,NULL,NULL,0);
- cvSave("intrinsic.xml",intrinsic);
- cvSave("distortion.xml",distortion);
- }
- CameraCalibrator::~CameraCalibrator(void)
- {
- cvReleaseMat(&object_points);
- cvReleaseMat(&image_points);
- cvReleaseMat(&point_counts);
- }
- //主函式:
- #include"CameraCalibrator.h"
- int main()
- {
- CameraCalibrator cam1(5,7,7);
- cam1.FindCorners();
- cam1.calibrate();
- return 0;
- }
內參數矩陣:
畸變引數矩陣:
內參數矩陣中:
F是相機的物理焦距長度,Sx是x方向每個單元畫素個數,單位是畫素/每單元, 所以fx的單位是畫素。
影象的高:240 影象的寬:320
設相機感測器的大小為m*n
Cx,Cy是成像中心相對於光軸與成像平面交點的偏移量:
畸變引數矩陣:
徑向畸變引數k1=-1.82442880,k2=16.5068817,k3=-49.8691101
切向畸變引數p1=-0.0106841922,p2=-0.00607223995
影象的高:240 影象的寬:320