前回のエントリまでで、カードの輪郭抽出までできたので、今回はその輪郭をもとにカードの傾きを求めることにする。
カードの傾きを求めることができれば、そのカードの上にARでモデルを表示するとき、カードの姿勢に追従することができる。
カードの傾きとは「2つのカードのそれぞれの4隅の頂点座標同士を対応付けたとき、の頂点座標の変換行列」といえる。
3Dポリゴンでいうところの透視変換行列ってやつですね。
OpenCvにそれっぽい関数があったので使うことにする。
カードの輪郭をもとに透視変換行列を求めるまでの手順は次のとおり
- カードの輪郭を求める(findContours関数の引数でCV_RETR_EXTERNALを使えば一番外側の輪郭だけ取れる)
- カードの輪郭を直線に近似する(approxPolyD)
- カードの頂点同士の変換行列を求める(findHomography)
この手順で得られた変換行列を、乗算することで、カードの傾きを合わせることができる。
というわけでコード
前回作った輪郭を求める関数はfind_card()としてセリ利しました。
// arg1 輪郭を現す頂点
// arg2 頂点を検出する元となった画像データ
// @note まずは実験として、色々な情報を出したりしてみよう
// @note point2fで本当にいいのだろうか
static void draw_approx_contour(vector<cv::Point>& contours, Mat& ipSrc, vector<Point2f>& oContours)
{
// approxPolyDP関数を使って、複雑な輪郭を任意のレベルに近似する
// arg1, 輪郭のMat表現。Matコンストラクタに輪郭を与えて生成できる
// このmatはCV_32SC2 または CV_32FC2でなければならない。
// arg2, 近似結果の輪郭(matに与えた頂点と同じ型で受け取らなければならない)
// arg3, 近似精度
// arg4, trueの場合、輪郭は閉じたものであることを示す
cv::Mat approx_mat(contours); // コンストラクタで変換してくれる
vector<Point> approxContours; // 近似結果の輪郭を格納する領域
double epsiron = 32.0f;
approxPolyDP(approx_mat, approxContours, epsiron, true);
printf("approx-contors size = %d\n", approxContours.size() );
// 近似した輪郭を描画する
Mat approxImage(ipSrc.size(), CV_8U, cv::Scalar(255));
int polyNum = 4;
Point* lpApproxContours = &approxContours[0];
polylines(approxImage, (const Point**)&lpApproxContours , &polyNum, 1, true, Scalar(0,0,0), 1 );
imwrite("approx.jpg", approxImage);
// ここで型キャストまで実装するのは理想的ではないが、
// approxContoursからoContoursへ転送する。
for(int i = 0; i < approxContours.size(); i++){
oContours.push_back(approxContours[i]);
}
}
int main()
{ find_card(src, contours);
vector<cv::Point2f> approx_contours, target_approx_contours;
draw_approx_contour( contours, src, approx_contours);
// 2つの画像間の透視変換を求める
Mat proj_mat;
proj_mat = findHomography(approx_contours, target_approx_contours);
}
さて、これで透視変換行列を求めることができたけども、行列の数値だけ見てても楽しくない。
せっかくなのでカードに透視変換を加えて、効果を確認する。
確認のためのコードはこんな感じ。
前半部分はテスト用データを作るところ、後半は画像に変換行列を適用するところ。
// 比較対象の頂点として、180度回転+透視変換効果が得られる値を生成する。
target_approx_contours[0] = approx_contours[2];
target_approx_contours[1] = approx_contours[3];
target_approx_contours[2] = approx_contours[0];
target_approx_contours[3] = approx_contours[1];
target_approx_contours[0].x += 100.0f;
target_approx_contours[3].x -= 100.0f; // 透視変換行列を適用して、結果を確認する。
warpPerspective(src, dstimg, proj_mat, Size(681, 412) );
imwrite("warp.jpg", dstimg);
さて、変換前と変換後を比べてみる。
変換前
変換後
あ。カラー画像とグレースケールの差は、出力するファイルを間違えただけ。
カードの輪郭の形状が変化しているということだけわかればいいので。
こんかいはここまで。