關於OpenCV的那些事——相機標定

這一節我們首先介紹下計算機視覺領域中常見的三個座標系:圖像座標系,相機座標系,世界座標系。以及他們之間的關係。然後介紹如何使用張正友相機標定法標定相機。

圖像座標系:


理想的圖像座標系原點O1和真實的O0有一定的偏差,由此我們建立了等式(1)和(2),可以用矩陣形式(3)表示。

相機座標系(C)和世界座標系(W):


通過相機與圖像的投影關係,我們得到了等式(4)和等式(5),可以用矩陣形式(6)表示。我們又知道相機座標系和世界座標的關係可以用等式(7)表示:


由等式(3),等式(6)和等式(7)我們可以推導出圖像座標系和世界座標系的關係:


其中M1稱爲相機的內參矩陣,包含內參(fx,fy,u0,v0)。M2稱爲相機的外參矩陣,包含外參(R:旋轉矩陣,T:平移矩陣)。

衆所周知,相機鏡頭存在一些畸變,主要是徑向畸變(下圖dr),也包括切向畸變(下圖dt)等。


上圖右側等式中,k1,k2,k3,k4,k5,k6爲徑向畸變,p1,p2爲切向畸變。在OpenCV中我們使用張正友相機標定法通過10幅不同角度的棋盤圖像來標定相機獲得相機內參和畸變係數。函數爲calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs,flag)

objectPoints: 一組世界座標系中的3D

imagePoints: 超過10張圖片的角點集合

imageSize: 每張圖片的大小

cameraMatrix: 內參矩陣

distCoeffs: 畸變矩陣(默認獲得5個即便參數k1,k2,p1,p2,k3,可修改)

rvecs: 外參:旋轉向量

tvecs: 外參:平移向量

flag: 標定時的一些選項:

CV_CALIB_USE_INTRINSIC_GUESS:使用該參數時,在cameraMatrix矩陣中應該有fx,fy,u0,v0的估計值。否則的話,將初始化(u0,v0)圖像的中心點,使用最小二乘估算出fx,fy。

CV_CALIB_FIX_PRINCIPAL_POINT:在進行優化時會固定光軸點。當CV_CALIB_USE_INTRINSIC_GUESS參數被設置,光軸點將保持在中心或者某個輸入的值。

CV_CALIB_FIX_ASPECT_RATIO:固定fx/fy的比值,只將fy作爲可變量,進行優化計算。當CV_CALIB_USE_INTRINSIC_GUESS沒有被設置,fx和fy將會被忽略。只有fx/fy的比值在計算中會被用到。

CV_CALIB_ZERO_TANGENT_DIST:設定切向畸變參數(p1,p2)爲零。

CV_CALIB_FIX_K1,...,CV_CALIB_FIX_K6:對應的徑向畸變在優化中保持不變。

CV_CALIB_RATIONAL_MODEL:計算k4,k5,k6三個畸變參數。如果沒有設置,則只計算其它5個畸變參數。

首先我們打開攝像頭並按下'g'鍵開始標定:

VideoCapture cap(1);
cap.set(CV_CAP_PROP_FRAME_WIDTH,640);
cap.set(CV_CAP_PROP_FRAME_HEIGHT,480);
if(!cap.isOpened()){
	std::cout<<"打開攝像頭失敗,退出";
	exit(-1);
}
namedWindow("Calibration");
    std::cout<<"Press 'g' to start capturing images!"<<endl; 
if( cap.isOpened() && key == 'g' )
{
<span style="white-space:pre">	</span>mode = CAPTURING;
}
按下空格鍵(SPACE)後使用findChessboardCorners函數在當前幀尋找是否存在可用於標定的角點,如果存在將其提取出來後亞像素化並壓入角點集合,保存當前圖像:

