catalinaの備忘録

ソフトウェアやハードウェアの備忘録。後で逆引きできるように。

opencvでカードの傾きを検出する

前回のエントリまでで、カードの輪郭抽出までできたので、今回はその輪郭をもとにカードの傾きを求めることにする。

 

カードの傾きを求めることができれば、そのカードの上にARでモデルを表示するとき、カードの姿勢に追従することができる。

 

カードの傾きとは「2つのカードのそれぞれの4隅の頂点座標同士を対応付けたとき、の頂点座標の変換行列」といえる。

3Dポリゴンでいうところの透視変換行列ってやつですね。

OpenCvにそれっぽい関数があったので使うことにする。

 

カードの輪郭をもとに透視変換行列を求めるまでの手順は次のとおり

  1. カードの輪郭を求める(findContours関数の引数でCV_RETR_EXTERNALを使えば一番外側の輪郭だけ取れる)
  2. カードの輪郭を直線に近似する(approxPolyD)
  3. カードの頂点同士の変換行列を求める(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);

 

 

さて、変換前と変換後を比べてみる。

 

変換前

f:id:Catalina1344:20140603094708j:plain

変換後

 

f:id:Catalina1344:20140614005129j:plain

あ。カラー画像とグレースケールの差は、出力するファイルを間違えただけ。

カードの輪郭の形状が変化しているということだけわかればいいので。

こんかいはここまで。