初学OpenCV之摄像机标定
最近方向定下來(lái)是雙目立體視覺(jué),主要是做重建這塊的研究。大致過(guò)程是圖像獲取->攝像機(jī)標(biāo)定->特征提取->匹配->三維重建,當(dāng)然開(kāi)始可以進(jìn)行圖像預(yù)處理,矯正,后期可以進(jìn)行點(diǎn)云的進(jìn)一步處理,如渲染表面使其更接近于現(xiàn)實(shí)物體。
圖像獲取相對(duì)來(lái)說(shuō)比較簡(jiǎn)單,用相機(jī)拍攝目標(biāo)物(大型場(chǎng)景或特定小型的室內(nèi)物體)。但有兩點(diǎn)需要注意:
1、雙目重建所需的圖像一般為兩張,角度相差不應(yīng)過(guò)大,否則公共部分太少以至于重建效果不佳;整個(gè)過(guò)程簡(jiǎn)便,成本也不高,但缺陷是只有兩張圖像的點(diǎn)云所表示的物體信息不會(huì)很全面;
2、標(biāo)定所需的圖像又是另外拍攝的,用張正友標(biāo)定法的話(huà),把印有黑白棋盤(pán)格的圖像粘至硬紙板上,然后左右攝像機(jī)各自進(jìn)行拍攝,理論上獲得角度(圖像)越多,最終標(biāo)定結(jié)果越精確;標(biāo)定板見(jiàn)下圖:
這里主要結(jié)合OpenCV對(duì)左右攝像機(jī)標(biāo)定做一個(gè)簡(jiǎn)單的介紹,望朋友們指正,一起交流、進(jìn)步。
攝像機(jī)的標(biāo)定問(wèn)題是機(jī)器視覺(jué)領(lǐng)域的入門(mén)問(wèn)題,可以分為傳統(tǒng)的攝像機(jī)定標(biāo)方法和攝像機(jī)自定標(biāo)方法。定標(biāo)的方法有很多中常見(jiàn)的有:Tsai(傳統(tǒng))和張正友(介于傳統(tǒng)和自定標(biāo))等,
攝像機(jī)成像模型和四個(gè)坐標(biāo)系(通用原理)。
攝像機(jī)模型采用經(jīng)典的小孔模型,如圖中Oc(光心),像面π表示的是視野平面,其到光心的距離為f(鏡頭焦距)。
四個(gè)坐標(biāo)系分別為:世界坐標(biāo)系(Ow),攝像機(jī)坐標(biāo)系(Oc),圖像物理坐標(biāo)系(O1,單位mm),圖像像素坐標(biāo)系(O,位于視野平面的左上角,單位pix)。
空間某點(diǎn)P到其像點(diǎn)p的坐標(biāo)轉(zhuǎn)換過(guò)程主要是通過(guò)這四套坐標(biāo)系的三次轉(zhuǎn)換實(shí)現(xiàn)的,首先將世界坐標(biāo)系進(jìn)行平移和轉(zhuǎn)換得到攝像機(jī)坐標(biāo)系,然后根據(jù)三角幾何變換得到圖像物理坐標(biāo)系,最后根據(jù)像素和公制單位的比率得到圖像像素坐標(biāo)系。(實(shí)際的應(yīng)用過(guò)程是這個(gè)的逆過(guò)程,即由像素長(zhǎng)度獲知實(shí)際的長(zhǎng)度)。
ps:通過(guò)攝像頭的標(biāo)定,可以得到視野平面上的mm/pix分辨率,對(duì)于視野平面以外的物體還是需要通過(guò)坐標(biāo)轉(zhuǎn)換得到視野平面上。
轉(zhuǎn)化的過(guò)程和公式參見(jiàn):攝像機(jī)標(biāo)定原理(關(guān)鍵是三個(gè)坐標(biāo)系).ppt
2 張正友算法的原理
zhang法通過(guò)對(duì)一定標(biāo)板在不同方向多次(三次以上)完整拍照,不需要知道定標(biāo)板的運(yùn)動(dòng)方式。直接獲得相機(jī)的內(nèi)參(參考文獻(xiàn)上矩陣A)和畸變系數(shù)。該標(biāo)定方法精度高于自定標(biāo)法,且不需要高精度的定位儀器。
ZHANG的算法包含兩個(gè)模型:一.經(jīng)典針孔模型,包含四個(gè)坐標(biāo)系,二畸變模型(這個(gè)來(lái)源未知)
公式三項(xiàng)依次表示,徑向畸變,切線畸變,薄棱鏡畸變。OPENCV中函數(shù)只能給出k1,k2,p1,p2。
還存在另外一種畸變模型,見(jiàn)《攝像機(jī)標(biāo)定算法庫(kù)的設(shè)計(jì)和試驗(yàn)驗(yàn)證》一文26 page。
張正友標(biāo)定有matlab的工具箱TOOLBOX_CAL,以及OpenCV庫(kù),下面是C++結(jié)合OpenCV的代碼:
1 #include "cvut.h"
2 #include <iostream>
3 #include <fstream>
4 #include <string>
5 using namespace cvut;
6 using namespace std;
7 void main()
8 {
9 ifstream fin("calibdata.txt");
10 ofstream fout("calibration_result.txt");
11 //****************開(kāi)始提取角點(diǎn)***********************//
12 cout<<"開(kāi)始提取角點(diǎn)………………";
13 int image_count=0;
14 CvSize image_size;
15 CvSize board_size = cvSize(5,7);
16 CvPoint2D32f * image_points_buf =
17 new CvPoint2D32f[board_size.width*board_size.height];
18 Seq<CvPoint2D32f> image_points_seq;
19 string filename;
20 while (std::getline(fin,filename))
21 {
22 cout<<"\n 將鼠標(biāo)焦點(diǎn)移到標(biāo)定圖像所在窗口"
23 <<"并輸入回車(chē)進(jìn)行下一幅圖像的角點(diǎn)提取 \n";
24 image_count++;
25 int count;
26 Image<uchar> view(filename);
27 if (image_count == 1)
28 {
29 image_size.width = view.size().width;
30 image_size.height = view.size().height;
31 }
32 if (0 == cvFindChessboardCorners( view.cvimage, board_size,
33 image_points_buf, &count, CV_CALIB_CB_ADAPTIVE_THRESH ))
34 {
35 cout<<"can not find chessboard corners!\n";
36 exit(1);
37 }
38 else
39 {
40 Image<uchar> view_gray(view.size(),8,1);
41 rgb2gray(view,view_gray);
42 cvFindCornerSubPix( view_gray.cvimage,
43 image_points_buf, count, cvSize(11,11),
44 cvSize(-1,-1),
45 cvTermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 30, 0.1 ));
46 image_points_seq.push_back(image_points_buf,count);
47 cvDrawChessboardCorners( view.cvimage, board_size,
48 image_points_buf, count, 1);
49 view.show("calib");
50 cvWaitKey();
51 view.close();
52 }
53 }//角點(diǎn)提取循環(huán)
54 delete []image_points_buf;
55 cout<<"角點(diǎn)提取完成!\n"<<endl;
56 cout<<"開(kāi)始定標(biāo)………………"<<"\n"<<endl;
57 CvSize square_size = cvSize(10,10);
58 Matrix<double> object_points(1,
59 board_size.width*board_size.height*image_count,3);
60 Matrix<double> image_points(1,image_points_seq.cvseq->total,2);
61 Matrix<int> point_counts(1,image_count,1);
62 Matrix<double> intrinsic_matrix(3,3,1);
63 Matrix<double> distortion_coeffs(1,4,1);
64 Matrix<double> rotation_vectors(1,image_count,3);
65 Matrix<double> translation_vectors(1,image_count,3);
66 int i,j,t;
67 for (t=0;t<image_count;t++) {
68 for (i=0;i<board_size.height;i++) {
69 for (j=0;j<board_size.width;j++) {
70 object_points(0,t*board_size.height*board_size.width
71 + i*board_size.width + j,0) = i*square_size.width;
72 object_points(0,t*board_size.height*board_size.width
73 + i*board_size.width + j,1) = j*square_size.height;
74 object_points(0,t*board_size.height*board_size.width
75 + i*board_size.width + j,2) = 0;
76 }
77 }
78 }
79 char str[10];
80 itoa(image_points_seq.cvseq->total,str,10);
81 cout<<str<<"\n"<<endl;
82 for (i=0;i<image_points_seq.cvseq->total;i++)
83 {
84 image_points(0,i,0) = image_points_seq[i].x;
85 image_points(0,i,1) = image_points_seq[i].y;
86 }
87 for (i=0;i<image_count;i++)
88 {
89 point_counts(0,i) = board_size.width*board_size.height;
90 }
91 cvCalibrateCamera2(object_points.cvmat,
92 image_points.cvmat,
93 point_counts.cvmat,
94 image_size,
95 intrinsic_matrix.cvmat,
96 distortion_coeffs.cvmat,
97 rotation_vectors.cvmat,
98 translation_vectors.cvmat,
99 0);
100 cout<<"定標(biāo)完成!\n";
101 cout<<"標(biāo)定結(jié)果顯示\n";
102 cout<<"***************************************\n";
103 cout<<"相機(jī)內(nèi)參intrinsic_matrix\n";
104 for(int h=0;h<3;h++)
105 {
106 cout<<"X:"<<intrinsic_matrix(h,0,0)<<"\tY:"<<
107 intrinsic_matrix(h,1,0)<<"\tZ:"<<intrinsic_matrix(h,2,0)
108 <<"\n";
109 }
110 cout<<"\n畸變系數(shù):distortion_coeffs\n";
111 for(int ndis=0;ndis<4;ndis++)
112 {
113 cout<<distortion_coeffs(0,ndis,0)<<"\\";
114 }
115 cout<<"\n";
116 cout<<"\nrotation_vectors\n";
117 for(int rot=0;rot<7;rot++)
118 {
119 cout<<"X:"<<rotation_vectors(0,rot,0)<<"\tY:"
120 <<rotation_vectors(0,rot,1)<<"\tZ:"<<rotation_vectors(0,rot,2)
121 <<"\n";
122 }
123 cout<<"\ntranslation_vectors\n";
124 for(i=0;i<7;i++)
125 {
126 cout<<"第"<<i+1<<"張圖"<<"\tX:"<<translation_vectors(0,i,0)
127 <<"\tY:"<<translation_vectors(0,i,1)
128 <<"\tZ:"<<translation_vectors(0,i,2)<<"\n";
129 }
130 cout<<"*********************\n";
131 cout<<"開(kāi)始評(píng)價(jià)定標(biāo)結(jié)果………………\n";
132 double total_err = 0.0;
133 double err = 0.0;
134 Matrix<double> image_points2(1,point_counts(0,0,0),2);
135 int temp_num = point_counts(0,0,0);
136 cout<<"\t每幅圖像的定標(biāo)誤差:\n";
137 fout<<"每幅圖像的定標(biāo)誤差:\n";
138 for (i=0;i<image_count;i++)
139 {
140 cvProjectPoints2(object_points.get_cols(i *
141 point_counts(0,0,0),(i+1)*point_counts(0,0,0)-1).cvmat,
142 rotation_vectors.get_col(i).cvmat,
143 translation_vectors.get_col(i).cvmat,
144 intrinsic_matrix.cvmat,
145 distortion_coeffs.cvmat,
146 image_points2.cvmat,
147 0,0,0,0);
148 err = cvNorm(image_points.get_cols(i*point_counts(0,0,0),(i+1)
149 *point_counts(0,0,0)-1).cvmat,
150 image_points2.cvmat,
151 CV_L1);
152 total_err += err/=point_counts(0,0,0);
153 cout<<"****************************\n";
154 cout<<"\t\t第"<<i+1<<"幅圖像的平均誤差:"<<err<<"像素"<<'\n';
155 fout<<"\t第"<<i+1<<"幅圖像的平均誤差:"<<err<<"像素"<<'\n';
156 cout<<"顯示image_point2\n";
157 for(int ih=0;ih<7;ih++)
158 {
159 cout<<"X:"<<image_points2(0,ih,0)<<"\tY:"
160 <<image_points2(0,ih,1)<<"\n";
161 }
162 cout<<"顯示object_Points\n";
163 for(int iw=0;iw<7;iw++)
164 {
165 cout<<"X:"<<image_points.get_cols(i*point_counts(0,0,0),(i+1)
166 *point_counts(0,0,0)-1)(0,iw,0)
167 <<"\tY:"<<image_points.get_cols(i*point_counts(0,0,0),(i+1)
168 *point_counts(0,0,0)-1)(0,iw,1)<<"\n";
169 }
170 }
171 cout<<"\t總體平均誤差:"<<total_err/image_count<<"像素"<<'\n';
172 fout<<"總體平均誤差:"<<total_err/image_count<<"像素"<<'\n'<<'\n';
173 cout<<"評(píng)價(jià)完成!\n";
174 cout<<"開(kāi)始保存定標(biāo)結(jié)果………………";
175 Matrix<double> rotation_vector(3,1);
176 Matrix<double> rotation_matrix(3,3);
177 fout<<"相機(jī)內(nèi)參數(shù)矩陣:\n";
178 fout<<intrinsic_matrix<<'\n';
179 fout<<"畸變系數(shù):\n";
180 fout<<distortion_coeffs<<'\n';
181 for (i=0;i<image_count;i++) {
182 fout<<"第"<<i+1<<"幅圖像的旋轉(zhuǎn)向量:\n";
183 fout<<rotation_vectors.get_col(i);
184 for (j=0;j<3;j++)
185 {
186 rotation_vector(j,0,0) = rotation_vectors(0,i,j);
187 }
188 cvRodrigues2(rotation_vector.cvmat,rotation_matrix.cvmat);
189 fout<<"第"<<i+1<<"幅圖像的旋轉(zhuǎn)矩陣:\n";
190 fout<<rotation_matrix;
191 fout<<"第"<<i+1<<"幅圖像的平移向量:\n";
192 fout<<translation_vectors.get_col(i)<<'\n';
193 }
194 cout<<"完成保存\n";
195 }
這段程序可以直接運(yùn)行,本人是在vs2010+OpenCV2.4.0環(huán)境下,配置在前面文章中有介紹。標(biāo)定板圖像序列名稱(chēng)放在文件"calibdata.txt"中,每行一幅圖像名,路勁問(wèn)題這里就不論述了。如
image/chess1.jpg
image/chess2.jpg
……
最后標(biāo)定結(jié)果存儲(chǔ)在"calibration_result.txt"中,命令行窗口也會(huì)顯示一些信息。觀察結(jié)果會(huì)發(fā)現(xiàn),攝像機(jī)內(nèi)參數(shù)矩陣M1和畸變系數(shù)k1,k2,p1,p2是直接調(diào)用OpenCV中標(biāo)定函數(shù)求解出的,以及每幅圖像相對(duì)于對(duì)應(yīng)攝像機(jī)成像平面的旋轉(zhuǎn)矩陣R'(或旋轉(zhuǎn)向量)和平移向量t',而不是世界坐標(biāo)系相對(duì)于攝像機(jī)坐標(biāo)系,也不是兩部攝像機(jī)之間的空間關(guān)系。而對(duì)于外參[R t],一直都是很抽象的問(wèn)題。
在重建的過(guò)程中,主要需要兩個(gè)元素:
1、攝像機(jī)內(nèi)M1、外參數(shù)M2(或者說(shuō)是投影矩陣M=M1*M2)
2、左右成像平面上的匹配特征點(diǎn)對(duì)
最后通過(guò)視差原理計(jì)算出物體上特征點(diǎn)對(duì)應(yīng)的空間點(diǎn)坐標(biāo),得到最終點(diǎn)云。
當(dāng)然,有興趣的可以繼續(xù)學(xué)習(xí)、研究下去,外參可以通過(guò)圖像上的點(diǎn)和預(yù)先得到的世界坐標(biāo)系下的物體上的空間點(diǎn)坐標(biāo),利用一系列數(shù)學(xué)關(guān)系計(jì)算。
在實(shí)際操作中,可以在重建過(guò)程進(jìn)行自標(biāo)定,即不用分別進(jìn)行標(biāo)定求得攝像機(jī)參數(shù),而是直接通過(guò)特征點(diǎn)對(duì)計(jì)算投影矩陣,后面步驟同上。當(dāng)然自標(biāo)定得到的結(jié)果精度不是很高,目前較多的是上面所述的張正友標(biāo)定法,介于傳統(tǒng)與自標(biāo)定之間。
不要讓青春留下太多遺憾,專(zhuān)注
總結(jié)
以上是生活随笔為你收集整理的初学OpenCV之摄像机标定的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 信用卡微信支付算刷卡吗?一文带你了解信用
- 下一篇: pywinauto客户端自动化---模拟