if( (key & 255) == 32 )
{
	image_size = frame.size();
	/* 提取角點 */   
	Mat imageGray;
	cvtColor(frame, imageGray , CV_RGB2GRAY);
	bool patternfound = findChessboardCorners(frame, board_size, corners,CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE + CALIB_CB_FAST_CHECK );
	if (patternfound)   
	{    
		n++;
		tempname<<n;
		tempname>>filename;
		filename+=".jpg";
		/* 亞像素精確化 */
		cornerSubPix(imageGray, corners, Size(11, 11), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));
		count += corners.size();
		corners_Seq.push_back(corners);
		imwrite(filename,frame);
		tempname.clear();
		filename.clear();
	}
	else
	{
		std::cout<<"Detect Failed.\n";
	}
}	
角點提取完成後開始標定,首先初始化定標板上角點的三維座標:
for (int t=0;t<image_count;t++) 
{   
<span style="white-space:pre">	</span>vector<Point3f> tempPointSet;
        for (int i=0;i<board_size.height;i++) 
	{   
        <span style="white-space:pre">	</span>for (int j=0;j<board_size.width;j++) 
		{   
                /* 假設定標板放在世界座標系中z=0的平面上 */   
			Point3f tempPoint;
			tempPoint.x = i*square_size.width;
			tempPoint.y = j*square_size.height;
			tempPoint.z = 0;
			tempPointSet.push_back(tempPoint);
           <span style="white-space:pre">	</span>}   
        }
	object_Points.push_back(tempPointSet);
}
使用calibrateCamera函數開始標定:
calibrateCamera(object_Points, corners_Seq, image_size,  intrinsic_matrix  ,distortion_coeffs, rotation_vectors, translation_vectors);  
完成定標後對標定進行評價,計算標定誤差並寫入文件:
std::cout<<"每幅圖像的定標誤差:"<<endl;   
fout<<"每幅圖像的定標誤差:"<<endl<<endl;   
for (int i=0;  i<image_count;  i++) 
{
	vector<Point3f> tempPointSet = object_Points[i];
        /****    通過得到的攝像機內外參數,對空間的三維點進行重新投影計算,得到新的投影點     ****/
	projectPoints(tempPointSet, rotation_vectors[i], translation_vectors[i], intrinsic_matrix, distortion_coeffs, image_points2);
        /* 計算新的投影點和舊的投影點之間的誤差*/  
	vector<Point2f> tempImagePoint = corners_Seq[i];
	Mat tempImagePointMat = Mat(1,tempImagePoint.size(),CV_32FC2);
	Mat image_points2Mat = Mat(1,image_points2.size(), CV_32FC2);
	for (int j = 0 ; j < tempImagePoint.size(); j++)
	{
		image_points2Mat.at<Vec2f>(0,j) = Vec2f(image_points2[j].x, image_points2[j].y);
		tempImagePointMat.at<Vec2f>(0,j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);
	}
	err = norm(image_points2Mat, tempImagePointMat, NORM_L2);
        total_err += err/=  point_counts[i];   
        std::cout<<"第"<<i+1<<"幅圖像的平均誤差:"<<err<<"像素"<<endl;   
        fout<<"第"<<i+1<<"幅圖像的平均誤差:"<<err<<"像素"<<endl;   
}   
std::cout<<"總體平均誤差:"<<total_err/image_count<<"像素"<<endl;   
fout<<"總體平均誤差:"<<total_err/image_count<<"像素"<<endl<<endl;   
std::cout<<"評價完成!"<<endl;
顯示標定結果並寫入文件:
std::cout<<"開始保存定標結果………………"<<endl;       
Mat rotation_matrix = Mat(3,3,CV_32FC1, Scalar::all(0)); /* 保存每幅圖像的旋轉矩陣 */   
       
fout<<"相機內參數矩陣:"<<endl;   
fout<<intrinsic_matrix<<endl<<endl;   
fout<<"畸變係數:\n";   
fout<<distortion_coeffs<<endl<<endl<<endl;   
for (int i=0; i<image_count; i++) 
{ 
        fout<<"第"<<i+1<<"幅圖像的旋轉向量:"<<endl;   
        fout<<rotation_vectors[i]<<endl;   
  
        /* 將旋轉向量轉換爲相對應的旋轉矩陣 */   
        Rodrigues(rotation_vectors[i],rotation_matrix);   
        fout<<"第"<<i+1<<"幅圖像的旋轉矩陣:"<<endl;   
        fout<<rotation_matrix<<endl;   
        fout<<"第"<<i+1<<"幅圖像的平移向量:"<<endl;   
        fout<<translation_vectors[i]<<endl<<endl;   
}   
std::cout<<"完成保存"<<endl; 
fout<<endl;

具體的代碼實現和工程詳見:Calibration

運行截圖:



下一節我們將使用RPP相機姿態算法得到相機的外部參數:旋轉和平移。


==============================================================================================

2015/11/14補充:

所有分辨率下的畸變(k1,k2,p1,p2)相同,但內參不同(fx,fy,u0,v0),不同分辨率下需要重新標定相機內參。以下是羅技C920在1920*1080下的內參:


==============================================================================================

2016/08/20補充:

findChessboardCorners函數的第二個參數是定義棋盤格的橫縱內角點個數,要設置正確,不然函數找不到合適的角點,返回false。如下圖中的橫內角點是12,縱內角點是7,則Size board_size = Size(12, 7); 


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