All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.martinschneider.justtestlah.visual.TemplateMatcher Maven / Gradle / Ivy

package io.github.martinschneider.justtestlah.visual;

import io.github.martinschneider.justtestlah.configuration.JustTestLahConfiguration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.opencv.core.Core;
import org.opencv.core.Core.MinMaxLocResult;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Template matcher
 *
 * 

This class provides methods to check whether a given image (template) is part of another one * (target). We use a simple (yet effective) way to detect the template image in various sizes by * scaling the target up and down to a minimum and maximum size. * *

We return on the first match that exceeds the specific threshold (matching quality). This * means that it is not necessarily the best possible match. */ public class TemplateMatcher { private static final int MIN_IMAGE_WIDTH = 320; private static final int MAX_IMAGE_WIDTH = 2048; private static final Logger LOG = LoggerFactory.getLogger(TemplateMatcher.class); private static final String FILE_EXTENSION = "png"; private static final String DATE_PATTERN = "yyyy-MM-dd HH.mm.ss"; private JustTestLahConfiguration configuration; /** * Check whether the template appears anywhere within the target image. * * @param targetFile path to the target file * @param templateFile path to the template file * @param threshold matching threshold * @return {@link Match} */ public Match match(String targetFile, String templateFile, double threshold) { return match( targetFile, templateFile, threshold, LocalDateTime.now().format(DateTimeFormatter.ofPattern(DATE_PATTERN))); } /** * Check whether the template appears anywhere within the target image. * * @param targetFile path to the target file * @param templateFile path to the template file * @param threshold matching threshold * @param description of the check * @return {@link Match} */ public Match match(String targetFile, String templateFile, double threshold, String description) { checkForOpenCv(); Mat image = Imgcodecs.imread(targetFile); Mat templ = Imgcodecs.imread(templateFile); MinMaxLocResult bestMatch = new MinMaxLocResult(); Mat originalImage = image; /** * This could be improved by combining the two while loops (checking the original size first, * then slightly smaller and larger images etc. instead of first scaling all the way down and * then scaling up (or by using a framework that performs size-invariant template matching). */ while (image.width() > MIN_IMAGE_WIDTH) { int resultCols = image.cols() - templ.cols() + 1; int resultRows = image.rows() - templ.rows() + 1; Mat result = new Mat(resultRows, resultCols, CvType.CV_32FC1); Imgproc.matchTemplate(image, templ, result, Imgproc.TM_CCOEFF_NORMED); MinMaxLocResult match = Core.minMaxLoc(result); if (match.maxVal > bestMatch.maxVal) { bestMatch = match; } if (bestMatch.maxVal >= threshold) { break; } // else image = scaleImage(image, 0.9); } if (bestMatch.maxVal < threshold) { image = originalImage; } while (bestMatch.maxVal < threshold && image.width() < MAX_IMAGE_WIDTH) { int resultCols = image.cols() - templ.cols() + 1; int resultRows = image.rows() - templ.rows() + 1; Mat result = new Mat(resultRows, resultCols, CvType.CV_32FC1); Imgproc.matchTemplate(image, templ, result, Imgproc.TM_CCOEFF_NORMED); MinMaxLocResult match = Core.minMaxLoc(result); if (match.maxVal > bestMatch.maxVal) { bestMatch = match; } if (match.maxVal >= threshold) { break; } // else image = scaleImage(image, 1.1); } if (bestMatch.maxVal >= threshold) { LOG.info( "Image {} contains image {} with match quality {}", targetFile, templateFile, bestMatch.maxVal); Imgproc.rectangle( image, new Point(bestMatch.maxLoc.x, bestMatch.maxLoc.y), new Point(bestMatch.maxLoc.x + templ.cols(), bestMatch.maxLoc.y + templ.rows()), new Scalar(255, 0, 0), 10); String fileName = System.getProperty("user.dir") + "/target/" + description + "." + FILE_EXTENSION; LOG.info("Writing result of template matching to {}", fileName); Imgcodecs.imwrite(fileName, image); return new Match( true, (int) Math.round(bestMatch.maxLoc.x + templ.cols() / 2), (int) Math.round(bestMatch.maxLoc.y + templ.rows() / 2)); } // else LOG.info( "Image {} does not contain image {}. The closest match has quality {}", targetFile, templateFile, bestMatch.maxVal); return new Match(false); } @Autowired public void setConfiguration(JustTestLahConfiguration configuration) { this.configuration = configuration; } /** Check whether OpenCV is enabled. */ private void checkForOpenCv() { if (!configuration.isOpenCvEnabled()) { throw new UnsupportedOperationException( "OpenCV is not enabled. Set opencv.enabled=true in justtestlah.properties!"); } } private Mat scaleImage(Mat image, double scaleFactor) { Mat resizedImage = new Mat(); Size sz = new Size(image.width() * scaleFactor, image.height() * scaleFactor); Imgproc.resize(image, resizedImage, sz); return resizedImage; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy