我們首先介紹下計(jì)算機(jī)視覺(jué)領(lǐng)域中常見的三個(gè)坐標(biāo)系:圖像坐標(biāo)系,相機(jī)坐標(biāo)系,世界坐標(biāo)系。以及他們之間的關(guān)系。然后介紹如何使用張正友相機(jī)標(biāo)定法標(biāo)定相機(jī)。
總體原理:
攝像機(jī)標(biāo)定(Camera calibration)簡(jiǎn)單來(lái)說(shuō)是從世界坐標(biāo)系換到圖像坐標(biāo)系的過(guò)程,也就是求最終的投影矩陣的過(guò)程。
基本的坐標(biāo)系:
世界坐標(biāo)系;
相機(jī)坐標(biāo)系;
成像平面坐標(biāo)系;
像素坐標(biāo)系
一般來(lái)說(shuō),標(biāo)定的過(guò)程分為兩個(gè)部分:
第一步是從世界坐標(biāo)系轉(zhuǎn)為相機(jī)坐標(biāo)系,這一步是三維點(diǎn)到三維點(diǎn)的轉(zhuǎn)換,包括R,t(相機(jī)外參,確定了相機(jī)在某個(gè)三維空間中的位置和朝向)等參數(shù);
第二部是從相機(jī)坐標(biāo)系轉(zhuǎn)為成像平面坐標(biāo)系(像素坐標(biāo)系),這一步是三維點(diǎn)到二維點(diǎn)的轉(zhuǎn)換,包括K(相機(jī)內(nèi)參,是對(duì)相機(jī)物理特性的近似)等參數(shù);
投影矩陣 : P=K [ R | t ] 是一個(gè)3×4矩陣,混合了內(nèi)參和外參而成。
圖像坐標(biāo)系:
理想的圖像坐標(biāo)系原點(diǎn)O1和真實(shí)的O0有一定的偏差,由此我們建立了等式(1)和(2),可以用矩陣形式(3)表示。
相機(jī)坐標(biāo)系(C)和世界坐標(biāo)系(W):
通過(guò)相機(jī)與圖像的投影關(guān)系,我們得到了等式(4)和等式(5),可以用矩陣形式(6)表示。我們又知道相機(jī)坐標(biāo)系和世界坐標(biāo)的關(guān)系可以用等式(7)表示:
由等式(3),等式(6)和等式(7)我們可以推導(dǎo)出圖像坐標(biāo)系和世界坐標(biāo)系的關(guān)系:
其中M1稱為相機(jī)的內(nèi)參矩陣,包含內(nèi)參(fx,fy,u0,v0)。M2稱為相機(jī)的外參矩陣,包含外參(R:旋轉(zhuǎn)矩陣,T:平移矩陣)。
眾所周知,相機(jī)鏡頭存在一些畸變,主要是徑向畸變(下圖dr),也包括切向畸變(下圖dt)等。
上圖右側(cè)等式中,k1,k2,k3,k4,k5,k6為徑向畸變,p1,p2為切向畸變。在OpenCV中我們使用張正友相機(jī)標(biāo)定法通過(guò)10幅不同角度的棋盤圖像來(lái)標(biāo)定相機(jī)獲得相機(jī)內(nèi)參和畸變系數(shù)。函數(shù)為calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs,flag)
objectPoints: 一組世界坐標(biāo)系中的3D
imagePoints: 超過(guò)10張圖片的角點(diǎn)集合
imageSize: 每張圖片的大小
cameraMatrix: 內(nèi)參矩陣
distCoeffs: 畸變矩陣(默認(rèn)獲得5個(gè)即便參數(shù)k1,k2,p1,p2,k3,可修改)
rvecs: 外參:旋轉(zhuǎn)向量
tvecs: 外參:平移向量
flag: 標(biāo)定時(shí)的一些選項(xiàng):
CV_CALIB_USE_INTRINSIC_GUESS:使用該參數(shù)時(shí),在cameraMatrix矩陣中應(yīng)該有fx,fy,u0,v0的估計(jì)值。否則的話,將初始化(u0,v0)圖像的中心點(diǎn),使用最小二乘估算出fx,fy。
CV_CALIB_FIX_PRINCIPAL_POINT:在進(jìn)行優(yōu)化時(shí)會(huì)固定光軸點(diǎn)。當(dāng)CV_CALIB_USE_INTRINSIC_GUESS參數(shù)被設(shè)置,光軸點(diǎn)將保持在中心或者某個(gè)輸入的值。
CV_CALIB_FIX_ASPECT_RATIO:固定fx/fy的比值,只將fy作為可變量,進(jìn)行優(yōu)化計(jì)算。當(dāng)CV_CALIB_USE_INTRINSIC_GUESS沒(méi)有被設(shè)置,fx和fy將會(huì)被忽略。只有fx/fy的比值在計(jì)算中會(huì)被用到。
CV_CALIB_ZERO_TANGENT_DIST:設(shè)定切向畸變參數(shù)(p1,p2)為零。
CV_CALIB_FIX_K1,。。。,CV_CALIB_FIX_K6:對(duì)應(yīng)的徑向畸變?cè)趦?yōu)化中保持不變。
CV_CALIB_RATIONAL_MODEL:計(jì)算k4,k5,k6三個(gè)畸變參數(shù)。如果沒(méi)有設(shè)置,則只計(jì)算其它5個(gè)畸變參數(shù)。
首先我們打開攝像頭并按下‘g’鍵開始標(biāo)定:
?。踓pp] view plain copy print?
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;
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;
?。踓pp] view plain copy print?
if( cap.isOpened() && key == ‘g’ )
{
《span style=“white-space:pre”》 《/span》mode = CAPTURING;
}
if( cap.isOpened() && key == ‘g’ )
{
《span style=“white-space:pre”》 《/span》mode = CAPTURING;
}
按下空格鍵(SPACE)后使用findChessboardCorners函數(shù)在當(dāng)前幀尋找是否存在可用于標(biāo)定的角點(diǎn),如果存在將其提取出來(lái)后亞像素化并壓入角點(diǎn)集合,保存當(dāng)前圖像:
?。踓pp] view plain copy print?
if( (key & 255) == 32 )
{
image_size = frame.size();
/* 提取角點(diǎn) */
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”;
}
}
if( (key & 255) == 32 )
{
image_size = frame.size();
/* 提取角點(diǎn) */
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”;
}
}
角點(diǎn)提取完成后開始標(biāo)定,首先初始化定標(biāo)板上角點(diǎn)的三維坐標(biāo):
[cpp] view plain copy print?
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++)
{
/* 假設(shè)定標(biāo)板放在世界坐標(biāo)系中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);
}
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++)
{
/* 假設(shè)定標(biāo)板放在世界坐標(biāo)系中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函數(shù)開始標(biāo)定:
[cpp] view plain copy print?
calibrateCamera(object_Points, corners_Seq, image_size, intrinsic_matrix ,distortion_coeffs, rotation_vectors, translation_vectors);
calibrateCamera(object_Points, corners_Seq, image_size, intrinsic_matrix ,distortion_coeffs, rotation_vectors, translation_vectors);
完成定標(biāo)后對(duì)標(biāo)定進(jìn)行評(píng)價(jià),計(jì)算標(biāo)定誤差并寫入文件:
?。踓pp] view plain copy print?
std::cout《《“每幅圖像的定標(biāo)誤差:”《《endl;
fout《《“每幅圖像的定標(biāo)誤差:”《《endl《《endl;
for (int i=0; i《image_count; i++)
{
vector《Point3f》 tempPointSet = object_Points[i];
/**** 通過(guò)得到的攝像機(jī)內(nèi)外參數(shù),對(duì)空間的三維點(diǎn)進(jìn)行重新投影計(jì)算,得到新的投影點(diǎn) ****/
projectPoints(tempPointSet, rotation_vectors[i], translation_vectors[i], intrinsic_matrix, distortion_coeffs, image_points2);
/* 計(jì)算新的投影點(diǎn)和舊的投影點(diǎn)之間的誤差*/
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《《“評(píng)價(jià)完成!”《《endl;
std::cout《《“每幅圖像的定標(biāo)誤差:”《《endl;
fout《《“每幅圖像的定標(biāo)誤差:”《《endl《《endl;
for (int i=0; i《image_count; i++)
{
vector《Point3f》 tempPointSet = object_Points[i];
/**** 通過(guò)得到的攝像機(jī)內(nèi)外參數(shù),對(duì)空間的三維點(diǎn)進(jìn)行重新投影計(jì)算,得到新的投影點(diǎn) ****/
projectPoints(tempPointSet, rotation_vectors[i], translation_vectors[i], intrinsic_matrix, distortion_coeffs, image_points2);
/* 計(jì)算新的投影點(diǎn)和舊的投影點(diǎn)之間的誤差*/
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《《“評(píng)價(jià)完成!”《《endl;
顯示標(biāo)定結(jié)果并寫入文件:
[cpp] view plain copy print?
std::cout《《“開始保存定標(biāo)結(jié)果………………”《《endl;
Mat rotation_matrix = Mat(3,3,CV_32FC1, Scalar::all(0)); /* 保存每幅圖像的旋轉(zhuǎn)矩陣 */
fout《《“相機(jī)內(nèi)參數(shù)矩陣:”《《endl;
fout《《intrinsic_matrix《《endl《《endl;
fout《《“畸變系數(shù):\n”;
fout《《distortion_coeffs《《endl《《endl《《endl;
for (int i=0; i《image_count; i++)
{
fout《《“第”《《i+1《《“幅圖像的旋轉(zhuǎn)向量:”《《endl;
fout《《rotation_vectors[i]《《endl;
/* 將旋轉(zhuǎn)向量轉(zhuǎn)換為相對(duì)應(yīng)的旋轉(zhuǎn)矩陣 */
Rodrigues(rotation_vectors[i],rotation_matrix);
fout《《“第”《《i+1《《“幅圖像的旋轉(zhuǎn)矩陣:”《《endl;
fout《《rotation_matrix《《endl;
fout《《“第”《《i+1《《“幅圖像的平移向量:”《《endl;
fout《《translation_vectors[i]《《endl《《endl;
}
std::cout《《“完成保存”《《endl;
fout《《endl;
std::cout《《“開始保存定標(biāo)結(jié)果………………”《《endl;
Mat rotation_matrix = Mat(3,3,CV_32FC1, Scalar::all(0)); /* 保存每幅圖像的旋轉(zhuǎn)矩陣 */
fout《《“相機(jī)內(nèi)參數(shù)矩陣:”《《endl;
fout《《intrinsic_matrix《《endl《《endl;
fout《《“畸變系數(shù):\n”;
fout《《distortion_coeffs《《endl《《endl《《endl;
for (int i=0; i《image_count; i++)
{
fout《《“第”《《i+1《《“幅圖像的旋轉(zhuǎn)向量:”《《endl;
fout《《rotation_vectors[i]《《endl;
/* 將旋轉(zhuǎn)向量轉(zhuǎn)換為相對(duì)應(yīng)的旋轉(zhuǎn)矩陣 */
Rodrigues(rotation_vectors[i],rotation_matrix);
fout《《“第”《《i+1《《“幅圖像的旋轉(zhuǎn)矩陣:”《《endl;
fout《《rotation_matrix《《endl;
fout《《“第”《《i+1《《“幅圖像的平移向量:”《《endl;
fout《《translation_vectors[i]《《endl《《endl;
}
std::cout《《“完成保存”《《endl;
fout《《endl;
具體的代碼實(shí)現(xiàn)和工程詳見:Calibration
運(yùn)行截圖:
下一節(jié)我們將使用RPP相機(jī)姿態(tài)算法得到相機(jī)的外部參數(shù):旋轉(zhuǎn)和平移。
2015/11/14補(bǔ)充:
所有分辨率下的畸變(k1,k2,p1,p2)相同,但內(nèi)參不同(fx,fy,u0,v0),不同分辨率下需要重新標(biāo)定相機(jī)內(nèi)參。以下是羅技C920在1920*1080下的內(nèi)參:
2016/08/20補(bǔ)充:
findChessboardCorners函數(shù)的第二個(gè)參數(shù)是定義棋盤格的橫縱內(nèi)角點(diǎn)個(gè)數(shù),要設(shè)置正確,不然函數(shù)找不到合適的角點(diǎn),返回false。如下圖中的橫內(nèi)角點(diǎn)是12,縱內(nèi)角點(diǎn)是7,則Size board_size = Size(12, 7);
評(píng)論