About BBS

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

作成者: 怡土順一, 最終変更者: 怡土順一, 最終変更リビジョン: 470, 最終変更日時: 2009-07-01 14:01:51 +0900 (水, 01 7月 2009)

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

カメラキャリブレーションとは,(ある時点において)カメラ固有の内部パラメータと, ワールド座標系における位置姿勢を意味する外部パラメータを求める処理である. カメラのキャリブレーションがなされると,ある3次元座標を持った点がカメラ画像のどこに投影されるか, あるいは複数のカメラに投影された点が3次元空間中のどこにあるか,などが計算できる. また,カメラ特有の(円周方向および半径方向)歪みの補正を行うこともでき る(が,これはキャリブレーション手法に依存し,歪みを持たないモデルを利用する単純な手法も存在する). 適応できるキャリブレーション手法は,カメラの台数や用意できる治具によって変化する. OpenCVのカメラキャリブレーションは,Z.Zhangの手法を基に実装されている (Zhangの手法については,"A flexible new technique for camera calibration". IEEE Transactions on Pattern Analysis and Machine Intelligence, 22(11):1330-1334, 2000.を参照されたい). キャリブレーション関数 Ver.2 から(cvCalibrateCamera2など)は,Matlabのキャリブレーションエンジン(これもZhangの手法)を移植したものになった.

サンプル


カメラキャリブレーション cvCalibrateCamera2, cvFindExtrinsicCameraParams2

Zhangの手法を用いてカメラのキャリブレーションを行い,結果をファイルに保存する

サンプルコード

#include <stdio.h> #include <cv.h> #include <highgui.h> #define IMAGE_NUM (25) /* 画像数 */ #define PAT_ROW (7) /* パターンの行数 */ #define PAT_COL (10) /* パターンの列数 */ #define PAT_SIZE (PAT_ROW*PAT_COL) #define ALL_POINTS (IMAGE_NUM*PAT_SIZE) #define CHESS_SIZE (24.0) /* パターン1マスの1辺サイズ[mm] */ int main (int argc, char *argv[]) { int i, j, k; int corner_count, found; int p_count[IMAGE_NUM]; IplImage *src_img[IMAGE_NUM]; CvSize pattern_size = cvSize (PAT_COL, PAT_ROW); CvPoint3D32f objects[ALL_POINTS]; CvPoint2D32f *corners = (CvPoint2D32f *) cvAlloc (sizeof (CvPoint2D32f) * ALL_POINTS); CvMat object_points; CvMat image_points; CvMat point_counts; CvMat *intrinsic = cvCreateMat (3, 3, CV_32FC1); CvMat *rotation = cvCreateMat (1, 3, CV_32FC1); CvMat *translation = cvCreateMat (1, 3, CV_32FC1); CvMat *distortion = cvCreateMat (1, 4, CV_32FC1); // (1)キャリブレーション画像の読み込み for (i = 0; i < IMAGE_NUM; i++) { char buf[32]; sprintf (buf, "calib_img/%02d.png", i); if ((src_img[i] = cvLoadImage (buf, CV_LOAD_IMAGE_COLOR)) == NULL) { fprintf (stderr, "cannot load image file : %s\n", buf); } } // (2)3次元空間座標の設定 for (i = 0; i < IMAGE_NUM; i++) { for (j = 0; j < PAT_ROW; j++) { for (k = 0; k < PAT_COL; k++) { objects[i * PAT_SIZE + j * PAT_COL + k].x = j * CHESS_SIZE; objects[i * PAT_SIZE + j * PAT_COL + k].y = k * CHESS_SIZE; objects[i * PAT_SIZE + j * PAT_COL + k].z = 0.0; } } } cvInitMatHeader (&object_points, ALL_POINTS, 3, CV_32FC1, objects); // (3)チェスボード(キャリブレーションパターン)のコーナー検出 int found_num = 0; cvNamedWindow ("Calibration", CV_WINDOW_AUTOSIZE); for (i = 0; i < IMAGE_NUM; i++) { found = cvFindChessboardCorners (src_img[i], pattern_size, &corners[i * PAT_SIZE], &corner_count); fprintf (stderr, "%02d...", i); if (found) { fprintf (stderr, "ok\n"); found_num++; } else { fprintf (stderr, "fail\n"); } // (4)コーナー位置をサブピクセル精度に修正,描画 IplImage *src_gray = cvCreateImage (cvGetSize (src_img[i]), IPL_DEPTH_8U, 1); cvCvtColor (src_img[i], src_gray, CV_BGR2GRAY); cvFindCornerSubPix (src_gray, &corners[i * PAT_SIZE], corner_count, cvSize (3, 3), cvSize (-1, -1), cvTermCriteria (CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.03)); cvDrawChessboardCorners (src_img[i], pattern_size, &corners[i * PAT_SIZE], corner_count, found); p_count[i] = corner_count; cvShowImage ("Calibration", src_img[i]); cvWaitKey (0); } cvDestroyWindow ("Calibration"); if (found_num != IMAGE_NUM) return -1; cvInitMatHeader (&image_points, ALL_POINTS, 1, CV_32FC2, corners); cvInitMatHeader (&point_counts, IMAGE_NUM, 1, CV_32SC1, p_count); // (5)内部パラメータ,歪み係数の推定 cvCalibrateCamera2 (&object_points, &image_points, &point_counts, cvSize (640, 480), intrinsic, distortion); // (6)外部パラメータの推定 CvMat sub_image_points, sub_object_points; int base = 0; cvGetRows (&image_points, &sub_image_points, base * PAT_SIZE, (base + 1) * PAT_SIZE); cvGetRows (&object_points, &sub_object_points, base * PAT_SIZE, (base + 1) * PAT_SIZE); cvFindExtrinsicCameraParams2 (&sub_object_points, &sub_image_points, intrinsic, distortion, rotation, translation); // (7)XMLファイルへの書き出し CvFileStorage *fs; fs = cvOpenFileStorage ("camera.xml", 0, CV_STORAGE_WRITE); cvWrite (fs, "intrinsic", intrinsic); cvWrite (fs, "rotation", rotation); cvWrite (fs, "translation", translation); cvWrite (fs, "distortion", distortion); cvReleaseFileStorage (&fs); for (i = 0; i < IMAGE_NUM; i++) { cvReleaseImage (&src_img[i]); } return 0; }

