最近は両眼立体視の方向性が決まり、主に再構成の研究を行っています。一般的なプロセスは、画像取得 -> カメラ キャリブレーション -> 特徴抽出 -> マッチング -> 3 次元再構成です。もちろん、最初に画像の前処理と補正を実行し、後でさらに点群処理を実行することもできます。サーフェスをレンダリングして実際のオブジェクトに近づけます。
画像の取得は比較的簡単で、カメラを使用してターゲット オブジェクト (大きなシーンや屋内の特定の小さなオブジェクト) をキャプチャします。ただし、注意すべき点が 2 つあります。
1. 両眼再構成に必要な画像は通常 2 枚であり、角度の差が大きすぎてはなりません。そうでない場合、共通部分が少なすぎて再構成効果が低くなります。プロセス全体が簡単でコストも高くありません。ただし、欠点は、画像が 2 つしかないことです。点群によって表されるオブジェクト情報は包括的ではありません。
2. キャリブレーションに必要な画像は別々に撮影されます。張正佑のキャリブレーション方法を使用する場合、白黒の市松模様の画像をボール紙に貼り付け、左右のカメラを別々に撮影します。理論的には、より多くの角度 (画像) を撮影できます。取得できる角度 (画像) が増えるほど、最終的なキャリブレーション結果がより正確になります。キャリブレーション プレートは次の図に示されています。
ここでは主にOpenCVをベースにした左右カメラのキャリブレーションについて簡単に紹介しますので、友達に添削してもらいながらコミュニケーションをとりながら一緒に進めていただければ幸いです。
カメラ キャリブレーション問題は、マシン ビジョンの分野における初歩的な問題であり、従来のカメラ キャリブレーション方法とカメラの自己キャリブレーション方法に分けることができます。多くの校正方法がありますが、最も一般的なものは、Tsai (従来型) および Zhang Zhengyou (従来型と自己校正の間) などです。
カメラ画像モデルと 4 つの座標系 (一般原則)。
カメラモデルは古典的なピンホールモデルを採用しており、図Oc(光学中心)に示すように、像面πは視野平面を表し、光学中心までの距離はf(レンズ焦点距離)です。
4 つの座標系は次のとおりです: 世界座標系 (Ow)、カメラ座標系 (Oc)、画像物理座標系 (O1、単位 mm)、画像ピクセル座標系 (O、視野平面の左上隅に位置します) 、単位ピクセル)。
空間上のある点 P からその像点 p への座標変換処理は、主にこれら 4 組の座標系の 3 回の変換によって実現されます。まず世界座標系を平行移動変換してカメラ座標系を求め、次に画像の物理座標は三角幾何学変換システムに従って取得され、最後に画像のピクセル座標系はピクセルとメートル単位の比率に基づいて取得されます。(実際の適用プロセスはこれの逆プロセスです。つまり、実際の長さはピクセル長からわかります)。
ps: カメラのキャリブレーションにより、視野面上の mm/pix 解像度は得られますが、視野面外のオブジェクトについては、座標変換によって視野面を取得する必要があります。
変換プロセスと計算式については、カメラのキャリブレーション原理 (キーは 3 つの座標系).ppt を参照してください。
2 張正佑のアルゴリズムの原則
zhang 法では、ターゲット プレートの動きを知ることなく、特定のターゲット プレートの完全な写真を異なる方向から複数回 (3 回以上) 撮影します。カメラの内部パラメータ(参照行列A)と歪み係数を直接取得します。この校正方法は自己校正方法よりも正確であり、高精度の位置決め機器を必要としません。
ZHANG のアルゴリズムには 2 つのモデルが含まれています: 1. 4 つの座標系を含む古典的なピンホール モデル、および 2. 歪みモデル (このソースは不明)
式の 3 つの項は、半径方向の歪み、接線方向の歪み、薄いプリズムの歪みを順に表します。OPENCV の関数は、k1、k2、p1、p2 のみを与えることができます。
別の歪みモデルもあります。記事「カメラ キャリブレーション アルゴリズム ライブラリの設計と実験的検証」の 26 ページを参照してください。
Zhang Zhengyou は matlab のツールボックス TOOLBOX_CAL と OpenCV ライブラリを調整しました。以下は OpenCV と組み合わせた C++ のコードです。
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 //****************开始提取角点***********************//
12 cout<<"开始提取角点………………";
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 将鼠标焦点移到标定图像所在窗口"
23 <<"并输入回车进行下一幅图像的角点提取 \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 }//角点提取循环
54 delete []image_points_buf;
55 cout<<"角点提取完成!\n"<<endl;
56 cout<<"开始定标………………"<<"\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<<"定标完成!\n";
101 cout<<"标定结果显示\n";
102 cout<<"***************************************\n";
103 cout<<"相机内参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畸变系数: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<<"开始评价定标结果………………\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每幅图像的定标误差:\n";
137 fout<<"每幅图像的定标误差:\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<<"评价完成!\n";
174 cout<<"开始保存定标结果………………";
175 Matrix<double> rotation_vector(3,1);
176 Matrix<double> rotation_matrix(3,3);
177 fout<<"相机内参数矩阵:\n";
178 fout<<intrinsic_matrix<<'\n';
179 fout<<"畸变系数:\n";
180 fout<<distortion_coeffs<<'\n';
181 for (i=0;i<image_count;i++) {
182 fout<<"第"<<i+1<<"幅图像的旋转向量:\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<<"幅图像的旋转矩阵:\n";
190 fout<<rotation_matrix;
191 fout<<"第"<<i+1<<"幅图像的平移向量:\n";
192 fout<<translation_vectors.get_col(i)<<'\n';
193 }
194 cout<<"完成保存\n";
195 }
このプログラムは直接実行できますが、vs2010+OpenCV2.4.0環境で実行しています、構成は前回の記事で紹介しています。キャリブレーション プレート イメージ シーケンスの名前は、1 行に 1 つのイメージ名でファイル「calibdata.txt」に配置されます。パスの強度の問題についてはここでは説明しません。のように
画像/チェス1.jpg
画像/チェス2.jpg
……
最終的なキャリブレーション結果は「calibration_result.txt」に保存され、コマンド ライン ウィンドウにもいくつかの情報が表示されます。結果を観察すると、カメラの内部パラメータ行列 M1 と歪み係数 k1、k2、p1、p2 は、OpenCV のキャリブレーション関数を直接呼び出すことによって解決され、それぞれの回転行列 R' (または回転ベクトル) が解かれることがわかります。対応するカメラの撮像面を基準とした画像 ) と並進ベクトル t' であり、カメラ座標系を基準とした世界座標系や 2 台のカメラ間の空間関係ではありません。外部パラメータ [R t] に関しては、常に非常に抽象的な問題でした。
再構築プロセス中には、次の 2 つの主要な要素が必要になります。
1. 内部カメラ M1、外部パラメータ M2 (または射影行列 M=M1*M2)
2. 左右の撮像面上の特徴点ペアのマッチング
最後に、物体上の特徴点に対応する空間点座標が視差の原理によって計算され、最終的な点群が得られます。
もちろん、興味がある人は学習や研究を続けることができ、外部パラメータは、画像上の点と、事前に取得した世界座標系内のオブジェクト上の空間点座標を介した一連の数学的関係を使用して計算できます。
実際の操作では、再構成プロセス中にセルフキャリブレーションを実行できます。つまり、カメラパラメータを取得するために別途キャリブレーションを行う必要はなく、特徴点ペアを通じて射影行列を直接計算します。以下の手順は上記と同じです。もちろん、自己校正で得られる結果の精度はそれほど高くありませんが、現在は、従来の校正と自己校正の中間の、前述の張正佑校正方法がよく使用されています。
以下のグループに参加して、Qt開発学習教材や技術交流を受け取りましょう↓↓↓↓↓↓