/** * Constructs a TemplateMatcher object. If a mask shape is specified, then only pixels that are * entirely inside the mask are included in the template. * * @param image the image to match * @param maskShape a shape to define inside pixels (may be null) */ public TemplateMatcher(BufferedImage image, Shape maskShape) { original = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); original.createGraphics().drawImage(image, 0, 0, null); mask = maskShape; getTemplate(); // set up the Gaussian curve fitter dataset = new Dataset(); fitter = new DatasetCurveFitter(dataset); fitter.setAutofit(true); f = new UserFunction("gaussian"); // $NON-NLS-1$ f.setParameters( new String[] {"a", "b", "c"}, // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ new double[] {1, 0, 1}); f.setExpression("a*exp(-(x-b)^2/c)", new String[] {"x"}); // $NON-NLS-1$ //$NON-NLS-2$ }
/** * Gets the template location at which the best match occurs in a rectangle and along a line. May * return null. * * @param target the image to search * @param searchRect the rectangle to search within the target image * @param x0 the x-component of a point on the line * @param y0 the y-component of a point on the line * @param slope the slope of the line * @param spread the spread of the line (line width = 1+2*spread) * @return the optimized template location of the best match, if any */ public TPoint getMatchLocation( BufferedImage target, Rectangle searchRect, double x0, double y0, double theta, int spread) { wTarget = target.getWidth(); hTarget = target.getHeight(); // determine insets needed to accommodate template int left = wTemplate / 2, right = left; if (wTemplate % 2 > 0) right++; int top = hTemplate / 2, bottom = top; if (hTemplate % 2 > 0) bottom++; // trim search rectangle if necessary searchRect.x = Math.max(left, Math.min(wTarget - right, searchRect.x)); searchRect.y = Math.max(top, Math.min(hTarget - bottom, searchRect.y)); searchRect.width = Math.min(wTarget - searchRect.x - right, searchRect.width); searchRect.height = Math.min(hTarget - searchRect.y - bottom, searchRect.height); if (searchRect.width <= 0 || searchRect.height <= 0) { peakHeight = Double.NaN; peakWidth = Double.NaN; return null; } // set up test pixels to search (rectangle plus template) int xMin = Math.max(0, searchRect.x - left); int xMax = Math.min(wTarget, searchRect.x + searchRect.width + right); int yMin = Math.max(0, searchRect.y - top); int yMax = Math.min(hTarget, searchRect.y + searchRect.height + bottom); wTest = xMax - xMin; hTest = yMax - yMin; if (target.getType() != BufferedImage.TYPE_INT_RGB) { BufferedImage image = new BufferedImage(wTarget, hTarget, BufferedImage.TYPE_INT_RGB); image.createGraphics().drawImage(target, 0, 0, null); target = image; } targetPixels = new int[wTest * hTest]; target.getRaster().getDataElements(xMin, yMin, wTest, hTest, targetPixels); // get the points to search along the line ArrayList<Point2D> searchPts = getSearchPoints(searchRect, x0, y0, theta); if (searchPts == null) { peakHeight = Double.NaN; peakWidth = Double.NaN; return null; } // collect differences in a map as they are measured HashMap<Point2D, Double> diffs = new HashMap<Point2D, Double>(); // find the point with the minimum difference from template double matchDiff = largeNumber; // larger than typical differences int xMatch = 0, yMatch = 0; double avgDiff = 0; Point2D matchPt = null; for (Point2D pt : searchPts) { int x = (int) pt.getX(); int y = (int) pt.getY(); double diff = getDifferenceAtTestPoint(x, y); diffs.put(pt, diff); avgDiff += diff; if (diff < matchDiff) { matchDiff = diff; xMatch = x; yMatch = y; matchPt = pt; } } avgDiff /= searchPts.size(); peakHeight = avgDiff / matchDiff - 1; peakWidth = Double.NaN; double dl = 0; int matchIndex = searchPts.indexOf(matchPt); // if match is not exact, fit a Gaussian and find peak if (!Double.isInfinite(peakHeight) && matchIndex > 0 && matchIndex < searchPts.size() - 1) { // fill data arrays Point2D pt = searchPts.get(matchIndex - 1); double diff = diffs.get(pt); xValues[0] = -pt.distance(matchPt); yValues[0] = avgDiff / diff - 1; xValues[1] = 0; yValues[1] = peakHeight; pt = searchPts.get(matchIndex + 1); diff = diffs.get(pt); xValues[2] = pt.distance(matchPt); yValues[2] = avgDiff / diff - 1; // determine approximate offset (dl) and width (w) values double pull = -xValues[0] / (yValues[1] - yValues[0]); double push = xValues[2] / (yValues[1] - yValues[2]); if (Double.isNaN(pull)) pull = LARGE_NUMBER; if (Double.isNaN(push)) push = LARGE_NUMBER; dl = 0.3 * (xValues[2] - xValues[0]) * (push - pull) / (push + pull); double ratio = dl > 0 ? peakHeight / yValues[0] : peakHeight / yValues[2]; double w = dl > 0 ? dl - xValues[0] : dl - xValues[2]; w = w * w / Math.log(ratio); // set parameters and fit to x data dataset.clear(); dataset.append(xValues, yValues); double rmsDev = 1; for (int k = 0; k < 3; k++) { double c = k == 0 ? w : k == 1 ? w / 3 : w * 3; f.setParameterValue(0, peakHeight); f.setParameterValue(1, dl); f.setParameterValue(2, c); rmsDev = fitter.fit(f); if (rmsDev < 0.01) { // fitter succeeded (3-point fit should be exact) dl = f.getParameterValue(1); peakWidth = f.getParameterValue(2); break; } } } double dx = dl * Math.cos(theta); double dy = dl * Math.sin(theta); double xImage = xMatch + searchRect.x - left - trimLeft + dx; double yImage = yMatch + searchRect.y - top - trimTop + dy; return new TPoint(xImage, yImage); }
/** * Gets the template location at which the best match occurs in a rectangle. May return null. * * @param target the image to search * @param searchRect the rectangle to search within the target image * @return the optimized template location at which the best match, if any, is found */ public TPoint getMatchLocation(BufferedImage target, Rectangle searchRect) { wTarget = target.getWidth(); hTarget = target.getHeight(); // determine insets needed to accommodate template int left = wTemplate / 2, right = left; if (wTemplate % 2 > 0) right++; int top = hTemplate / 2, bottom = top; if (hTemplate % 2 > 0) bottom++; // trim search rectangle if necessary searchRect.x = Math.max(left, Math.min(wTarget - right, searchRect.x)); searchRect.y = Math.max(top, Math.min(hTarget - bottom, searchRect.y)); searchRect.width = Math.min(wTarget - searchRect.x - right, searchRect.width); searchRect.height = Math.min(hTarget - searchRect.y - bottom, searchRect.height); if (searchRect.width <= 0 || searchRect.height <= 0) { peakHeight = Double.NaN; peakWidth = Double.NaN; return null; } // set up test pixels to search (rectangle plus template) int xMin = Math.max(0, searchRect.x - left); int xMax = Math.min(wTarget, searchRect.x + searchRect.width + right); int yMin = Math.max(0, searchRect.y - top); int yMax = Math.min(hTarget, searchRect.y + searchRect.height + bottom); wTest = xMax - xMin; hTest = yMax - yMin; if (target.getType() != BufferedImage.TYPE_INT_RGB) { BufferedImage image = new BufferedImage(wTarget, hTarget, BufferedImage.TYPE_INT_RGB); image.createGraphics().drawImage(target, 0, 0, null); target = image; } targetPixels = new int[wTest * hTest]; target.getRaster().getDataElements(xMin, yMin, wTest, hTest, targetPixels); // find the rectangle point with the minimum difference double matchDiff = largeNumber; // larger than typical differences int xMatch = 0, yMatch = 0; double avgDiff = 0; for (int x = 0; x <= searchRect.width; x++) { for (int y = 0; y <= searchRect.height; y++) { double diff = getDifferenceAtTestPoint(x, y); avgDiff += diff; if (diff < matchDiff) { matchDiff = diff; xMatch = x; yMatch = y; } } } avgDiff /= (searchRect.width * searchRect.height); peakHeight = avgDiff / matchDiff - 1; peakWidth = Double.NaN; double dx = 0, dy = 0; // if match is not exact, fit a Gaussian and find peak if (!Double.isInfinite(peakHeight)) { // fill data arrays xValues[1] = yValues[1] = peakHeight; for (int i = -1; i < 2; i++) { if (i == 0) continue; double diff = getDifferenceAtTestPoint(xMatch + i, yMatch); xValues[i + 1] = avgDiff / diff - 1; diff = getDifferenceAtTestPoint(xMatch, yMatch + i); yValues[i + 1] = avgDiff / diff - 1; } // estimate peakHeight = peak of gaussian // estimate offset dx of gaussian double pull = 1 / (xValues[1] - xValues[0]); double push = 1 / (xValues[1] - xValues[2]); if (Double.isNaN(pull)) pull = LARGE_NUMBER; if (Double.isNaN(push)) push = LARGE_NUMBER; dx = 0.6 * (push - pull) / (push + pull); // estimate width wx of gaussian double ratio = dx > 0 ? peakHeight / xValues[0] : peakHeight / xValues[2]; double wx = dx > 0 ? dx + 1 : dx - 1; wx = wx * wx / Math.log(ratio); // estimate offset dy of gaussian pull = 1 / (yValues[1] - yValues[0]); push = 1 / (yValues[1] - yValues[2]); if (Double.isNaN(pull)) pull = LARGE_NUMBER; if (Double.isNaN(push)) push = LARGE_NUMBER; dy = 0.6 * (push - pull) / (push + pull); // estimate width wy of gaussian ratio = dy > 0 ? peakHeight / yValues[0] : peakHeight / yValues[2]; double wy = dy > 0 ? dy + 1 : dy - 1; wy = wy * wy / Math.log(ratio); // set x parameters and fit to x data dataset.clear(); dataset.append(pixelOffsets, xValues); double rmsDev = 1; for (int k = 0; k < 3; k++) { double c = k == 0 ? wx : k == 1 ? wx / 3 : wx * 3; f.setParameterValue(0, peakHeight); f.setParameterValue(1, dx); f.setParameterValue(2, c); rmsDev = fitter.fit(f); if (rmsDev < 0.01) { // fitter succeeded (3-point fit should be exact) dx = f.getParameterValue(1); peakWidth = f.getParameterValue(2); break; } } if (!Double.isNaN(peakWidth)) { // set y parameters and fit to y data dataset.clear(); dataset.append(pixelOffsets, yValues); for (int k = 0; k < 3; k++) { double c = k == 0 ? wy : k == 1 ? wy / 3 : wy * 3; f.setParameterValue(0, peakHeight); f.setParameterValue(1, dx); f.setParameterValue(2, c); rmsDev = fitter.fit(f); if (rmsDev < 0.01) { // fitter succeeded (3-point fit should be exact) dy = f.getParameterValue(1); peakWidth = (peakWidth + f.getParameterValue(2)) / 2; break; } } if (rmsDev > 0.01) peakWidth = Double.NaN; } } double xImage = xMatch + searchRect.x - left - trimLeft + dx; double yImage = yMatch + searchRect.y - top - trimTop + dy; return new TPoint(xImage, yImage); }