OpenCVカメラのキャリブレーション

最近は両眼立体視の方向性が決まり、主に再構成の研究を行っています。一般的なプロセスは、画像取得 -> カメラ キャリブレーション -> 特徴抽出 -> マッチング -> 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開発学習教材や技術交流を受け取りましょう↓↓↓↓↓↓ 

おすすめ

転載: blog.csdn.net/hw5230/article/details/130266338