JavaCV を使用して 三国志大戦4 の 武将カードの 将器副 復活減少 を判定する

kameyatakefumi.hatenablog.com

前回、解任済み武将カードを判定する事ができました。
今回は個の判定に戻ろうと思います。

個の判定についてはバリバリにプログラムを書いていく事になりそうです。

調べた結果 OpenCV という画像処理系のライブラリが熱いみたいですね。
導入の手軽さから JavaCV を利用します。

将器副 復活減少 は以下になります。
f:id:kameya_takefumi:20171017162323p:plain

将器副 復活減少 が以下の画像に何個あるか判定したいです。
f:id:kameya_takefumi:20171013133242p:plain

指定の画像が対象となる画像のどこにあるのかを判定する処理を テンプレートマッチング というそうです。
最初、言葉がわからずに検索に苦労しました。

以下、JavaCV のテンプレートマッチングのサンプルです。
javacv/TemplateMatching.java at master · bytedeco/javacv · GitHub

テンプレートマッチング と OpenCV の説明では以下がわかりやすかったです。
Pythonでテンプレートマッチング、OpenCVサンプルコードと解説 : ネットサーフィンの壺

サンプルを以下の通り修正して実行しました。

public class TemplateMatching {

    public static void main(String[] args) {

        //read in image default colors
        Mat sourceColor = imread("SR董卓.PNG");
        Mat sourceGrey = new Mat(sourceColor.size(), CV_8UC1);
        cvtColor(sourceColor, sourceGrey, COLOR_BGR2GRAY);
        //load in template in grey 
        Mat template = imread("将器副 復活減少.PNG", CV_LOAD_IMAGE_GRAYSCALE);//int = 0
        //Size for the result image
        Size size = new Size(sourceGrey.cols() - template.cols() + 1, sourceGrey.rows() - template.rows() + 1);
        Mat result = new Mat(size, CV_32FC1);
        matchTemplate(sourceGrey, template, result, TM_CCORR_NORMED);

        getPointsFromMatAboveThreshold(result, 0.947f).stream().forEach((point) -> {
            rectangle(sourceColor, new Rect(point.x(), point.y(), template.cols(), template.rows()), randColor(), 2, 0, 0);
        });

        imshow("Original marked", sourceColor);
        waitKey(0);
        destroyAllWindows();
    }

    public static Scalar randColor() {
        int b, g, r;
        b = ThreadLocalRandom.current().nextInt(0, 255 + 1);
        g = ThreadLocalRandom.current().nextInt(0, 255 + 1);
        r = ThreadLocalRandom.current().nextInt(0, 255 + 1);
        return new Scalar(b, g, r, 0);
    }

    public static List<Point> getPointsFromMatAboveThreshold(Mat m, float t) {
        List<Point> matches = new ArrayList<>();
        FloatIndexer indexer = m.createIndexer();
        for (int y = 0; y < m.rows(); y++) {
            for (int x = 0; x < m.cols(); x++) {
                if (indexer.get(y, x) > t) {
                    System.out.println("(" + x + "," + y + ") = " + indexer.get(y, x));
                    matches.add(new Point(x, y));
                }
            }
        }
        return matches;
    }

}

以下、実行結果です。
f:id:kameya_takefumi:20171017164552p:plain

判定されている!
けど、何重にも判定が行われているようにみえます。

出力結果は以下の通りです。

(103,57) = 0.95975274
(104,57) = 0.9996804
(105,57) = 0.9592217
(159,57) = 0.9535268
(160,57) = 0.9985073
(161,57) = 0.9670713

しきい値 0.947f だと1つにつき3回判定されていますね。
しきい値を上げると、今度は別の画像を読ませたときに、引っかからないものが出てくるでしょう。
悩ましい所です。

私としては しきい値 を緩めで設定して、判定した対象の座標から +-3 ぐらいを1つとみなしてまとめる対応が好きですね。

変数として しきい値 以外に 許容誤差の値 も必要そうです。

OpenCV というか JavaCV は簡単に導入できて素晴らしいですね。
サンプルも豊富だし凄いし楽しい。

kameyatakefumi.hatenablog.com
kameyatakefumi.hatenablog.com