0

I am running template matching using openCV 3.4.7 Android SDK (java). The code work almost perfectly; when the template is match, it draws a rectangle on the matching area. The problem is that even when there is no match, it draws a random rectangle. I think that happens because the threshold is not set correctly. If so, can someone please help me out?

Here's the code:

public static void run(String inFile, String templateFile, String outFile,
                    int match_method) {
        Mat img = Imgcodecs.imread(inFile);
        Mat templ = Imgcodecs.imread(templateFile);

        // / Create the result matrix
        int result_cols = img.cols() - templ.cols() + 1;
        int result_rows = img.rows() - templ.rows() + 1;
        Mat result = new Mat(result_rows, result_cols, CvType.CV_32FC1);

        // / Do the Matching and Normalize
        Imgproc.matchTemplate(img, templ, result, match_method);
        Core.normalize(result, result, 0, 1, Core.NORM_MINMAX, -1, new Mat());

        // / Localizing the best match with minMaxLoc
        Core.MinMaxLocResult mmr = Core.minMaxLoc(result);

        Point matchLoc;
        if (match_method == Imgproc.TM_SQDIFF
                || match_method == Imgproc.TM_SQDIFF_NORMED) {
            matchLoc = mmr.minLoc;
        } else {
            matchLoc = mmr.maxLoc;
        }

        // / Show me what you got
        Imgproc.rectangle(img, matchLoc, new Point(matchLoc.x + templ.cols(),
                matchLoc.y + templ.rows()), new Scalar(0, 0, 128));

        // Save the visualized detection.
        System.out.println("Writing " + outFile);
        Imgcodecs.imwrite(outFile, img);
}
DeborahAnn
  • 191
  • 1
  • 11

3 Answers3

2

You can use Imgproc.TM_CCOEFF_NORMED or Imgproc.TM_CCORR_NORMED and mmr.maxVal >= 0.8. It should take care of most of your false positives.

Sample Code:

import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import java.io.File;
import java.nio.file.Files;

public class templateMatchingTester {

    private static String str = null;

    static {
        if (str == null) {
            str = "initialised";
            nu.pattern.OpenCV.loadShared();
            System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        }

    }

    private static Mat createMatrixFromImage(String imagePath) {
        Mat imageMatrix = Imgcodecs.imread(imagePath);
        Mat greyImage = new Mat();
        Imgproc.cvtColor(imageMatrix, greyImage, Imgproc.COLOR_BGR2GRAY);
        return greyImage;
    }

    private static boolean matchTemplate(String pathToInputImage,String pathToTemplate){

        Mat inputImage = createMatrixFromImage(pathToInputImage);
        Mat templateImage = createMatrixFromImage(pathToTemplate);

        // Create the result matrix
        int result_cols = inputImage.cols() - templateImage.cols() + 1;
        int result_rows = inputImage.rows() - templateImage.rows() + 1;
        Mat result = new Mat(result_rows, result_cols, CvType.CV_8UC1);
        int match_method;
        match_method = Imgproc.TM_CCOEFF_NORMED;//Imgproc.TM_CCORR_NORMED;
        Imgproc.matchTemplate(inputImage, templateImage, result, match_method);
        Core.MinMaxLocResult mmr = Core.minMaxLoc(result);
        double minMatchQuality = 0.85; 
        System.out.println(mmr.maxVal);
        if (mmr.maxVal >= minMatchQuality){
            return true;
        } else
        return false;
    }

    public static void main(String args[]) {

        String template = "path/to/your/templateImage";
        final File folder = new File("path/to/your/testImagesFolder/");
        int matchCount = 0;
        for (final File fileEntry : folder.listFiles()){
            if (matchTemplate(fileEntry.getPath(),template)){
                matchCount+=1;
            }else
                System.out.println(fileEntry.getPath());
        }
        System.out.println(matchCount);

    }
}
Shubham Jaiswal
  • 359
  • 1
  • 9
  • I have tried but sadly with no success because I always get all results 1.0 or really near the 1.0, making impossible to discard false positives. – DeborahAnn Dec 11 '19 at 13:38
  • can you share your template, a positive image and a negative sample? – Shubham Jaiswal Dec 11 '19 at 14:09
  • @Piglet Please check the above link for the template and images. – DeborahAnn Dec 11 '19 at 16:13
  • 1
    @DeborahAnn I tried template matching on all the images you shared, I didnot get any false results at all. Note: I was using Imgproc.TM_CCOEFF_NORMED with a threshold of 0.85. Could you try the same and revert back with your findings. – Shubham Jaiswal Dec 11 '19 at 17:20
  • @ShubhamJaiswal can you please share the complete code? I remember trying with TM_CCOEFF_NORMED and getting always 1.0 – DeborahAnn Dec 11 '19 at 17:28