// (1)キャリブレーション画像の読み込み
キャリブレーションに必要な画像列を読み込む.ここでは,calib_imgディレクトリ以下に あらかじめ用意した,"00.png"〜"24.png" という名前の画像ファイルを利用する. ここは,画像ファイルを置いたディレクトリやファイル名に合わせて適宜変更すること.
このように,このプログラムはカメラから直接画像を取得するものではないことに注意する. 本プログラムでは,キャリブレーションを行いたいカメラでキャリブレーション用のチェスパターン画像を前もって撮影しておく必要がある. zhangのキャリブレーションに必要な画像枚数(ビューの数)は決まっていないが, 一般的には,ブレなどのない適切な画像を数十枚程度用意すると十分な精度が得られる. 利用する画像数やパターンの行数,列数などの値はプログラムの最初で定義されているので, これらのパラメータも各自の環境に合わせて適宜変更すること.

// (2)3次元空間座標の設定
今回は,1マスの長さが24.0[mm],内側のコーナー数が7×10のチェスボードパターンを利用した (実際のチェスボードパターンファイル:PDF). 後述する理由により,コーナー数は,奇数×偶数,あるいは偶数×奇数の組み合わせを用いるのが望ましい. xおよびyの値は,パターンのマスのサイズにあわせて値を代入する. ここで設定される軸方向は,カメラの外部パラメータに影響を与える. また,zはすべて0または1に設定する必要がある.

// (3)チェスボード(キャリブレーションパターン)のコーナー検出
関数cvFindChessboardCorners()により,チェスボードのコーナーを検出する. コーナーは左下から右上に向けて検出され,第2引数で設定されたサイズのコーナーが全て検出された場合には,0以外の値を返す.
ちなみに,本プログラムとはあまり関係ないが,2枚以上のキャリブレーションパターンを認識させたい場合は, (縦横の矩形数が)異なるタイプのキャリブレーションパターンを用意する, あるいは,一つのパターンを検出する度にそこを塗りつぶして再探索する,などの手法が考えられる.
OpenCVの関数cvFindChessboardCorners()が検出するコーナーの順番は,パターンの行と列の定義に依存する. 以下の画像チェスパターンを,サンプルプログラムのように7×10(7行10列)と定義すると, 検出コーナーの最初,つまりキャリブレーション結果のワールド座標原点は,画像の(A)の場所になる. また,同様の画像を10×7(10行7列)と定義すると,原点は(B)となる. 関数cvFindChessboardCorners()は,チェスパターンのコーナーを検出するために矩形の検出を行うが, 矩形(黒色)に斜めに隣接する矩形の数が1つのものがあれば(図中の左上と右上), その矩形が含むコーナーの最初の点にする.それが存在しない場合は,斜め隣接矩形数が2つのものを用いる. コーナー数が,奇数×偶数,あるいは偶数×奇数の組み合わせのチェスパターンならば, その最初のコーナーが一意に決定されるが,そうでない場合は,コーナーが一意に決定されず, チェスパターンの画像によっては検出されるコーナーの順番が異なってしまう可能性がある.

