Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

How to classify image of object on a game board

Imagine I have a squared game board the same as Match-3 game (namely Candy Crush, Puzzle and Dragon, etc), my objective is to detect the orb type on their screen and save the result as an array. I have a set of orbs image with transparent background as the source image.

Game board of P&D

Source orb images

I worked on capturing the board screen and splitting the image into chunks of small square image each containing the orb but with the board color in the background.

I tried Histogram matching (both 4 methods) but all return false result. Template Matching yield only some correct matches but mostly incorrect. Possibly due to the background color of the board in the captured image. And in some cases, if my source image is an orb in grayscale (most image is colored), I got this error while comparing my captured image (colored) with the grayscale source image.

OpenCV(3.4.3) Error: Assertion failed ((depth == CV_8U || depth == CV_32F) && type == _templ.type() && _img.dims() <= 2) in void cv::matchTemplate(cv::InputArray, cv::InputArray, cv::OutputArray, int, cv::InputArray), file /build/3_4_pack-android/opencv/modules/imgproc/src/templmatch.cpp, line 1102 E/org.opencv.imgproc: imgproc::matchTemplate_11() caught cv::Exception: OpenCV(3.4.3) /build/3_4_pack-android/opencv/modules/imgproc/src/templmatch.cpp:1102: error: (-215:Assertion failed) (depth == CV_8U || depth == CV_32F) && type == _templ.type() && _img.dims() <= 2 in function 'void cv::matchTemplate(cv::InputArray, cv::InputArray, cv::OutputArray, int, cv::InputArray)'

with this code:

