BounceBall#05 ボールを反射させる その1

ボールが四角に衝突したときの反射について考えます。

f:id:imoto-yuya-1234:20170720220739p:plain:w350

上の図のよう各々のベクトルが決められたとき、ボールの進行ベクトル{\bf v}と反射ベクトル{\bf v'}、平行ベクトル{\bf p}でなされる大きな三角に着目すると求めたい{\bf v'}は下記の通りです。

{
{\bf v'} = 2{\bf p} - {\bf v} \tag{1} \label{1}
}

ここで{\bf v}は既知ですので、{\bf p}を求めればよいです。
{\bf v}{\bf p}、法線ベクトルの定数倍である{a{\bf n}}でなされる小さな三角に着目すれば、

{
{\bf p} = a{\bf n} + {\bf v} \tag{2} \label{2}
}

となるので、式\eqref{1}に代入して、

\begin{eqnarray}{\bf v'}&=&2( a{\bf n} + {\bf v}  ) - {\bf v}\\&=&{\bf v} + 2a{\bf n} \tag{3} \label{3}\end{eqnarray}

が得られます。既知である反射面のベクトルからその法線ベクトル{\bf n}を求められるので、定数aがわかれば反射ベクトル{\bf v'}を求めることができます。

BounceBall#04 ボールが四角に接触したことを検知する

前回までで四角を検出することとボールを動かすことができました。
次はボールが四角に接触するときを考えます。

ここでボールが四角に接触するとは、ボールと辺または角の最短距離がボールの半径以下であれば接触したと言えます。

そこで四角には4本の線分の内の1本について、線分ABとボールの中心点Cの最短距離dを考えます。この最短距離dがボールの半径以下であるかどうかを調べ、この試行を4本の線分に行えば、ボールと四角との当たり判定を行えます。

最短距離dについては三つのパターンがあります。

i)点Cから点Aの距離が最短距離dとなる場合

{
d = |C - A|
}
f:id:imoto-yuya-1234:20170704234757p:plain:w250

ii)点Cから点Bの距離が最短距離dとなる場合

{
d = |C - B|
}
f:id:imoto-yuya-1234:20170704234853p:plain:w250

iii)点Cから線分ABに対する垂線が最短距離dとなる場合

{
d = \frac{|(C - A) \times (B - A)|}{|(B - A)|}
}
f:id:imoto-yuya-1234:20170704234902p:plain:w220

導出は下記のサイトを参考にしました。
平面幾何におけるベクトル演算 » 直線と線分

BounceBall#03 ボールを動かす

前回まででカメラを使用し、その画像から四角形を検出することができました。次にカメラ画像の上でボールを動かします。

ソースコードgithubで公開しています。
https://github.com/imoto-yuya-1234/BounceBall
SHA: bbe82521e1b2749c7336a3aee4c2f583d647d6cf

このブログを参考にしています。
appdevmem.blogspot.jp

緑色のボールがカメラ画像内で動き回り、画像の端に到達すると反射します。
本当はカメラ画像をスマホ画面いっぱいに表示させたかったのですが、どうすればよいかわかりませんでした。わかり次第対応したいですが、優先度は低いので当分そのままだと思います。

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()を使って描画すればよいです。どのように四角形が検出できたか、画像を載せておきます。

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

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

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

前回、カメラ画像を2値化しました。今回は2値化した画像から四角形の座標を求めます。

輪郭を取得する

2値化した際、白になった領域の輪郭を取得します。

List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Mat hierarchy = new Mat();
Imgproc.findContours(tempMat, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_TC89_L1);
Imgproc.drawContours(rgbaMat, contours, -1, new Scalar(0, 255, 0), 1);

findContours()で輪郭を取得します。tempMatは2値化した画像です。contoursに輪郭のデータが格納されるので、contoursをdrawContours()で描画しています。

2値化画像です。

2値化画像から輪郭を取得して描画した画像です。

AndroidでOpenCV 画像から青色を検出する

Androidのカメラ画像から青色を検出します。

このブログを参考にしました。
アンドロイドでOpenCV:色検出

実装はこんな感じ。

// 青色を抽出
Imgproc.cvtColor(tempMat, tempMat, Imgproc.COLOR_RGBA2BGR);
Imgproc.cvtColor(tempMat, tempMat, Imgproc.COLOR_BGR2HSV);
Core.inRange(tempMat, new Scalar(90, 70, 70), new Scalar(110, 255, 255), tempMat);
Imgproc.cvtColor(tempMat, tempMat, Imgproc.COLOR_GRAY2BGRA);

他の色を抽出したいときの設定はこんな感じのようです。

// 赤色
Core.inRange(tempMat, new Scalar(0, 100, 30), new Scalar(5, 255, 255), tempMat);
// 緑色
Core.inRange(tempMat, new Scalar(50, 50, 50), new Scalar(80, 255, 255), tempMat);
// 黄色
Core.inRange(tempMat, new Scalar(20, 50, 50), new Scalar(40, 255, 255), tempMat);
// 肌色
Core.inRange(tempMat, new Scalar(0, 38, 89), new Scalar(20, 192, 243), tempMat);

色の抽出にはHSV形式が良いらしいです。この色の抽出が結構大変でした。RGBAから直接HSVに変換はできないらしく、RGBA→BGR→HSVとする必要があります。またinRangeの出力はGRAYなので適宜変換してください。ここでは色を抽出した後はGRAY→GBRAとしています。このことがわかるのにかなり時間を要しました。どれか一つでも抜けると出力画像がおかしくなったり、アプリ起動中に落ちたりします。

実際の入出力画像を示します。
入力画像(カメラ画像)

出力画像(青色を白に他を黒とした画像)

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

Androidでカメラを使えるようになったので、カメラの画像から四角形を検出したいと思います。一気に書くのは大変なので、下記のように分割して書きます。
その1 四角形の領域を抽出する
その2 輪郭を抽出する
その3 輪郭をポリゴン近似して頂点座標を求める

ソースコードgithubで公開しています。
https://github.com/imoto-yuya-1234/BounceBall
SHA: c86a350efd43e6949997f76f0117b04c43321b6c

四角形の領域を抽出する

まずは四角形の領域を抽出する必要があります。
今回は例えば白い四角形を検出することを考えて、輝度値の高い部分を抽出すれば四角形の領域を抽出できるとしました。そこでカメラ画像をグレースケール化して、輝度値において2値化します。

このブログに詳しく書いています。
blog.goo.ne.jp

こんな感じで実装できます。

// 入力画像をグレースケール変換
Imgproc.cvtColor(tempMat, tempMat, Imgproc.COLOR_RGBA2GRAY);
// 輝度値の高い箇所を抽出
Imgproc.threshold(tempMat, tempMat, 0, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);

THRESH_OTSUを使うことで、自動で最適な閾値を設定してくれるみたいです。
実際の入出力画像を示します。

こちらが入力画像(カメラ画像)です。

こちらが出力画像(2値化した画像)です。この画像から四角形を検出します。