// (4)コーナー位置をサブピクセル精度に修正,描画
関数cvFindChessboardCorners()においても,0.5[pixel]単位でコーナーが検出されるが, さらに検出されたコーナーをサブピクセル精度で修正する. また,それらのコーナーを画像上に描画し,実際に表示する. すべてのコーナーが正しく検出された場合は内部で定義された7色で描画され,そうでない場合はすべてのコーナーが赤色で描画される. このサンプルコードでは,全ての画像(25枚)でコーナーが正しく検出されない場合は,プログラムが終了する.

// (5)内部パラメータ,歪み係数の推定
関数cvCalibrateCamera2()により,カメラの内部パラメータ,および歪み係数を推定する. 内部パラメータは,以下の式のAにあたる.

s\cdot{\bf m} = {\bf A}\cdot[{\bf R}|{\bf t}]\cdot{\bf M}

// (6)外部パラメータの推定
カメラの外部パラメータを推定する.これは,上述の式の[R|t]にあたる. つまり,三次元のあるワールド座標系原点から,カメラ座標系への変換を表すパラメータである. ワールド座標系原点からの回転および並進を表す外部パラメータを決定するためには,基準となるワールド座標系原点が必要となる. ここでは,base=0として0番目の画像("00.png")を指定しており,この画像 に写るパターンの3次元座標系((2)で与えられた)における外部パラメータが推定される. この際のワールド座標系の原点は,(3)の項で説明したように決定される. このサンプルプログラムでは,実行結果例にある画像の赤色の行の最初のコーナーが原点となる.

// (7)XMLファイルへの書き出し
求められた,内部パラメータ,外部パラメータ,歪み係数をファイルに書き出す. ここでは,拡張子によりXML形式を指定しているが,YAML形式で出力することも可能である.

実行結果例

カメラキャリブレーションに用いた画像の一部(順不同)の認識結果. 実際のキャリブレーション結果ファイル(XML). このキャリブレーション結果は,DFW-VL500(sony)に広角レンズVCL-00637S(sony)を取り付けたもので行った.


歪み補正 cvUndistort2

キャリブレーションデータを利用して,歪みを補正する

サンプルコード

#include <cv.h> #include <highgui.h> int main (int argc, char *argv[]) { IplImage *src_img, *dst_img; CvMat *intrinsic, *distortion; CvFileStorage *fs; CvFileNode *param; // (1)補正対象となる画像の読み込み if (argc < 2 || (src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_COLOR)) == 0) return -1; dst_img = cvCloneImage (src_img); // (2)パラメータファイルの読み込み fs = cvOpenFileStorage ("camera.xml", 0, CV_STORAGE_READ); param = cvGetFileNodeByName (fs, NULL, "intrinsic"); intrinsic = (CvMat *) cvRead (fs, param); param = cvGetFileNodeByName (fs, NULL, "distortion"); distortion = (CvMat *) cvRead (fs, param); cvReleaseFileStorage (&fs); // (3)歪み補正 cvUndistort2 (src_img, dst_img, intrinsic, distortion); // (4)画像を表示,キーが押されたときに終了 cvNamedWindow ("Distortion", CV_WINDOW_AUTOSIZE); cvShowImage ("Distortion", src_img); cvNamedWindow ("UnDistortion", CV_WINDOW_AUTOSIZE); cvShowImage ("UnDistortion", dst_img); cvWaitKey (0); cvDestroyWindow ("Distortion"); cvDestroyWindow ("UnDistortion"); cvReleaseImage (&src_img); cvReleaseImage (&dst_img); cvReleaseMat (&intrinsic); cvReleaseMat (&distortion); return 0; }

// (1)補正対象となる画像の読み込み
補正を行う画像を読み込む.もちろん,補正対象となる画像を撮影したカメラは,キャリブレーション済みであるとする.

// (2)パラメータファイルの読み込み
キャリブレーション結果を格納したパラメータファイルを読み込む. 歪み補正に必要なパラメータは,内部パラメータ,および歪み係数だけなので,これをファイルから読み込む.

// (3)歪み補正
関数cvUndisotort2()により,入力画像の歪み補正をおこなう.

// (4)画像を表示,キーが押されたときに終了
歪み補正前画像と歪み補正後画像を表示し,なにかキーが押されるまで待つ.

実行結果例

[左]歪み補正前. [右]歪み補正後.


マップを利用した歪み補正 cvInitUndistortMap

キャリブレーションデータを利用してマップを作成し,歪みを補正する

サンプルコード