baseImage = new Mat[69]; baseImage[0] = Utils.loadResource(this, R.drawable.water, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //normal orb baseImage[1] = Utils.loadResource(this, R.drawable.w_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //shiny orb baseImage[2] = Utils.loadResource(this, R.drawable.w_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //weather orb baseImage[3] = Utils.loadResource(this, R.drawable.w_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //shiny weather orb baseImage[4] = Utils.loadResource(this, R.drawable.w_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //monotone orb baseImage[5] = Utils.loadResource(this, R.drawable.w_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //shiny monotone orb baseImage[6] = Utils.loadResource(this, R.drawable.w_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //targeted orb baseImage[7] = Utils.loadResource(this, R.drawable.w_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //initial froze orb baseImage[8] = Utils.loadResource(this, R.drawable.w_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //completely frozen orb baseImage[9] = Utils.loadResource(this, R.drawable.w_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //second stage frozen orb baseImage[10] = Utils.loadResource(this, R.drawable.w_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //last stage frozen orb

            baseImage[11] = Utils.loadResource(this, R.drawable.fire, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[12] = Utils.loadResource(this, R.drawable.f_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[13] = Utils.loadResource(this, R.drawable.f_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[14] = Utils.loadResource(this, R.drawable.f_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[15] = Utils.loadResource(this, R.drawable.f_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[16] = Utils.loadResource(this, R.drawable.f_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[17] = Utils.loadResource(this, R.drawable.f_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[18] = Utils.loadResource(this, R.drawable.f_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[19] = Utils.loadResource(this, R.drawable.f_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[20] = Utils.loadResource(this, R.drawable.f_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[21] = Utils.loadResource(this, R.drawable.f_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);

            baseImage[22] = Utils.loadResource(this, R.drawable.wood, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[23] = Utils.loadResource(this, R.drawable.p_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[24] = Utils.loadResource(this, R.drawable.p_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[25] = Utils.loadResource(this, R.drawable.p_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[26] = Utils.loadResource(this, R.drawable.p_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[27] = Utils.loadResource(this, R.drawable.p_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[28] = Utils.loadResource(this, R.drawable.p_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[29] = Utils.loadResource(this, R.drawable.p_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[30] = Utils.loadResource(this, R.drawable.p_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[31] = Utils.loadResource(this, R.drawable.p_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[32] = Utils.loadResource(this, R.drawable.p_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);

            baseImage[33] = Utils.loadResource(this, R.drawable.light, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[34] = Utils.loadResource(this, R.drawable.l_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[35] = Utils.loadResource(this, R.drawable.l_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[36] = Utils.loadResource(this, R.drawable.l_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[37] = Utils.loadResource(this, R.drawable.l_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[38] = Utils.loadResource(this, R.drawable.l_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[39] = Utils.loadResource(this, R.drawable.l_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[40] = Utils.loadResource(this, R.drawable.l_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[41] = Utils.loadResource(this, R.drawable.l_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[42] = Utils.loadResource(this, R.drawable.l_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[43] = Utils.loadResource(this, R.drawable.l_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);

            baseImage[44] = Utils.loadResource(this, R.drawable.dark, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[45] = Utils.loadResource(this, R.drawable.d_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[46] = Utils.loadResource(this, R.drawable.d_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[47] = Utils.loadResource(this, R.drawable.d_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[48] = Utils.loadResource(this, R.drawable.d_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[49] = Utils.loadResource(this, R.drawable.d_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[50] = Utils.loadResource(this, R.drawable.d_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[51] = Utils.loadResource(this, R.drawable.d_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[52] = Utils.loadResource(this, R.drawable.d_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[53] = Utils.loadResource(this, R.drawable.d_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[54] = Utils.loadResource(this, R.drawable.d_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);

            baseImage[55] = Utils.loadResource(this, R.drawable.heart, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[56] = Utils.loadResource(this, R.drawable.h_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[57] = Utils.loadResource(this, R.drawable.h_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[58] = Utils.loadResource(this, R.drawable.h_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[59] = Utils.loadResource(this, R.drawable.h_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[60] = Utils.loadResource(this, R.drawable.h_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[61] = Utils.loadResource(this, R.drawable.h_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[62] = Utils.loadResource(this, R.drawable.h_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[63] = Utils.loadResource(this, R.drawable.h_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[64] = Utils.loadResource(this, R.drawable.h_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
            baseImage[65] = Utils.loadResource(this, R.drawable.h_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);

            baseImage[66] = Utils.loadResource(this, R.drawable.question, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //question orb
            baseImage[67] = Utils.loadResource(this, R.drawable.stone, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //stone orb
            baseImage[68] = Utils.loadResource(this, R.drawable.x, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //random weather orb
//lazy implementation of template matching
private int compareImage(Mat tmp) {
    //Imgproc.cvtColor(tmp, tmp, Imgproc.COLOR_RGBA2BGR);

    int matchedIndex = -1;
    double maxScore = 0.0;
    double threshold = 0.9;
    //match against 3 abnormal orb
    for(int i=66; i<69; i++) {
        double maxVal = compareImage(baseImage[i], tmp);
        if(maxVal >= threshold && maxVal > maxScore) {
            maxScore = maxVal;
            matchedIndex = i;
            if(maxVal == 1.0) {
                return matchedIndex;
            }
        }
    }
    if(matchedIndex < 0) {
        int[] normalOrb = new int[]{0, 11, 22, 33, 44, 55};
        //match against 6 different normal element orb to find root
        for(int i=0; i<normalOrb.length; i++) {
            //Log.d("idx", Integer.toString(normalOrb[i]));
            //Log.d("t.row", Boolean.toString(baseImage[normalOrb[i]].rows() > tmp.rows()));
            //Log.d("t.row", Boolean.toString(baseImage[normalOrb[i]].cols() > tmp.cols()));

            double maxVal = compareImage(baseImage[normalOrb[i]], tmp);
            if (maxVal > maxScore) {
                maxScore = maxVal;
                matchedIndex = normalOrb[i];
                if(maxVal == 1.0) {
                    return matchedIndex;
                }
            }
        }
        //match against same element orb of different properties
        int last_idx = matchedIndex;
        for(int j=last_idx+1; j<last_idx+10; j++) {
            //Log.d("idx", Integer.toString(j));
            double maxVal = compareImage(baseImage[j], tmp);
            if(maxVal > maxScore) {
                maxScore = maxVal;
                matchedIndex = j;
                if(maxVal == 1.0) {
                    break;
                }
            }
        }
    }

    return matchedIndex;

}

private double compareImage(Mat source, Mat tmp) {

    if(source.rows() > tmp.rows() && source.cols() > tmp.cols()) {
        int r_rows = source.rows() - tmp.rows() + 1;
        int r_cols = source.cols() - tmp.cols() + 1;
        Mat result = new Mat();

        result.create(r_rows, r_cols, CvType.CV_32F);

        Imgproc.matchTemplate(source, tmp, result, Imgproc.TM_CCOEFF_NORMED);
        //Core.normalize(result, result, 0, 1, Core.NORM_MINMAX, -1, new Mat());
        Core.MinMaxLocResult mmr = Core.minMaxLoc(result);
        double maxVal = mmr.maxVal;
        return maxVal;
    } else {
        Log.d("Compare failed", "Changing to Monotone mode.");
        Imgproc.cvtColor(source, source, Imgproc.COLOR_RGBA2GRAY);
        Mat mTmp = new Mat();
        tmp.copyTo(mTmp);
        Imgproc.cvtColor(mTmp, mTmp, Imgproc.COLOR_RGBA2GRAY);
        if(source.rows() < tmp.rows() && source.cols() < tmp.cols()) {
            Mat copy = new Mat();
            source.copyTo(copy);
            tmp.copyTo(source);
            copy.copyTo(tmp);
        }

        Log.d("DepthMatch?", Boolean.toString(mTmp.depth()== CV_32F));

        int r_rows = (source.rows() > tmp.rows() && source.cols() > tmp.cols())? (source.rows() - mTmp.rows() + 1) : (mTmp.rows() - source.rows() + 1);
        Log.d("r_rows",""+r_rows);
        int r_cols = (source.rows() > tmp.rows() && source.cols() > tmp.cols())? (source.cols() - mTmp.cols() + 1) : (mTmp.cols() - source.cols() + 1);
        Log.d("r_cols",""+r_cols);
        Mat result = new Mat();

        result.create(r_rows, r_cols, CvType.CV_32F);

        Imgproc.matchTemplate(source, mTmp, result, Imgproc.TM_CCOEFF_NORMED);
        //Core.normalize(result, result, 0, 1, Core.NORM_MINMAX, -1, new Mat());
        Core.MinMaxLocResult mmr = Core.minMaxLoc(result);
        double maxVal = mmr.maxVal;
        return maxVal;
    }

Histogram comparison give me erroneous result, but rather fast while Template Matching offers more promising result but at cost of slower speed. For my case, each captured image needs to be compared with 19 source images as worst case, Template Matching consumes at least few seconds for just a 6x6 board. Is there any suggestion on other approach for the aforementioned scenario, or any possibly fixes to my code to generate better match at faster pace?

How to classify image of object on a game board

Imagine I have a squared game board the same as Match-3 game (namely Candy Crush, Puzzle and Dragon, etc), my objective is to detect the orb type on their screen and save the result as an array. I have a set of orbs image with transparent background as the source image.

Game board of P&D

Source orb images

I worked on capturing the board screen and splitting the image into chunks of small square image each containing the orb but with the board color in the background.

I tried Histogram Histogram matching (both 4 methods) but all return false result. Template Matching yield only some correct matches but mostly incorrect. Possibly due to the background color of the board in the captured image. And in some cases, if my source image is an orb in grayscale grayscale (most image is colored), I got this error while comparing my captured image (colored) with the grayscale grayscale source image.

OpenCV(3.4.3) Error: Assertion failed ((depth == CV_8U || depth == CV_32F) && type == _templ.type() && _img.dims() <= 2) in void cv::matchTemplate(cv::InputArray, cv::InputArray, cv::OutputArray, int, cv::InputArray), file /build/3_4_pack-android/opencv/modules/imgproc/src/templmatch.cpp, line 1102
 E/org.opencv.imgproc: imgproc::matchTemplate_11() caught cv::Exception: OpenCV(3.4.3) /build/3_4_pack-android/opencv/modules/imgproc/src/templmatch.cpp:1102: error: (-215:Assertion failed) (depth == CV_8U || depth == CV_32F) && type == _templ.type() && _img.dims() <= 2 in function 'void cv::matchTemplate(cv::InputArray, cv::InputArray, cv::OutputArray, int, cv::InputArray)'

cv::InputArray)'

with this code:

baseImage = new Mat[69];
  baseImage[0] = Utils.loadResource(this, R.drawable.water, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //normal orb
 baseImage[1] = Utils.loadResource(this, R.drawable.w_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //shiny orb
 baseImage[2] = Utils.loadResource(this, R.drawable.w_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //weather orb
  baseImage[3] = Utils.loadResource(this, R.drawable.w_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //shiny weather orb
 baseImage[4] = Utils.loadResource(this, R.drawable.w_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //monotone orb
  baseImage[5] = Utils.loadResource(this, R.drawable.w_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //shiny monotone orb
 baseImage[6] = Utils.loadResource(this, R.drawable.w_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //targeted orb
  baseImage[7] = Utils.loadResource(this, R.drawable.w_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //initial froze orb
 baseImage[8] = Utils.loadResource(this, R.drawable.w_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //completely frozen orb
  baseImage[9] = Utils.loadResource(this, R.drawable.w_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //second stage frozen orb
 baseImage[10] = Utils.loadResource(this, R.drawable.w_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //last stage frozen orb

orb
 baseImage[11] = Utils.loadResource(this, R.drawable.fire, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[12] = Utils.loadResource(this, R.drawable.f_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[13] = Utils.loadResource(this, R.drawable.f_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[14] = Utils.loadResource(this, R.drawable.f_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[15] = Utils.loadResource(this, R.drawable.f_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[16] = Utils.loadResource(this, R.drawable.f_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[17] = Utils.loadResource(this, R.drawable.f_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[18] = Utils.loadResource(this, R.drawable.f_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[19] = Utils.loadResource(this, R.drawable.f_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[20] = Utils.loadResource(this, R.drawable.f_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[21] = Utils.loadResource(this, R.drawable.f_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[22] = Utils.loadResource(this, R.drawable.wood, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[23] = Utils.loadResource(this, R.drawable.p_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[24] = Utils.loadResource(this, R.drawable.p_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[25] = Utils.loadResource(this, R.drawable.p_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[26] = Utils.loadResource(this, R.drawable.p_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[27] = Utils.loadResource(this, R.drawable.p_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[28] = Utils.loadResource(this, R.drawable.p_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[29] = Utils.loadResource(this, R.drawable.p_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[30] = Utils.loadResource(this, R.drawable.p_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[31] = Utils.loadResource(this, R.drawable.p_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[32] = Utils.loadResource(this, R.drawable.p_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[33] = Utils.loadResource(this, R.drawable.light, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[34] = Utils.loadResource(this, R.drawable.l_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[35] = Utils.loadResource(this, R.drawable.l_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[36] = Utils.loadResource(this, R.drawable.l_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[37] = Utils.loadResource(this, R.drawable.l_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[38] = Utils.loadResource(this, R.drawable.l_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[39] = Utils.loadResource(this, R.drawable.l_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[40] = Utils.loadResource(this, R.drawable.l_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[41] = Utils.loadResource(this, R.drawable.l_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[42] = Utils.loadResource(this, R.drawable.l_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[43] = Utils.loadResource(this, R.drawable.l_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[44] = Utils.loadResource(this, R.drawable.dark, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[45] = Utils.loadResource(this, R.drawable.d_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[46] = Utils.loadResource(this, R.drawable.d_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[47] = Utils.loadResource(this, R.drawable.d_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[48] = Utils.loadResource(this, R.drawable.d_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[49] = Utils.loadResource(this, R.drawable.d_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[50] = Utils.loadResource(this, R.drawable.d_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[51] = Utils.loadResource(this, R.drawable.d_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[52] = Utils.loadResource(this, R.drawable.d_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[53] = Utils.loadResource(this, R.drawable.d_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[54] = Utils.loadResource(this, R.drawable.d_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[55] = Utils.loadResource(this, R.drawable.heart, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[56] = Utils.loadResource(this, R.drawable.h_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[57] = Utils.loadResource(this, R.drawable.h_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[58] = Utils.loadResource(this, R.drawable.h_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[59] = Utils.loadResource(this, R.drawable.h_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[60] = Utils.loadResource(this, R.drawable.h_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[61] = Utils.loadResource(this, R.drawable.h_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[62] = Utils.loadResource(this, R.drawable.h_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[63] = Utils.loadResource(this, R.drawable.h_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[64] = Utils.loadResource(this, R.drawable.h_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
 baseImage[65] = Utils.loadResource(this, R.drawable.h_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
  baseImage[66] = Utils.loadResource(this, R.drawable.question, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //question orb
 baseImage[67] = Utils.loadResource(this, R.drawable.stone, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //stone orb
  baseImage[68] = Utils.loadResource(this, R.drawable.x, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //random weather orb
 //lazy implementation of template matching
 private int compareImage(Mat tmp) {
 //Imgproc.cvtColor(tmp, tmp, Imgproc.COLOR_RGBA2BGR);
 int matchedIndex = -1;
 double maxScore = 0.0;
 double threshold = 0.9;
  //match against 3 abnormal orb
 for(int i=66; i<69; i++) {
 double maxVal = compareImage(baseImage[i], tmp);
  if(maxVal >= threshold && maxVal > maxScore) {
 maxScore = maxVal;
 matchedIndex = i;
  if(maxVal == 1.0) {
 return matchedIndex;
 }
 }
 }
  if(matchedIndex < 0) {
  int[] normalOrb = new int[]{0, 11, 22, 33, 44, 55};
  //match against 6 different normal element orb to find root
 for(int i=0; i<normalOrb.length; i++) {
 //Log.d("idx", Integer.toString(normalOrb[i]));
  //Log.d("t.row", Boolean.toString(baseImage[normalOrb[i]].rows() > tmp.rows()));
 //Log.d("t.row", Boolean.toString(baseImage[normalOrb[i]].cols() > tmp.cols()));
 double maxVal = compareImage(baseImage[normalOrb[i]], tmp);
  if (maxVal > maxScore) {
 maxScore = maxVal;
 matchedIndex = normalOrb[i];
  if(maxVal == 1.0) {
 return matchedIndex;
 }
 }
 }
  //match against same element orb of different properties
 int last_idx = matchedIndex;
  for(int j=last_idx+1; j<last_idx+10; j++) {
 //Log.d("idx", Integer.toString(j));
 double maxVal = compareImage(baseImage[j], tmp);
  if(maxVal > maxScore) {
 maxScore = maxVal;
 matchedIndex = j;
  if(maxVal == 1.0) {
 break;
 }
 }
 }
 }
  return matchedIndex;
 }
 private double compareImage(Mat source, Mat tmp) {
  if(source.rows() > tmp.rows() && source.cols() > tmp.cols()) {
 int r_rows = source.rows() - tmp.rows() + 1;
 int r_cols = source.cols() - tmp.cols() + 1;
 Mat result = new Mat();
  result.create(r_rows, r_cols, CvType.CV_32F);
  Imgproc.matchTemplate(source, tmp, result, Imgproc.TM_CCOEFF_NORMED);
  //Core.normalize(result, result, 0, 1, Core.NORM_MINMAX, -1, new Mat());
 Core.MinMaxLocResult mmr = Core.minMaxLoc(result);
 double maxVal = mmr.maxVal;
 return maxVal;
  } else {
  Log.d("Compare failed", "Changing to Monotone mode.");
 Imgproc.cvtColor(source, source, Imgproc.COLOR_RGBA2GRAY);
 Mat mTmp = new Mat();
 tmp.copyTo(mTmp);
  Imgproc.cvtColor(mTmp, mTmp, Imgproc.COLOR_RGBA2GRAY);
  if(source.rows() < tmp.rows() && source.cols() < tmp.cols()) {
 Mat copy = new Mat();
 source.copyTo(copy);
 tmp.copyTo(source);
 copy.copyTo(tmp);
 }
  Log.d("DepthMatch?", Boolean.toString(mTmp.depth()== CV_32F));
  int r_rows = (source.rows() > tmp.rows() && source.cols() > tmp.cols())? (source.rows() - mTmp.rows() + 1) : (mTmp.rows() - source.rows() + 1);
 Log.d("r_rows",""+r_rows);
  int r_cols = (source.rows() > tmp.rows() && source.cols() > tmp.cols())? (source.cols() - mTmp.cols() + 1) : (mTmp.cols() - source.cols() + 1);
 Log.d("r_cols",""+r_cols);
  Mat result = new Mat();
  result.create(r_rows, r_cols, CvType.CV_32F);
  Imgproc.matchTemplate(source, mTmp, result, Imgproc.TM_CCOEFF_NORMED);
  //Core.normalize(result, result, 0, 1, Core.NORM_MINMAX, -1, new Mat());
 Core.MinMaxLocResult mmr = Core.minMaxLoc(result);
 double maxVal = mmr.maxVal;
 return maxVal;
  }

Histogram comparison give me erroneous result, but rather fast while Template Matching offers more promising result but at cost of slower speed. For my case, each captured image needs to be compared with 19 source images as worst case, Template Matching consumes at least few seconds for just a 6x6 board. Is there any suggestion on other approach for the aforementioned scenario, or any possibly fixes to my code to generate better match at faster pace?

How to classify image of object on a game board

Imagine I have a squared game board the same as Match-3 game (namely Candy Crush, Puzzle and Dragon, etc), my objective is to detect the orb type on their screen and save the result as an array. I have a set of orbs image with transparent background as the source image.

Game board of P&D

Source orb images

I worked on capturing the board screen and splitting the image into chunks of small square image each containing the orb but with the board color in the background.

I tried Histogram matching (both 4 methods) but all return false result. Template Matching yield only some correct matches but mostly incorrect. Possibly due to the background color of the board in the captured image. And in some cases, if my source image is an orb in grayscale (most image is colored), I got this error while comparing my captured image (colored) with the grayscale source image.

OpenCV(3.4.3) Error: Assertion failed ((depth == CV_8U || depth == CV_32F) && type == _templ.type() && _img.dims() <= 2) in void cv::matchTemplate(cv::InputArray, cv::InputArray, cv::OutputArray, int, cv::InputArray), file /build/3_4_pack-android/opencv/modules/imgproc/src/templmatch.cpp, line 1102
E/org.opencv.imgproc: imgproc::matchTemplate_11() caught cv::Exception: OpenCV(3.4.3) /build/3_4_pack-android/opencv/modules/imgproc/src/templmatch.cpp:1102: error: (-215:Assertion failed) (depth == CV_8U || depth == CV_32F) && type == _templ.type() && _img.dims() <= 2 in function 'void cv::matchTemplate(cv::InputArray, cv::InputArray, cv::OutputArray, int, cv::InputArray)'

with this code:

baseImage = new Mat[69];
                baseImage[0] = Utils.loadResource(this, R.drawable.water, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //normal orb
                baseImage[1] = Utils.loadResource(this, R.drawable.w_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //shiny orb
                baseImage[2] = Utils.loadResource(this, R.drawable.w_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //weather orb
                baseImage[3] = Utils.loadResource(this, R.drawable.w_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //shiny weather orb
                baseImage[4] = Utils.loadResource(this, R.drawable.w_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //monotone orb
                baseImage[5] = Utils.loadResource(this, R.drawable.w_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //shiny monotone orb
                baseImage[6] = Utils.loadResource(this, R.drawable.w_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //targeted orb
                baseImage[7] = Utils.loadResource(this, R.drawable.w_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //initial froze orb
                baseImage[8] = Utils.loadResource(this, R.drawable.w_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //completely frozen orb
                baseImage[9] = Utils.loadResource(this, R.drawable.w_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //second stage frozen orb
                baseImage[10] = Utils.loadResource(this, R.drawable.w_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //last stage frozen orb

                baseImage[11] = Utils.loadResource(this, R.drawable.fire, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[12] = Utils.loadResource(this, R.drawable.f_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[13] = Utils.loadResource(this, R.drawable.f_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[14] = Utils.loadResource(this, R.drawable.f_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[15] = Utils.loadResource(this, R.drawable.f_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[16] = Utils.loadResource(this, R.drawable.f_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[17] = Utils.loadResource(this, R.drawable.f_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[18] = Utils.loadResource(this, R.drawable.f_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[19] = Utils.loadResource(this, R.drawable.f_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[20] = Utils.loadResource(this, R.drawable.f_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[21] = Utils.loadResource(this, R.drawable.f_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);

                baseImage[22] = Utils.loadResource(this, R.drawable.wood, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[23] = Utils.loadResource(this, R.drawable.p_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[24] = Utils.loadResource(this, R.drawable.p_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[25] = Utils.loadResource(this, R.drawable.p_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[26] = Utils.loadResource(this, R.drawable.p_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[27] = Utils.loadResource(this, R.drawable.p_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[28] = Utils.loadResource(this, R.drawable.p_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[29] = Utils.loadResource(this, R.drawable.p_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[30] = Utils.loadResource(this, R.drawable.p_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[31] = Utils.loadResource(this, R.drawable.p_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[32] = Utils.loadResource(this, R.drawable.p_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);

                baseImage[33] = Utils.loadResource(this, R.drawable.light, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[34] = Utils.loadResource(this, R.drawable.l_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[35] = Utils.loadResource(this, R.drawable.l_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[36] = Utils.loadResource(this, R.drawable.l_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[37] = Utils.loadResource(this, R.drawable.l_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[38] = Utils.loadResource(this, R.drawable.l_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[39] = Utils.loadResource(this, R.drawable.l_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[40] = Utils.loadResource(this, R.drawable.l_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[41] = Utils.loadResource(this, R.drawable.l_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[42] = Utils.loadResource(this, R.drawable.l_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[43] = Utils.loadResource(this, R.drawable.l_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);

                baseImage[44] = Utils.loadResource(this, R.drawable.dark, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[45] = Utils.loadResource(this, R.drawable.d_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[46] = Utils.loadResource(this, R.drawable.d_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[47] = Utils.loadResource(this, R.drawable.d_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[48] = Utils.loadResource(this, R.drawable.d_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[49] = Utils.loadResource(this, R.drawable.d_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[50] = Utils.loadResource(this, R.drawable.d_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[51] = Utils.loadResource(this, R.drawable.d_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[52] = Utils.loadResource(this, R.drawable.d_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[53] = Utils.loadResource(this, R.drawable.d_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[54] = Utils.loadResource(this, R.drawable.d_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);

                baseImage[55] = Utils.loadResource(this, R.drawable.heart, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[56] = Utils.loadResource(this, R.drawable.h_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[57] = Utils.loadResource(this, R.drawable.h_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[58] = Utils.loadResource(this, R.drawable.h_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[59] = Utils.loadResource(this, R.drawable.h_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[60] = Utils.loadResource(this, R.drawable.h_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[61] = Utils.loadResource(this, R.drawable.h_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[62] = Utils.loadResource(this, R.drawable.h_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[63] = Utils.loadResource(this, R.drawable.h_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[64] = Utils.loadResource(this, R.drawable.h_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[65] = Utils.loadResource(this, R.drawable.h_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);

                baseImage[66] = Utils.loadResource(this, R.drawable.question, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //question orb
                baseImage[67] = Utils.loadResource(this, R.drawable.stone, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //stone orb
                baseImage[68] = Utils.loadResource(this, R.drawable.x, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //random weather orb
    //lazy implementation of template matching
    private int compareImage(Mat tmp) {
        //Imgproc.cvtColor(tmp, tmp, Imgproc.COLOR_RGBA2BGR);

        int matchedIndex = -1;
        double maxScore = 0.0;
        double threshold = 0.9;
        //match against 3 abnormal orb
        for(int i=66; i<69; i++) {
            double maxVal = compareImage(baseImage[i], tmp);
            if(maxVal >= threshold && maxVal > maxScore) {
                maxScore = maxVal;
                matchedIndex = i;
                if(maxVal == 1.0) {
                    return matchedIndex;
                }
            }
        }
        if(matchedIndex < 0) {
            int[] normalOrb = new int[]{0, 11, 22, 33, 44, 55};
            //match against 6 different normal element orb to find root
            for(int i=0; i<normalOrb.length; i++) {
                //Log.d("idx", Integer.toString(normalOrb[i]));
                //Log.d("t.row", Boolean.toString(baseImage[normalOrb[i]].rows() > tmp.rows()));
                //Log.d("t.row", Boolean.toString(baseImage[normalOrb[i]].cols() > tmp.cols()));

                double maxVal = compareImage(baseImage[normalOrb[i]], tmp);
                if (maxVal > maxScore) {
                    maxScore = maxVal;
                    matchedIndex = normalOrb[i];
                    if(maxVal == 1.0) {
                        return matchedIndex;
                    }
                }
            }
            //match against same element orb of different properties
            int last_idx = matchedIndex;
            for(int j=last_idx+1; j<last_idx+10; j++) {
                //Log.d("idx", Integer.toString(j));
                double maxVal = compareImage(baseImage[j], tmp);
                if(maxVal > maxScore) {
                    maxScore = maxVal;
                    matchedIndex = j;
                    if(maxVal == 1.0) {
                        break;
                    }
                }
            }
        }

        return matchedIndex;

    }

    private double compareImage(Mat source, Mat tmp) {

        if(source.rows() > tmp.rows() && source.cols() > tmp.cols()) {
            int r_rows = source.rows() - tmp.rows() + 1;
            int r_cols = source.cols() - tmp.cols() + 1;
            Mat result = new Mat();

            result.create(r_rows, r_cols, CvType.CV_32F);

            Imgproc.matchTemplate(source, tmp, result, Imgproc.TM_CCOEFF_NORMED);
            //Core.normalize(result, result, 0, 1, Core.NORM_MINMAX, -1, new Mat());
            Core.MinMaxLocResult mmr = Core.minMaxLoc(result);
            double maxVal = mmr.maxVal;
            return maxVal;
        } else {
            Log.d("Compare failed", "Changing to Monotone mode.");
            Imgproc.cvtColor(source, source, Imgproc.COLOR_RGBA2GRAY);
            Mat mTmp = new Mat();
            tmp.copyTo(mTmp);
            Imgproc.cvtColor(mTmp, mTmp, Imgproc.COLOR_RGBA2GRAY);
            if(source.rows() < tmp.rows() && source.cols() < tmp.cols()) {
                Mat copy = new Mat();
                source.copyTo(copy);
                tmp.copyTo(source);
                copy.copyTo(tmp);
            }

            Log.d("DepthMatch?", Boolean.toString(mTmp.depth()== CV_32F));

            int r_rows = (source.rows() > tmp.rows() && source.cols() > tmp.cols())? (source.rows() - mTmp.rows() + 1) : (mTmp.rows() - source.rows() + 1);
            Log.d("r_rows",""+r_rows);
            int r_cols = (source.rows() > tmp.rows() && source.cols() > tmp.cols())? (source.cols() - mTmp.cols() + 1) : (mTmp.cols() - source.cols() + 1);
            Log.d("r_cols",""+r_cols);
            Mat result = new Mat();

            result.create(r_rows, r_cols, CvType.CV_32F);

            Imgproc.matchTemplate(source, mTmp, result, Imgproc.TM_CCOEFF_NORMED);
            //Core.normalize(result, result, 0, 1, Core.NORM_MINMAX, -1, new Mat());
            Core.MinMaxLocResult mmr = Core.minMaxLoc(result);
            double maxVal = mmr.maxVal;
            return maxVal;
        }

Histogram comparison give me erroneous result, but rather fast while Template Matching offers more promising result but at cost of slower speed. For my case, each captured image needs to be compared with 19 source images as worst case, Template Matching consumes at least few seconds for just a 6x6 board. Is there any suggestion on other approach for the aforementioned scenario, or any possibly fixes to my code to generate better match at faster pace?

Edit: Below screenshot shows that Template Matching using Imgproc.TM_CCOEFF_NORMED yield consistent result but mostly incorrect. The smaller orb icon is the result generated by template matching, while the larger in the back is the orb on the game board. The code in above take result with the max value as closest matching. simulation result

How to classify image of object on a game board

UPDATE : I have tested implementation on PHash, ORB, AKAZE, Template Matching, Histogram Matching.

Imagine I have a squared game board the same as Match-3 game (namely Candy Crush, Puzzle and Dragon, etc), my objective is to detect the orb type on their screen and save the result as an array. I have a set of orbs image with transparent background as the source image.

Game board of P&D

Source orb images

I worked on capturing the board screen and splitting the image into chunks of small square image each containing the orb but with the board color in the background.

I tried Histogram matching (both 4 methods) but all return false result. Template Matching yield only some correct matches but mostly incorrect. Possibly due to the background color of the board in the captured image. And in some cases, if my source image is an orb in grayscale (most image is colored), I got this error while comparing my captured image (colored) with the grayscale source image.

OpenCV(3.4.3) Error: Assertion failed ((depth == CV_8U || depth == CV_32F) && type == _templ.type() && _img.dims() <= 2) in void cv::matchTemplate(cv::InputArray, cv::InputArray, cv::OutputArray, int, cv::InputArray), file /build/3_4_pack-android/opencv/modules/imgproc/src/templmatch.cpp, line 1102
E/org.opencv.imgproc: imgproc::matchTemplate_11() caught cv::Exception: OpenCV(3.4.3) /build/3_4_pack-android/opencv/modules/imgproc/src/templmatch.cpp:1102: error: (-215:Assertion failed) (depth == CV_8U || depth == CV_32F) && type == _templ.type() && _img.dims() <= 2 in function 'void cv::matchTemplate(cv::InputArray, cv::InputArray, cv::OutputArray, int, cv::InputArray)'

with this code:

baseImage = new Mat[69];
                baseImage[0] = Utils.loadResource(this, R.drawable.water, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //normal orb
                baseImage[1] = Utils.loadResource(this, R.drawable.w_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //shiny orb
                baseImage[2] = Utils.loadResource(this, R.drawable.w_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //weather orb
                baseImage[3] = Utils.loadResource(this, R.drawable.w_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //shiny weather orb
                baseImage[4] = Utils.loadResource(this, R.drawable.w_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //monotone orb
                baseImage[5] = Utils.loadResource(this, R.drawable.w_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //shiny monotone orb
                baseImage[6] = Utils.loadResource(this, R.drawable.w_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //targeted orb
                baseImage[7] = Utils.loadResource(this, R.drawable.w_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //initial froze orb
                baseImage[8] = Utils.loadResource(this, R.drawable.w_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //completely frozen orb
                baseImage[9] = Utils.loadResource(this, R.drawable.w_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //second stage frozen orb
                baseImage[10] = Utils.loadResource(this, R.drawable.w_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //last stage frozen orb

                baseImage[11] = Utils.loadResource(this, R.drawable.fire, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[12] = Utils.loadResource(this, R.drawable.f_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[13] = Utils.loadResource(this, R.drawable.f_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[14] = Utils.loadResource(this, R.drawable.f_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[15] = Utils.loadResource(this, R.drawable.f_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[16] = Utils.loadResource(this, R.drawable.f_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[17] = Utils.loadResource(this, R.drawable.f_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[18] = Utils.loadResource(this, R.drawable.f_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[19] = Utils.loadResource(this, R.drawable.f_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[20] = Utils.loadResource(this, R.drawable.f_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[21] = Utils.loadResource(this, R.drawable.f_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);

                baseImage[22] = Utils.loadResource(this, R.drawable.wood, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[23] = Utils.loadResource(this, R.drawable.p_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[24] = Utils.loadResource(this, R.drawable.p_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[25] = Utils.loadResource(this, R.drawable.p_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[26] = Utils.loadResource(this, R.drawable.p_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[27] = Utils.loadResource(this, R.drawable.p_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[28] = Utils.loadResource(this, R.drawable.p_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[29] = Utils.loadResource(this, R.drawable.p_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[30] = Utils.loadResource(this, R.drawable.p_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[31] = Utils.loadResource(this, R.drawable.p_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[32] = Utils.loadResource(this, R.drawable.p_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);

                baseImage[33] = Utils.loadResource(this, R.drawable.light, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[34] = Utils.loadResource(this, R.drawable.l_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[35] = Utils.loadResource(this, R.drawable.l_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[36] = Utils.loadResource(this, R.drawable.l_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[37] = Utils.loadResource(this, R.drawable.l_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[38] = Utils.loadResource(this, R.drawable.l_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[39] = Utils.loadResource(this, R.drawable.l_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[40] = Utils.loadResource(this, R.drawable.l_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[41] = Utils.loadResource(this, R.drawable.l_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[42] = Utils.loadResource(this, R.drawable.l_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[43] = Utils.loadResource(this, R.drawable.l_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);

                baseImage[44] = Utils.loadResource(this, R.drawable.dark, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[45] = Utils.loadResource(this, R.drawable.d_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[46] = Utils.loadResource(this, R.drawable.d_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[47] = Utils.loadResource(this, R.drawable.d_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[48] = Utils.loadResource(this, R.drawable.d_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[49] = Utils.loadResource(this, R.drawable.d_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[50] = Utils.loadResource(this, R.drawable.d_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[51] = Utils.loadResource(this, R.drawable.d_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[52] = Utils.loadResource(this, R.drawable.d_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[53] = Utils.loadResource(this, R.drawable.d_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[54] = Utils.loadResource(this, R.drawable.d_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);

                baseImage[55] = Utils.loadResource(this, R.drawable.heart, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[56] = Utils.loadResource(this, R.drawable.h_s, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[57] = Utils.loadResource(this, R.drawable.h_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[58] = Utils.loadResource(this, R.drawable.h_s_w, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[59] = Utils.loadResource(this, R.drawable.h_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[60] = Utils.loadResource(this, R.drawable.h_s_mono, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[61] = Utils.loadResource(this, R.drawable.h_target, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[62] = Utils.loadResource(this, R.drawable.h_i0, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[63] = Utils.loadResource(this, R.drawable.h_i1, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[64] = Utils.loadResource(this, R.drawable.h_i2, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
                baseImage[65] = Utils.loadResource(this, R.drawable.h_i3, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);

                baseImage[66] = Utils.loadResource(this, R.drawable.question, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //question orb
                baseImage[67] = Utils.loadResource(this, R.drawable.stone, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //stone orb
                baseImage[68] = Utils.loadResource(this, R.drawable.x, Imgcodecs.CV_LOAD_IMAGE_UNCHANGED); //random weather orb
    //lazy implementation of template matching
    private int compareImage(Mat tmp) {
        //Imgproc.cvtColor(tmp, tmp, Imgproc.COLOR_RGBA2BGR);

        int matchedIndex = -1;
        double maxScore = 0.0;
        double threshold = 0.9;
        //match against 3 abnormal orb
        for(int i=66; i<69; i++) {
            double maxVal = compareImage(baseImage[i], tmp);
            if(maxVal >= threshold && maxVal > maxScore) {
                maxScore = maxVal;
                matchedIndex = i;
                if(maxVal == 1.0) {
                    return matchedIndex;
                }
            }
        }
        if(matchedIndex < 0) {
            int[] normalOrb = new int[]{0, 11, 22, 33, 44, 55};
            //match against 6 different normal element orb to find root
            for(int i=0; i<normalOrb.length; i++) {
                //Log.d("idx", Integer.toString(normalOrb[i]));
                //Log.d("t.row", Boolean.toString(baseImage[normalOrb[i]].rows() > tmp.rows()));
                //Log.d("t.row", Boolean.toString(baseImage[normalOrb[i]].cols() > tmp.cols()));

                double maxVal = compareImage(baseImage[normalOrb[i]], tmp);
                if (maxVal > maxScore) {
                    maxScore = maxVal;
                    matchedIndex = normalOrb[i];
                    if(maxVal == 1.0) {
                        return matchedIndex;
                    }
                }
            }
            //match against same element orb of different properties
            int last_idx = matchedIndex;
            for(int j=last_idx+1; j<last_idx+10; j++) {
                //Log.d("idx", Integer.toString(j));
                double maxVal = compareImage(baseImage[j], tmp);
                if(maxVal > maxScore) {
                    maxScore = maxVal;
                    matchedIndex = j;
                    if(maxVal == 1.0) {
                        break;
                    }
                }
            }
        }

        return matchedIndex;

    }

    private double compareImage(Mat source, Mat tmp) {

        if(source.rows() > tmp.rows() && source.cols() > tmp.cols()) {
            int r_rows = source.rows() - tmp.rows() + 1;
            int r_cols = source.cols() - tmp.cols() + 1;
            Mat result = new Mat();

            result.create(r_rows, r_cols, CvType.CV_32F);

            Imgproc.matchTemplate(source, tmp, result, Imgproc.TM_CCOEFF_NORMED);
            //Core.normalize(result, result, 0, 1, Core.NORM_MINMAX, -1, new Mat());
            Core.MinMaxLocResult mmr = Core.minMaxLoc(result);
            double maxVal = mmr.maxVal;
            return maxVal;
        } else {
            Log.d("Compare failed", "Changing to Monotone mode.");
            Imgproc.cvtColor(source, source, Imgproc.COLOR_RGBA2GRAY);
            Mat mTmp = new Mat();
            tmp.copyTo(mTmp);
            Imgproc.cvtColor(mTmp, mTmp, Imgproc.COLOR_RGBA2GRAY);
            if(source.rows() < tmp.rows() && source.cols() < tmp.cols()) {
                Mat copy = new Mat();
                source.copyTo(copy);
                tmp.copyTo(source);
                copy.copyTo(tmp);
            }

            Log.d("DepthMatch?", Boolean.toString(mTmp.depth()== CV_32F));

            int r_rows = (source.rows() > tmp.rows() && source.cols() > tmp.cols())? (source.rows() - mTmp.rows() + 1) : (mTmp.rows() - source.rows() + 1);
            Log.d("r_rows",""+r_rows);
            int r_cols = (source.rows() > tmp.rows() && source.cols() > tmp.cols())? (source.cols() - mTmp.cols() + 1) : (mTmp.cols() - source.cols() + 1);
            Log.d("r_cols",""+r_cols);
            Mat result = new Mat();

            result.create(r_rows, r_cols, CvType.CV_32F);

            Imgproc.matchTemplate(source, mTmp, result, Imgproc.TM_CCOEFF_NORMED);
            //Core.normalize(result, result, 0, 1, Core.NORM_MINMAX, -1, new Mat());
            Core.MinMaxLocResult mmr = Core.minMaxLoc(result);
            double maxVal = mmr.maxVal;
            return maxVal;
        }

Histogram comparison comparison give me erroneous result, but rather fast while Template Matching Matching offers more promising result but at cost of slower speed. Both AKAZE and ORB gave slighly better result but is slower than template matching. For my case, each captured image needs to be compared with 19 source images as worst case, Template Matching Matching consumes at least few seconds for just a 6x6 board. Is there any suggestion on other approach for the aforementioned scenario, or any possibly fixes to my code to generate better match at faster pace?

Edit: Below screenshot shows that Template Matching using Imgproc.TM_CCOEFF_NORMED yield consistent result but mostly incorrect. The smaller orb icon is the result generated by template matching, while the larger in the back is the orb on the game board. The code in above take result with the max value as closest matching. simulation resultTemplate matching result

Below is the result from AKAZE and ORB. It is very slow for my use case. AKAZE result