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
また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()を使って描画すればよいです。どのように四角形が検出できたか、画像を載せておきます。
前回載せた、取得した輪郭を描画した画像です。
取得した輪郭をポリゴン近似等行い、四角形を検出した画像です。