読者です 読者をやめる 読者になる 読者になる

catalinaの備忘録

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

Opencvでカードの輪郭抽出

OpenCvでごにょごにょやろうとしていたら、色々とハマってしまったので、備忘録をかねたメモ。
とは言いつつも、何やってるコードなのか思い出せない部分も多くて困ったちゃん。

 

前回のエントリで書いた輪郭抽出のアルゴリズムをコードにおとしていくとこんな感じになるというメモ。


 1, ガウシアンフィルタをかけて画像全体をぼかす(ノイズ除去)

フィルタをかけずにいきなり輪郭抽出してしまうと、あちこちのノイズを拾ってしまってやりたいことができなくなってしまう。
そこで、画像全体をぼかして輪郭抽出をしやすくする。
今回使った関数は、ガウシアンフィルタ。

Mat mat_gaussian = new Mat();
Imgproc.GaussianBlur(mGray, mat_gaussian, new Size(1, 1), 0, 0);
Imgproc.threshold(mat_gaussian, mat_bin, 100, 255, Imgproc.THRESH_BINARY);
 

まず、グレースケールの画像を作っておき、あらかじめmGrayに格納しておく。ガウシアンフィルタをかけて、スレッショルド設定で2値化する。これでmat_binに2値で輪郭が強調された画像が格納される。

 

2, 輪郭抽出

 

この画像をつかって、輪郭を抽出する。輪郭を抽出するにはfindContours関数を使う。この関数は次のように使う。

List contour = new ArrayList();
hiert = new Mat();
Imgproc.findContours(mat_bin,
    contour,             // 輪郭情報が格納される
    hiert,               // 階層構造が格納される
    Imgproc.RETR_TREE,   // 階層構造をもった輪郭を出力する
    Imgproc.CHAIN_APPROX_SIMPLE
);

出力結果は、2つに分かれている。「頂点を構成する2次元の頂点座標の配列」「階層構造を表現するMat構造体」

 

上記のコードでは、輪郭の出力に「RETR_TREE」を指定したので、階層構造をもった輪郭情報が出力される。
階層構造のMatはどんな情報かというと、Javadocの説明では次のとおり。

// hiert[i][0] = 同じ階層での、前の輪郭
// hiert[i][1] = 同じ階層での、次の輪郭 // hiert[i][2] = 子の輪郭 // hiert[i][3] = 親の輪郭

上記のMatの要素それぞれにcontourの要素を指し示す番号が格納される。

また、親が存在しない(カメラの一番外側の輪郭など)では-1が格納される。
また、次の輪郭と前の輪郭をたどっていくことで、目的の輪郭を構成することができる。

ここで、カードを認識することを考えたとき、白色の背景に置いたカードを至近距離のカメラで撮影したとき、認識される輪郭は「カメラのフレーム(一番外側)」の輪郭と、その内側にある「カード」の輪郭と考えられる。

 

たとえば、画面に1個だけあるであろうカードの輪郭を描画するテストコードは次のとおり。
っと、貼り付けて気づいたけど、親も子もない輪郭って何だろう?何して書いたコードなんだろう。これは。

 

for(int i=0;i<contour.size();i++) {
    // hiert[i][0] = 同じ階層での、前の輪郭
    // hiert[i][1] = 同じ階層での、次の輪郭
    // hiert[i][2] = 子の輪郭
    // hiert[i][3] = 親の輪郭

Mat tmp_mat = contour.get(i); double Area = Imgproc.contourArea(tmp_mat); int[] relation = new int[4]; int n = hiert.get(0, i, relation); int childIndex = relation[2]; int parentIndex = relation[3]; if( childIndex != -1 && parentIndex != -1 && Area > 32767){ Scalar color =new Scalar(255, 128, 128 ); Imgproc.drawContours(mRgba, contour, i, // index color, 4, // 描画する線の太さ 1, // 描画する輪郭の種類, hiert, 0, // max level new Point(0,0) ); } }

ちなみに、描画のmax levelは、輪郭の親子関係のうち、子供の輪郭を 何階層まで描画するかという指定。

 これで白色背景に置いたカードを認識して表示することができた。

 

なかなか手ごわかったけど、一番手ごわかったのは自分自身の頭。配列のインデックスは0だってのに、なぜか1から読もうとしてた。落ち着いて考えないとつまんないトコで躓くね。

 

 

次のステップ

次はこの輪郭のモーメントを求めることにする。
モーメントには幾つかの種類があるけども、OpenCvの関数を駆使して、Hu不変モーメントを求めることが目標。
Hu不変モーメントは、平行移動、拡大、回転などの動作に対して不変な値。
どういうことかというと、カードが傾いていても、カメラとカードの距離が異なっていても、撮影した画面の中央にカードがなくとも、同じ値を指し示すはずのもの。

とは言ったものの、数式がさっぱり意味不明なので、実験してみないことにはなんとも言えず。
実験も「同じ輪郭として認識した!」「異なる輪郭なので認識しなかった」といった程度の判断方法しかない。

数式の意味はわからなくても、値の変化を見てみたい気もするので、デバッグ機能つけるのもアリかもしれない。OpenCvやAndroidでの描画系のことはよくわかってないし。

というわけで今日はここまで。

電車の中でサクっとブログ書けるのって素敵だね。うん。