1

Use a normed match method to ensure your match value is [0..1].

Replace this line

Core.normalize(result, result, 0, 1, Core.NORM_MINMAX, -1, new Mat());

with a thresholding operation. Otherwise a best match of 0.9 would become 1 by the second normalization and you would lose the actual match "quality" information.

Normalizing the result of the template matching will always result in your best match being 1 making it impossible to discard a bad match.

Piglet
  • 27,501
  • 3
  • 20
  • 43
  • I am not normalizing the result as you suggested and have set a threshold checking the mmr.maxVal (I am suing TM_CCOEFF). Even though I have removed many of false positives, I have still a few false positives. Sadly if I increase the threshold it happens that I discard correct matches. – DeborahAnn Dec 11 '19 at 13:31
  • @DeborahAnn if you cannot get rid of false positives using a global threshold you have multiple things that match as well as the thing you're looking for. then you need another approach. – Piglet Dec 11 '19 at 14:18
0

i wrote an app that would take a screenshot of the game overwatch and attempt to tell who is on each team. using template matching and open cv. project need to iterate over the result image and check values.

OpenCVUtils.getPointsFromMatAboveThreshold(result, 0.90f)

public static void scaleAndCheckAll(String guid){       
    Mat source = imread(IMG_PROC_PATH + guid);  //load the source image
    Mat scaledSrc = new Mat(defaultScreenshotSize, source.type());
    resize(source, scaledSrc, defaultScreenshotSize);
    Mat sourceGrey = new Mat(scaledSrc.size(), CV_8UC1);
    cvtColor(scaledSrc, sourceGrey, COLOR_BGR2GRAY);        

    for (String hero : getCharacters()) {
        Mat template = OpenCVUtils.matFromJar(TEMPLATES_FOLDER + hero + ".png", 0); //load a template
        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);// get results
        Scalar color =  OpenCVUtils.randColor();
        List<Point> points = OpenCVUtils.getPointsFromMatAboveThreshold(result, 
0.90f);
        for (Point point : points) {
            //rectangle(scaledSrc, new Rect(point.x(),point.y(),template.cols(),template.rows()), color, -2, 0, 0);
            putText(scaledSrc, hero, point, FONT_HERSHEY_PLAIN, 2, color);
        }
    }
    String withExt = IMG_PROC_PATH + guid +".png";
    imwrite(withExt,  scaledSrc);
    File noExt = new File(IMG_PROC_PATH + guid);
    File ext = new File(withExt);
    noExt.delete();
    ext.renameTo(noExt);                
}

the other method.

public static List<Point> getPointsFromMatAboveThreshold(Mat m, float t){
    List<Point> matches = new ArrayList<Point>();
    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;
}

you can just get the first from the list or see how close they are if you expect multiple matches.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
mavriksc
  • 1,130
  • 1
  • 7
  • 10
  • I will try your code with my application and let you know. What parts of the code should I remove (like the for (String hero : getCharacters())). Also, what does the method putText(scaledSrc, hero, point, FONT_HERSHEY_PLAIN, 2, color); ? – DeborahAnn Dec 11 '19 at 13:36
  • yes this matches multiple templates against a single image, to try and find the best match. the whole project is just a few classes if you have any questions let me know – mavriksc Dec 11 '19 at 16:09
  • Cannot resolve FloatIndexer and OpenCVUtils.matFromJar. – DeborahAnn Dec 11 '19 at 16:21
  • matfromjar is code form my project that loads an image you don't need that. and i think floatindexer is from javacv. possibly not the same open cv. tyou just need to loop thru all pixels in the result and check the value. – mavriksc Dec 11 '19 at 17:22