BounceBall#02 OpenCVで四角形を検出する その3

前回、輝度値の高い箇所の輪郭を取得しました。今回はこの輪郭をポリゴン近似して、四角形の頂点を求めます。

輪郭をポリゴン近似する

求めた輪郭(contours)をapproxPolyDP()を使ってポリゴン近似します。approx2fに近似した結果を格納します。

MatOfPoint2f contours2f = new MatOfPoint2f(contours.get(i).toArray());
MatOfPoint2f approx2f = new MatOfPoint2f();
Imgproc.approxPolyDP(contours2f, approx2f, 0.05 * Imgproc.arcLength(contours2f, true), true);

書いてはいませんが、実際にはfor文でiについて0からcontoursのサイズ分回っています。
List型のcontoursをapproxPolyDP()の引数に渡せるようにMatOfPoint2fに変換します。この変換も苦労しました。もっとスマートなやり方があるのかもしれません。
また0.05の値ですが、小さくすればより精度の良い近似ができます。今回は四角形程度の頂点の少ない近似とするために、色々振ってみた結果、0.05となりました。

近似した結果から凸包を求める

まず凸包についてですが、ここでは特に記述しません。Wikipediaのリンクを貼っておきます。
凸包 - Wikipedia

明示していませんでしたが、今回検出した四角形は凹んでいない(凸包)の四角形です。なるべくよく四角形を検出できるように、凸包の四角形だけを取得する処理をいれます。

convexHull()を使って凸包を求めます。

MatOfPoint approx = new MatOfPoint(approx2f.toArray());
MatOfInt hull = new MatOfInt();
Imgproc.convexHull(approx, hull);

MatOfPoint2f型のapprox2fをMatOfPoint型に変換する必要があります。
そしてhullに結果を入れるのですが、hullに格納されるのはインデックスで座標ではありません。

四角形の座標を取得する

英語のQ&Aですが、ここを参考にしています。
stackoverflow.com

approxとhullを使って四角形の座標を求めます。

ArrayList<ArrayList<Integer>> cornerPoints = new ArrayList<ArrayList<Integer>>(); 
if (hull.size().height == 4) {
    ArrayList<Integer> srcPoints = new ArrayList<Integer>();
    for (int k = 0; k < hull.size().height; k++) {
        int hullIndex = (int)hull.get(k, 0)[0];
        double[] m = approx.get(hullIndex, 0);
        srcPoints.add((int)m[0]);
        srcPoints.add((int)m[1]);
    }
    cornerPoints.add(srcPoints);
}

cornerPointsには四角形の座標を複数格納します。三角形等、他の図形の座標を入れられるように可変な配列としました。
if文のhull.size().heightは頂点の数(何角形か)を表していて、今回は四角形のときのみ処理の対象としています。
hullIndexはapproxにおいて選択する座標番号を示しています。hullIndexを用いることで、座標の順番を反時計回りにできます。このhull, hullIndexの使用例があまりなく苦労しました。
あとはcornerPointsに四角形の座標が格納されているので、circle()やline()を使って描画すればよいです。どのように四角形が検出できたか、画像を載せておきます。

前回載せた、取得した輪郭を描画した画像です。

取得した輪郭をポリゴン近似等行い、四角形を検出した画像です。