#include <cv.h> #include <highgui.h> int main (int argc, char *argv[]) { IplImage *src_img, *dst_img; CvMat *intrinsic, *distortion; CvFileStorage *fs; CvFileNode *param; IplImage *mapx = cvCreateImage (cvSize (640, 480), IPL_DEPTH_32F, 1); IplImage *mapy = cvCreateImage (cvSize (640, 480), IPL_DEPTH_32F, 1); if (argc < 2 || (src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_COLOR)) == 0) return -1; dst_img = cvCloneImage (src_img); fs = cvOpenFileStorage ("camera.xml", 0, CV_STORAGE_READ); param = cvGetFileNodeByName (fs, NULL, "intrinsic"); intrinsic = (CvMat *) cvRead (fs, param); param = cvGetFileNodeByName (fs, NULL, "distortion"); distortion = (CvMat *) cvRead (fs, param); cvReleaseFileStorage (&fs); // (1)歪み補正のためのマップ初期化 cvInitUndistortMap (intrinsic, distortion, mapx, mapy); // (2)歪み補正 cvRemap (src_img, dst_img, mapx, mapy); cvNamedWindow ("Distortion", CV_WINDOW_AUTOSIZE); cvShowImage ("Distortion", src_img); cvNamedWindow ("UnDistortion", CV_WINDOW_AUTOSIZE); cvShowImage ("UnDistortion", dst_img); cvWaitKey (0); cvDestroyWindow ("Distortion"); cvDestroyWindow ("UnDistortion"); cvReleaseImage (&src_img); cvReleaseImage (&dst_img); cvReleaseImage (&mapx); cvReleaseImage (&mapy); cvReleaseMat (&intrinsic); cvReleaseMat (&distortion); return 0; }

// (1)歪み補正のためのマップ初期化
関数cvInitUndistortMap()を用いて,歪みを補正するためのマップ mapx,mapy の初期化を行う.

// (2)歪み補正
作成されたマップを用いて,画像の幾何変換(cvRemap)を行う. 関数cvUndistort2()を利用する場合と比べて,計算速度が向上する. しかし,一般的な幾何変換を行う関数を利用しているためか,以前のバージョンで実装されていた歪み補正関数に比べてパフォーマンスが劣る. もちろん,以前の関数 cvUnDistortInit,cvUnDistort も cvcompat.h 内で宣言されているが, 将来的に廃止される予定なので利用は推奨されない (またコメントによれば,これらの関数は,quite hackerish implementations である). 単純に歪み補正の速度だけを求めるならば, Yahoo!Groupsの投稿(Speeding up undistort)にもあるように,自分でLUTを作成すれば良い. その場合は,画像の補完を別途行う必要がある.

実行結果例

[左]歪み補正前. [右]歪み補正後.

OpenCV-1.0 リファレンス マニュアル
OpenCV-1.1pre リファレンス マニュアル
OpenCVサンプルコード


画素値の直接操作
部分画像のシャッフル
画像の連結
画像のコピー
画像形状の変形
タイリング
画像の反転
逆行列(擬似逆行列)の計算
色空間の写像
離散フーリエ変換
階層構造を持つ輪郭の座標取得
図形の描画
ポリゴンの描画
凸ポリゴンの描画
テキストの描画
IplImage構造体情報の保存
マップのシーケンスを保存
IplImage構造体情報の読み込み
マップのシーケンスを読み込む
K-means法によるクラスタリング
クラスタリングによる減色処理
エッジの検出
コーナーの検出
並進移動のためのピクセルサンプリング
回転移動のためのピクセルサンプリング
画像のサイズ変更
画像のアフィン変換(1)
画像のアフィン変換(2)
画像の透視投影変換
全方位画像の透視投影変換
モルフォロジー変換
平滑化
ユーザ定義フィルタ
境界線の作成
画像の二値化
画像の二値化(大津の手法)
画像ピラミッドの作成
画像ピラミッドを用いた画像の領域分割
平均値シフト法による画像のセグメント化
Watershedアルゴリズムによる画像の領域分割
輪郭の検出と描画
画像のモーメントを計算
ハフ変換による直線検出
ハフ変換による円検出
距離変換とその可視化
不要オブジェクトの除去
ヒストグラムの描画
ヒストグラム間の距離
二次元のヒストグラム
バックプロジェクションパッチ
ヒストグラムの均一化
テンプレートマッチング
形状のマッチング
点列を包含する矩形
輪郭領域の面積と輪郭の長さ
二つの矩形を包含する矩形
楕円のフィッティング
点列を包含する図形
動的背景更新による物体検出
snakeによる輪郭追跡(静止画)
オプティカルフロー1
オプティカルフロー2
オプティカルフロー3
Condensation
顔の検出
カメラキャリブレーション
歪み補正
マップを利用した歪み補正
サポートベクターマシン
画像の各ピクセル値を特徴ベクトルとしたSVMの学習
画像の各ピクセル値を特徴ベクトルとしたSVMによる物体検出
マウスイベントの取得
トラックバーの利用
カメラからの画像キャプチャ
動画としてファイルへ書き出す
ラベリング