OpenCV calibrateCamera関数のプロになる
カメラ画像から物体の位置や姿勢を認識するタスクは、古くからコンピュータビジョンの分野で研究されている話題です。 カメラは三次元の情報を二次元の画像に写し込むので、奥行方向の情報は欠けてしまうのですが、左右上下のどの方向に物体があるのかという情報は読み取ることができます。 この情報をうまく活用してやることで、自動運転のための物体認識やAR/MRで使うリアルタイムなCG合成など、様々なアプリケーションが実現できます。
こうした物体の位置検出をするためには、「カメラから見てこの方向にある物体はカメラ画像上のここに映り込む」という対応関係を事前に知っておく必要があります。 calibrateCamera 関数は、この対応関係を較正するために利用されます。
2. OpenCVのカメラモデル
カメラを較正するための準備として、まず物体がカメラに映り込む様子を数式にモデル化する必要があります。 このモデルは、カメラモデルと呼ばれます。
2.1. 定義- カメラをピンホールカメラとして考える(ピントは、見たい範囲全体をまんべんなくボケずに撮影できるような位置に固定されていると仮定する)
- カメラの光学中心(ピンホールカメラの穴の位置・画角の基点)を原点とし、右をX、下をY、前をZとする右手座標系を考える
- カメラ座標系で見て にあるオブジェクトは、以下の式で求められる画素 に映り込むものとする
はカメラの内部パラメータ(intrinsic parameters)と呼ばれます。 特に は歪みパラメータ(distortion parameters)と呼ばれます。 また、伝統的に
をカメラ行列(camera matrix)と呼びます(歪みを考慮しない場合の計算式を整理するとこの行列が出てくるため)。 上のモデルで計算する場合、実際にこの行列を使うことはないですが、呼び方として覚えておきましょう。
補足魚眼レンズと呼ばれる広角レンズを搭載したカメラの場合、上のモデルでは画像の輪郭に近い部分の歪みは正確に表現できません。 広角カメラを使う場合は、 cv::fisheye という別のモジュールが用意されているので、そちらを利用するほうがよいです。
また、上の図で と定義していた画像平面をあえて傾かせる、ティルト機能付きカメラというものも存在します。 詳細は「シャインプルーフの原理」で検索してみてください。 calibrateCamera 関数はこうしたティルト機能付きカメラのキャリブレーションにも対応しており、ティルトの影響を歪みパラメータ として算出できます。 ただ、このあたりの式を書くと紙面が長くなるので今回は省略します。
2.2. 解説 透視投影変換まず、ピンホールカメラモデルに従って、透視投影(perspective projection)という遠近法を考慮した変換を行います。 これを簡単に可視化したのが先ほどの図です。
図中の の位置に画像平面があると仮定します。 すると、オブジェクトがある点 とカメラ原点を結んだときに交差する点 にオブジェクトが写り込む、という風に考えることができます。
ビューポート変換次に、 の画像平面上の二次元座標 と画像に切り出したときのピクセル座標 の対応関係を考えます。 平たく言えば、二次元平面の座標値をピクセル値に直す処理をするのです。
という風に定式化できます。 実際の画像に写り込むのは、変換後の が0〜画像の幅・高さの範囲に収まる領域です。
ここで出てきた はスキュー(skew)と呼ばれるパラメータです。 この値が大きくなることは「四角形を撮影したとき平行四辺形に歪んだものが撮影される」という状態を意味するのですが、最近のカメラ素子はこの種の歪みをほとんど生じないため、OpenCVでは0で固定されています。
はカメラの画角と画像の解像度(サイズ)を反映したパラメータで、焦点距離と呼ばれます。 数式的には
f_x(pix) = レンズの焦点距離(m) × 画像の幅(pix) / 撮像素子の幅(m) f_y(pix) = レンズの焦点距離(m) × 画像の高さ(pix) / 撮像素子の高さ(m)はカメラの光軸を通ってきた光が写り込むピクセルの座標です。 おおよそ画像の中心あたりの座標になります。
各種画像歪みの考慮さて、ここまでの式で理想的なピンホールカメラをモデル化することはできたのですが、現実のカメラはレンズ収差や組み付け精度が原因で画像に歪み(distortion)が生じます。 この歪みを考慮するため、画像平面上で理想的な座標 から歪んだ座標 への射影を考えます。
第一項は放射状歪み(radial distortion)、第二項・第三項は接線歪み(tangential distortion)、第四項・第五項は薄プリズム歪み(thin prism distortion)と呼ばれます。
3. calibrateCamera関数の内部実装
3.1. 問題の定式化OpenCVでは、以下のZhangの手法を拡張したものが実装されています。
- Zhengyou Zhang. A flexible new technique for camera calibration. Pattern Analysis and Machine Intelligence, IEEE Transactions on, 22(11):1330–1334, 2000.
- 被引用数が5桁を数える大ヒット論文です
- 実は上で紹介した放射状歪みのモデル化もこの論文で提案されたものです
Zhangの手法の基本的な考え方は、回帰などで利用する最小二乗法による関数フィッティングと同じです。 すなわち、三次元座標 とピクセル座標 のペアをたくさん集めて、「 から計算されるピクセル座標」と「実際に計測されたピクセル座標 」の誤差(再投影誤差)がなるべく小さくなるようなパラメータを探します。
ちなみに、こうした再投影誤差の最小化によって知りたいパラメータを求める手法はBundle Adjustmentと呼ばれており、コンピュータビジョンの分野ではメジャーな計算手法の1つになっています。
カメラモデルに従ってピクセル座標を計算する関数を 、カメラパラメータをまとめて として、Bundle Adjustmentを数式で表すと以下のようになります。
つまり座標 が既知であるマーカーを複数用意し、カメラで撮影したときのピクセル座標 を計算し、この最適化問題を解くことでカメラパラメータを求めることができます。
しかし、ここで問題になるのは、どうやってカメラ座標系における三次元座標 を計算するかという点です。 いちいちカメラからの距離を定規で測っていては手間がかかりますし、誤差も大きくなります。
そこでZhangの方法では、相対座標(モデル座標)が既知であるマーカー群を用意し、それをカメラの前で姿勢を変えながら何度か撮影する、という手法を取ります。 そして、最適化問題で求めるべきパラメータの中に「各フレームにおけるモデル座標とカメラ座標系の幾何関係(外部パラメータ : extrinsic parameters)」を加えてしまいます。
モデル座標系におけるマーカー座標を 、 番目のフレームにおけるモデル座標系とカメラ座標系の間の回転・並進成分を として、数式で表すと以下のようになります。
3.2. 問題の解法二乗和の最小化(Least-Squares Problems)はよく利用される最適化問題で、過去に効率的なアルゴリズムが提案されています。 OpenCVではその一つであるLevenberg-Marquardt法を利用しています。 LM法の詳細はこちらの記事で解説しているので、興味があれば読んでみてください。
ただし、LM法は非常に有効な手法なのですが、一般に数値計算の反復解法は局所最適解のみを探索するので、正しくないパラメータに収束することがあります。 正しいパラメータに収束させるためには、それなりに正解に近い初期解を与えてやる必要があります。
calibrateCamera 関数の内部でも、最初に初期解を推定してからLM法を解いています。 外部パラメータの初期解は、モデル座標から画素平面へのホモグラフィーを考えることで計算しています。 ただし、ホモグラフィーを計算するためにはチェッカーボードのようにマーカーが平面上に配置されていることが条件になります。 非平面に配置されたマーカーを使うときはこの方法が使えないので、 calibrateCamera 関数を呼ぶ際に内部パラメータの初期解を与える必要があります。 この場合、外部パラメータの初期解はいったん内部パラメータを初期解で固定した上でLM法を解いて求められます。
4. calibrateCamera関数の使い方
double cv::calibrateCamera( InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, Size imageSize, InputOutputArray cameraMatrix, InputOutputArray distCoeffs, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, OutputArray stdDeviationsIntrinsics, OutputArray stdDeviationsExtrinsics, OutputArray perViewErrors, int flags = 0, TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, DBL_EPSILON) ) retval, cameraMatrix, distCoeffs, rvecs, tvecs = cv2.calibrateCamera( objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs[, rvecs[, tvecs[, flags[, criteria ]]]]) retval, cameraMatrix, distCoeffs, rvecs, tvecs, stdDeviationsIntrinsics, stdDeviationsExtrinsics, perViewErrors = cv2.calibrateCameraExtended( objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs[, rvecs[, tvecs[, stdDeviationsIntrinsics[, stdDeviationsExtrinsics[, perViewErrors[, flags[, criteria ]]]]]]])- C/C++版の戻り値・Python版の戻り値 retval
- 最小化問題を解いた際の最終的な再投影誤差のRMS(Root Mean Square)
- 単位はピクセル
- これが小さいほど、LM法が誤差なく収束していることになる
- モデル座標系の姿勢ごとに、モデル座標系におけるマーカー座標のリストをリストにしたもの
- C/C++版では std::vector> に準ずる型
- Python版では [np.array([(x1, y1, z1), (x2, y2, z2), . ], np.float32)] に準ずる型
- Point3d 型や float64 型はダメ
- [[np.array((x, y), np.float32)]] はダメ
- モデル座標系の姿勢ごとに、マーカーが写り込んだピクセル座標のリストをリストにしたもの
- C/C++版では std::vector> に準ずる型
- Python版では [np.array([(x1, y1), (x2, y2), . ], np.float32)] に準ずる型
- Point2d 型や float64 型はダメ
- [[np.array((x, y), np.float32)]] はダメ
- カメラの解像度(ピクセル単位)
- の初期解を求めるときに使うだけなので cameraMatrix の初期解を与えるときは指定しなくてもよい
- を含むカメラ行列
- 非平面に配置されたマーカーを使うときは初期解の指定が必須
- \begin f _ x & 0 & c _ x \\ 0 & f _ y & c _ y \\ 0 & 0 & 1 \end
- 歪みパラメータのリスト
- flags をどう指定するかによって長さが変わる(デフォルトは5)
- モデル座標系の姿勢ごとに求めた、モデル座標系からカメラ座標系への回転ベクトルと並進ベクトル
- 回転ベクトルは cv::Rodrigues() で回転行列と相互変換できる
- 回転行列 と並進ベクトル を使うと、モデル座標系の座標 をカメラ座標系の座標 に変換できる
- 長さの単位は、モデル座標系におけるマーカー座標を記述したときと同じ単位
- 三次元座標の回転表現についてはこちらの過去記事にまとめています(回転ベクトル・回転行列・クォータニオン・オイラー角についてまとめてみた - かみのメモ)
- LM法の収束判定に使うパラメータ
- 明らかに反復回数が足りていないなどのケースを除いてデフォルトのままでよい
- 列挙体は cv::CALIB_* の形で定義されている
- 例:高次の放射状歪みを推定するとき
- CALIB_RATIONAL_MODEL
- CALIB_ZERO_TANGENT_DIST | CALIB_FIX_K1 | CALIB_FIX_K2 | CALIB_FIX_K3
- distCoeffs に何も指定しないと0のまま固定される
- CALIB_USE_INTRINSIC_GUESS
- cameraMatrix に3×3行列を渡す
5. 結果の見方
5.1. キャリブレーション結果の例参考までに、とあるカメラのキャリブレーション結果を添付しておきます。 撮影画像は5枚で、 で固定しています。
Image Size : (1024, 1280) RMS : 0.3162601179834329 Intrinsic parameters : [[3.45238543e+03 0.00000000e+00 5.87300567e+02] [0.00000000e+00 3.44991446e+03 5.21409899e+02] [0.00000000e+00 0.00000000e+00 1.00000000e+00]] Distortion parameters : [[-2.23845891e-01 -7.32573873e-01 -7.30350307e-04 -1.68303803e-03 0.00000000e+00]] Rotation vector / Translation vector 0 [[-0.14270636 -0.05149731 -1.55891318]] [[-3.85846485 2.3775021 48.44265457]] Rotation vector / Translation vector 1 [[0.65924161 0.87410419 1.48192946]] [[ 2.98064258 -3.67776322 39.63023961]] Rotation vector / Translation vector 2 [[ 0.37713801 -0.56964512 -1.53005519]] [[-4.8628964 2.10324681 39.70077357]] Rotation vector / Translation vector 3 [[-0.30036312 -0.22436363 -1.52070959]] [[-2.19763672 2.38379239 46.23177521]] Rotation vector / Translation vector 4 [[-0.13302378 0.47900357 0.03471266]] [[-0.27348193 -5.69902391 47.07153337]] 5.2. RMSを確認する一番わかりやすいのはRMSです。 先ほども説明したように、RMSは最適化問題の最終的な誤差をピクセル単位で表現したものです。 これが数ピクセル以下ならとりあえずLM法自体は正常に収束していると言っていいです(ただし正しい解に収束したとは限らない)。
5.3. カメラ行列を確認する次にわかりやすいのは焦点距離 と中心座標 です。 が大体同じ値を取っていて、 が画像の中心座標に近ければOKです(光軸が斜めを向いているようなカメラを除く)。
5.4. 歪みパラメータを確認する歪みパラメータの確認は難しいのですが、目安として絶対値が10を超えているときは疑ってかかったほうがよいでしょう。 とはいえ、 が負で が正であるために互いの歪みが相殺されて最終的な見た目はそれっぽくなっているというケースもあります。
5.5. 回転・並進ベクトルを確認する回転・並進ベクトルも検証が難しいですが、慣れてくると何となくそれらしい値になっているかを確認できるようになります。 わかりやすい部分としては、並進ベクトルのz成分がカメラからオブジェクトまでのおおよその距離を表しているので、それっぽい値になっているか確認してみましょう。 長さの単位は、モデル座標系におけるマーカー座標を記述したときと同じ単位になります。
6. 良い結果が出なかったときの試行錯誤
- チェッカーボードが曲がっていないか/正しいものを使っているか確認する
- チェッカーボードを使う場合、厳密に平面であるという条件がかなり重要になってきます。アクリル板など丈夫な板に貼り付けましょう。PC画面に表示させたものを使ってもいいです。
- また気付きにくい仕様ですが、OpenCVの findChessboardCorners() はチェッカーパターンの周りに白色の余白があることを前提としています。余白があるか確認しましょう。
- 画像がきれいに撮影できているか確認する
- 手ブレや蛍光灯の反射光の映り込みがあるとパターンをうまく検出できないことがあります。
- 焦点ボケのせいでチェッカーパターンがボケてしまっている場合は、カメラの絞り(F値)を絞ってみるか、ボケに強いドットパターンの使用を検討してみましょう。
- データ数(撮影枚数)を増やす
- Zhangの論文では5枚だけですが、OpenCVの実装では推定するパラメータが増えているので、最低でも10枚、余裕があれば15枚以上は撮影しておくのがよいと思います。
- 同じ姿勢ばかり撮影してもあまり意味がないので色々な姿勢を試しましょう。
- 初期解を与える
- それらしい初期解を与えると、結果が劇的に改善されることもあります。
- 特に影響が大きいのはカメラ行列の初期解です。 は画像の中心のピクセル座標を計算するだけですし、 もレンズの焦点距離、撮像素子のサイズをメーカーの説明書で調べれば先ほどの計算式で簡単に割り出せます。
- CALIB_USE_INTRINSIC_GUESS フラグを立てるのを忘れずに。
- 歪みパラメータを減らす
- 多くの歪みパラメータを使用する設定にしていると、うまく収束しないことがあります。一度思い切って使用する歪みパラメータを減らしてみましょう。
以上、 calibrateCamera 関数周りの話をまとめました。