相機成像幾何模型(原理)
一、四大座標系及目的
四大座標系:世界座標系(測量座標系),相機座標系,圖像座標系(膠捲座標系,連續值),像素座標系。
目的:用數學方式描述3D點如何投影到2D像素座標系中(正投影:Forward projection),以及反過來的投影過程(Back projection)。
計算機視覺的首要任務就是要通過拍攝到的圖像信息獲取到物體在真實三維世界裏相對應的信息,於是,建立物體從三維世界映射到相機成像平面這一過程中的幾何模型就顯得尤爲重要
另外,描述相機座標系下的3D點投影到圖像座標系下2D點的過程稱爲透視投影(perspective projection)。其中,f爲相機焦距,(X,Y,Z)爲相機座標系下某點的座標,(x,y)爲圖像座標系下與(X,Y,Z)對應的座標。
二、從世界座標系到相機座標系的變換
世界座標系:也稱測量座標系,它是一個三維直角座標系()。
在世界座標系中,可以描述相機和待測物體的空間位置。而世界座標系的位置根據實際情況自行確定。
相機座標系:它也是一個三維直角座標系()。
相機座標系的原點是鏡頭的光心,x、y軸分別與像平面兩邊平行,z軸爲鏡頭的光軸,與像平面垂直。
從世界座標系到相機座標系:剛體變換,也就是隻改變物體的空間位置(平移)和朝向(旋轉),而不改變物體的形狀。
用旋轉矩陣R和平移向量t可以表示這種變換。
在齊次座標下,旋轉矩陣R是正交矩陣,可通過Rodrigues變換轉爲只有三個獨立變量的旋轉向量。因此,剛體變換用6個參數就可以表示(3個旋轉向量,3個平移向量),而這6個參數就作爲相機的外參。
相機外參實現了空間點從世界座標系到相機座標系的變換。
其中,R 是 3 3,t 是 3 1。
齊次座標下,可以表示爲:
三、從相機座標系到圖像座標系的變換
圖像座標系:也叫平面座標系。用物理單位表示像素的位置,單位是mm。座標原點爲相機光軸與成像平面的交點,通常情況下是成像平面的中點。
從相機座標系到圖像座標系:屬於透視投影關係,從3D轉換到2D。
根據相似三角形原理:
在齊次座標下表示爲:
其中,爲透視投影矩陣。
這樣就完成了相機座標系到理想的圖像座標系的轉換(我們默認各個座標系的變換都是線性的),但實際上,相機鏡頭中的鏡片由於光線的通過產生不規則的折射,總是存在鏡頭畸變的。畸變的引入使得成像模型中的幾何變換關係爲非線性。
畸變的類型很多,但通常只考慮徑向畸變和切向畸變。
引入畸變之後,理想的圖像座標系到真實的圖像座標系的變換爲:
其中,和爲徑向引起的畸變,和爲切向引起的畸變。
徑向畸變形成的原因:鏡頭本身的缺陷(製造工藝不完美)導致的。
包括枕形畸變和桶形畸變。
從圖中可以看出:離中心越遠的地方,形變越明顯(eg:四個角的位置)。
即:鏡頭的邊緣形變更顯著。
來張真實的效果圖:
切向畸變:有薄透鏡畸變和離心畸變等。
薄透鏡畸變形成的原因:透鏡存在一定的細微傾斜。
離心畸變形成的原因:鏡頭由多個透鏡組合而成,而各透鏡的光軸不在同一條中心線上。
四、從圖像座標系到像素座標系的變換
從圖像座標系到像素座標系:沒有旋轉,只是座標原點和單位不一樣。
圖像座標系座標原點爲相機光軸與成像平面的交點,單位是mm,屬於物理單位。
像素座標系座標原點在左上角,以像素爲單位,我們通常描述一個像素點是幾行幾列。
所以,兩者之間的轉換如下:
在齊次座標下:
五、相機投影模型的總結
通過上面四個座標系的轉換就可以得到一個點從世界座標系轉換到像素座標系:
其中,爲相機內參,爲相機外參。
上面等式的模型如下:
所以,相機成像模型最關鍵的部分就是要得到相機的內參和外參。
相機標定內參和外參(代碼)
所用的圖片:
相機標定時可用的標定板圖像集
代碼:
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/calib3d/calib3d.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <fstream>
using namespace cv;
using namespace std;
int main()
{
ifstream fin("calibdata.txt"); /* 標定所用圖像文件的路徑 */
ofstream fout("caliberation_result.txt"); /* 保存標定結果的文件 */
//讀取每一幅圖像,從中提取出角點,然後對角點進行亞像素精確化
cout << "開始提取角點………………" << endl;
int image_count=0; /* 圖像數量 */
Size image_size; /* 圖像的尺寸 */
Size board_size = Size(6,9); /* 標定板上每行、列的角點數 */
vector<Point2f> image_points_buf; /* 緩存每幅圖像上檢測到的角點 */
vector<vector<Point2f>> image_points_seq; /* 保存檢測到的所有角點 */
string filename;
int count= -1 ;//用於存儲角點個數
while (getline(fin,filename))
{
image_count++;
// 用於觀察檢驗輸出
cout<<"image_count = "<<image_count<<endl;
Mat imageInput=imread(filename);
if (image_count == 1) //讀入第一張圖片時獲取圖像寬高信息
{
image_size.width = imageInput.cols;
image_size.height =imageInput.rows;
cout<<"image_size.width = "<<image_size.width<<endl;
cout<<"image_size.height = "<<image_size.height<<endl;
}
/* 提取角點 */
if (0 == findChessboardCorners(imageInput,board_size,image_points_buf))
{
cout<<"can not find chessboard corners!\n"; //找不到角點
exit(1);
}
else
{
Mat view_gray;
cvtColor(imageInput,view_gray,CV_RGB2GRAY);
/* 亞像素精確化 */
find4QuadCornerSubpix(view_gray,image_points_buf,Size(5,5)); //對粗提取的角點進行精確化
//cornerSubPix(view_gray,image_points_buf,Size(5,5),Size(-1,-1),TermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER,30,0.1));
image_points_seq.push_back(image_points_buf); //保存亞像素角點
/* 在圖像上顯示角點位置 */
drawChessboardCorners(view_gray,board_size,image_points_buf,false); //用於在圖片中標記角點
imshow("Camera Calibration",view_gray);//顯示圖片
waitKey(10000);//暫停0.5S
}
}
int total = image_points_seq.size();
cout<<"total = "<<total<<endl;
//相機標定
cout<<"開始標定………………";
/*棋盤三維信息*/
Size square_size = Size(10,10); /* 實際測量得到的標定板上每個棋盤格的大小 */
vector<vector<Point3f>> object_points; /* 保存標定板上角點的三維座標 */
/*內外參數*/
Mat cameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0)); /* 相機內參矩陣 */
vector<int> point_counts; // 每幅圖像中角點的數量
Mat distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0)); /* 攝像機的5個畸變係數:k1,k2,p1,p2,k3 */
vector<Mat> tvecsMat; /* 每幅圖像的平移向量 */
vector<Mat> rvecsMat; /* 每幅圖像的旋轉向量 */
/* 初始化標定板上角點的三維座標 */
int i,j,t;
for (t=0;t<image_count;t++)
{
vector<Point3f> tempPointSet;
for (i=0;i<board_size.height;i++)
{
for (j=0;j<board_size.width;j++)
{
Point3f realPoint;
/* 假設標定板放在世界座標系中z=0的平面上 */
realPoint.x = i*square_size.width;
realPoint.y = j*square_size.height;
realPoint.z = 0;
tempPointSet.push_back(realPoint);
}
}
object_points.push_back(tempPointSet);
}
/* 初始化每幅圖像中的角點數量,假定每幅圖像中都可以看到完整的標定板 */
for (i=0;i<image_count;i++)
{
point_counts.push_back(board_size.width*board_size.height);
}
/* 開始標定 */
calibrateCamera(object_points,image_points_seq,image_size,cameraMatrix,distCoeffs,rvecsMat,tvecsMat,0);
cout<<"標定完成!\n";
//對標定結果進行評價
cout<<"開始評價標定結果………………\n";
double total_err = 0.0; /* 所有圖像的平均誤差的總和 */
double err = 0.0; /* 每幅圖像的平均誤差 */
vector<Point2f> image_points2; /* 保存重新計算得到的投影點 */
cout<<"\t每幅圖像的標定誤差:\n";
fout<<"===========每幅圖像的標定誤差===========\n";
for (i=0;i<image_count;i++)
{
vector<Point3f> tempPointSet=object_points[i];
/* 通過得到的攝像機內外參數,對空間的三維點進行重新投影計算,得到新的投影點 */
projectPoints(tempPointSet,rvecsMat[i],tvecsMat[i],cameraMatrix,distCoeffs,image_points2);
/* 計算新的投影點和舊的投影點之間的誤差*/
vector<Point2f> tempImagePoint = image_points_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<<"pixel"<<endl;
fout<<"第"<<i+1<<"幅圖像的平均誤差:"<<err<<"pixel"<<endl;
}
std::cout<<"總體平均誤差:"<<total_err/image_count<<"pixel"<<endl;
fout<<"總體平均誤差:"<<total_err/image_count<<"pixel"<<endl<<endl;
std::cout<<"評價完成!"<<endl;
//保存定標結果
std::cout<<"開始保存定標結果………………"<<endl;
Mat rotation_matrix = Mat(3,3,CV_32FC1, Scalar::all(0)); /* 保存每幅圖像的旋轉矩陣 */
fout<<"================相機內參================"<<endl;
fout<<"=============(非齊次座標下)============="<< endl;
fout<<cameraMatrix<<endl<<endl;
fout << "================相機外參================" << endl;
fout << "=============(非齊次座標下)=============" << endl;
for (int i=0; i<image_count; i++)
{
fout<<"第"<<i+1<<"幅圖像的旋轉向量:"<<endl;
fout<<rvecsMat[i]<<endl;
/* 將旋轉向量轉換爲相對應的旋轉矩陣 */
Rodrigues(rvecsMat[i],rotation_matrix);
fout<<"第"<<i+1<<"幅圖像的旋轉矩陣:"<<endl;
fout<<rotation_matrix<<endl;
fout<<"第"<<i+1<<"幅圖像的平移向量:"<<endl;
fout<<tvecsMat[i]<<endl<<endl;
}
std::cout<<"完成保存"<<endl;
fout<<endl;
return 0;
}
運行結果:
(1)角點繪製(以right01.jpg~right04.jpg爲例)
(2)命令行運行結果
(3)文件中運行結果
遇到的一些問題
(1)如何將圖片集保存到txt文本中?
保證圖片集和txt文件在同一目錄,然後圖片名字寫入txt文件中保存。
(2)繪製標定板上的角點時,應設定最佳的角點數。這裏給定的標定板爲7*10格(7行10列)的,所以代碼設定爲
Size board_size = Size(6,9);
即最外邊的角點都不列入其中。
3、相機內參爲4個,外參爲6個,可以直接給出內參和外參,也可以以內參矩陣和外參矩陣的方式給出。另外,內參矩陣和外參矩陣又分爲齊次座標下的和非齊次座標下的。