(17/04/10)プログラム全体を上げました.

ここ数日ブログが見れなくなっていたようで,すみません.OpenCVで魚眼レンズのキャリブレーションを行ったんですが,なかなか手こずったので備忘録を残しておきます.

基本的なキャリブレーションの手順はピンホールカメラのキャリブレーションと同じで,1)画像上のマーカーパターン検出,2)対応する3Dマーカー座標用意,3)キャリブレーション関数にそれらを渡す,という感じで,基本的には3番目で使う関数をcv::fisheye::calibrate()を変えればOKです.ただ,cv::fisheye::calibrate()がどうにも不安定で,原因を調べていました.

結論から言うと,1)ヤコビアンの逆行列を求める際の精度が足りていなかった,2)カメラ行列とレンズ歪みパラメータの同時推定が不安定だった,というのが原因だったようです.

1) calib3d/src/fisheye.cppのソースを追っかけていくと,処理としては誤差関数のヤコビアンを計算して勾配を下っていっているような感じだったのですが,ヤコビアンから求めたパラメータの変更量がゼロになっていました(fisheye.cpp L.768).さらに追いかけると,ComputeJacobians()内の1437行目の JJ2_inv = JJ3.inv(); でJJ2_invでは値が全てゼロになってしまっていました.逆行列計算の精度が足りなかったようです.OpenCVに用意されている逆行列計算方式をいくつか試しても,精度が足りない or 非常に遅い,だったため,以下のようにEigenのFullPivLUでの逆行列計算に変更しました.

// 精度が足りない
// JJ2_inv = JJ3.inv(DECOMP_SVD);

// EigenのFullPivLUを使って逆行列を求める
Eigen::MatrixXd jj3 = Eigen::Map<Eigen::MatrixXd>(reinterpret_cast<double*>(JJ3.data), JJ3.rows, JJ3.cols);
Eigen::MatrixXd jj2_inv = jj3.fullPivLu().inverse();

JJ2_inv.create(jj2_inv.rows(), jj2_inv.cols(), CV_64FC1);
for (int i = 0; i < jj2_inv.rows(); i++) {
	for (int j = 0; j < jj2_inv.cols(); j++) {
		JJ2_inv.at<double>(i, j) = jj2_inv(i, j);
	}
}

2) それでもまだ正しいキャリブレーション結果が得られなかったので,一度,歪みパラメータは固定としてカメラ行列を求めておいて,それを初期値として再度,歪みを含めた推定を行うようにしました.下記コード内のcv::fisheye::calibrate_()は逆行列処理を変更して新たに作った関数です(最後にコードを置いておきます).

cv::Mat intrinsic, distortion;
std::vector<cv::Mat> rvecs, tvecs;

// レンズ歪み無しでカメラ行列のみを一度推定
int flags = cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC | cv::fisheye::CALIB_FIX_SKEW;
flags |= cv::fisheye::CALIB_FIX_K1 | cv::fisheye::CALIB_FIX_K2 | cv::fisheye::CALIB_FIX_K3 | cv::fisheye::CALIB_FIX_K4;
cv::fisheye::calibrate_(object_points, corners, cv::Size(640, 480), intrinsic, distortion, rvecs, tvecs, flags, cv::TermCriteria(cv::TermCriteria::MAX_ITER + cv::TermCriteria::EPS, 100, 1e-12));

// 上の推定結果を初期値として歪み有りでもう一度推定
flags = cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC | cv::fisheye::CALIB_USE_INTRINSIC_GUESS | cv::fisheye::CALIB_FIX_SKEW;
cv::fisheye::calibrate_(object_points, corners, cv::Size(640, 480), intrinsic, distortion, rvecs, tvecs, flags, cv::TermCriteria(cv::TermCriteria::MAX_ITER + cv::TermCriteria::EPS, 100, 1e-12));

これでキャリブレーション自体は出来たのですが,さらにさらにcv::fisheye::undistortImage()もおかしかったので,このページから引っ張ってきたコードでrectificationを行いました.

[src](キャリブレーション部分のみ,Win7 VC12 OpenCV2.4.10 + Eigenで動作確認)

src.zip内のfisheye.hppをインクルードして,上のコード例みたいにcv::fisheye::calibrate_()を呼ぶと(少なくとも私の環境では)魚眼レンズのキャリブレーションができました.下の画像のようにパラメータの変更分(delta)と再投影誤差(err)を表示するようになっているので,問題点の特定が少しだけ容易になるかなと思います.

なんだか,疲れました.ちょっと片手間に魚眼レンズで遊んでみるか!と思って始めたらドツボにはまってかなり時間を削られました.cv::fisheye周りはどうにも不安定で,しかも情報が少ないので,結局ソースを追いかけてくはめになってしまいました.キャリブレーション自体はなんとかできて良かったです.

1件のコメント

  1. […] 前々回の記事でOpenCVのfisheye::calibrate()関数をちょこちょこ変えたものを貼っていましたが,あとで必要になりそうな気がするので,キャリブレーションプログラム全体のソースを置いて […]

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です