这个问题通常很难回答,因为人们通常对图像匹配有不同的要求。有些人可能希望搜索可能与他们提供的模板图像具有不同大小或方向的图像,在这种情况下,需要采用缩放或旋转不变的方法。有各种选项,例如查找相似的纹理,特征或形状,但我将重点介绍仅查找与模板图像处于完全相同位置的相似颜色的像素的方法。这似乎最适合您的示例,该示例似乎属于模板匹配类别。
可能的方法
在这种情况下,问题与互相关和卷积的信号处理概念密切相关,这通常使用FFT实现,因为它非常快(它在名称中!这就是您链接到的方法中使用的方法,并且FFTW库在尝试此类实现时可能有用,因为它具有Java的包装器。使用互相关效果很好,如这个问题以及著名的waldo问题所示。
另一种选择不是使用所有像素进行比较,而是仅使用更易于查找且更可能唯一的特征。这将需要一个功能描述符,如SIFT,SURF或许多其他功能描述符之一。您需要查找两个图像中的所有要素,然后查找与模板图像中具有相似位置的要素。通过这种方法,我建议你使用JavaCV。
您提到的随机猜测方法应该在可能的情况下快速工作,但不幸的是,它通常不适用,因为它仅适用于某些在正确位置附近产生紧密匹配的图像组合。
除非你使用外部库,否则Java中最简单的方法就是我所说的暴力破解方法,尽管它有点慢。暴力破解方法仅涉及在整个映像中搜索与您要查找的映像最匹配的子区域。我将进一步解释这种方法。首先,您需要定义如何确定两个大小相等的图像之间的相似性。这可以通过对像素颜色之间的差异求和来完成,这需要定义RGB值之间的差异。
颜色相似性
确定两个 RGB 值之间差值的一种方法是使用欧氏距离:
sqrt( (r1-r2)^2 + (g1-g2)^2 + (b1-b2)^2 )
可以使用与RGB不同的色彩空间,但由于您的子图像很可能几乎相同(而不仅仅是视觉上相似),因此这应该可以正常工作。如果您有 ARGB 色彩空间,并且不希望半透明像素对结果产生太大影响,则可以使用:
a1 * a2 * sqrt( (r1-r2)^2 + (g1-g2)^2 + (b1-b2)^2 )
如果颜色具有透明度(假设并且介于 0 和 1 之间),则将给出较小的值。我建议您使用透明度而不是白色区域,并使用PNG文件格式,因为它不使用有损压缩来巧妙地扭曲图像中的颜色。a1
a2
比较图像
要比较大小相等的图像,您可以对其各个像素之间的差异求和。然后,此总和是差值的度量值,您可以在图像中搜索差异度量值最低的区域。如果您甚至不知道图像是否包含子图像,则会变得更加困难,但这可以通过具有高差值度量的最佳匹配来指示。如果需要,还可以将差值度量值归一化为介于 0 和 1 之间,方法是将其除以子图像的大小和最大可能的 RGB 差值(sqrt(3),其中欧氏距离和 RGB 值介于 0 到 1 之间)。然后,零将是一个相同的匹配项,任何接近一的匹配项都将尽可能不同。
暴力破解实现
下面是一个简单的实现,它使用暴力方法搜索图像。通过您的示例图像,它发现位于 (139,55) 的位置是具有最佳匹配的区域的左上角位置(看起来是正确的)。在我的PC上运行大约需要10到15秒,位置的标准化差异测量值约为0.57。
/**
* Finds the a region in one image that best matches another, smaller, image.
*/
public static int[] findSubimage(BufferedImage im1, BufferedImage im2){
int w1 = im1.getWidth(); int h1 = im1.getHeight();
int w2 = im2.getWidth(); int h2 = im2.getHeight();
assert(w2 <= w1 && h2 <= h1);
// will keep track of best position found
int bestX = 0; int bestY = 0; double lowestDiff = Double.POSITIVE_INFINITY;
// brute-force search through whole image (slow...)
for(int x = 0;x < w1-w2;x++){
for(int y = 0;y < h1-h2;y++){
double comp = compareImages(im1.getSubimage(x,y,w2,h2),im2);
if(comp < lowestDiff){
bestX = x; bestY = y; lowestDiff = comp;
}
}
}
// output similarity measure from 0 to 1, with 0 being identical
System.out.println(lowestDiff);
// return best location
return new int[]{bestX,bestY};
}
/**
* Determines how different two identically sized regions are.
*/
public static double compareImages(BufferedImage im1, BufferedImage im2){
assert(im1.getHeight() == im2.getHeight() && im1.getWidth() == im2.getWidth());
double variation = 0.0;
for(int x = 0;x < im1.getWidth();x++){
for(int y = 0;y < im1.getHeight();y++){
variation += compareARGB(im1.getRGB(x,y),im2.getRGB(x,y))/Math.sqrt(3);
}
}
return variation/(im1.getWidth()*im1.getHeight());
}
/**
* Calculates the difference between two ARGB colours (BufferedImage.TYPE_INT_ARGB).
*/
public static double compareARGB(int rgb1, int rgb2){
double r1 = ((rgb1 >> 16) & 0xFF)/255.0; double r2 = ((rgb2 >> 16) & 0xFF)/255.0;
double g1 = ((rgb1 >> 8) & 0xFF)/255.0; double g2 = ((rgb2 >> 8) & 0xFF)/255.0;
double b1 = (rgb1 & 0xFF)/255.0; double b2 = (rgb2 & 0xFF)/255.0;
double a1 = ((rgb1 >> 24) & 0xFF)/255.0; double a2 = ((rgb2 >> 24) & 0xFF)/255.0;
// if there is transparency, the alpha values will make difference smaller
return a1*a2*Math.sqrt((r1-r2)*(r1-r2) + (g1-g2)*(g1-g2) + (b1-b2)*(b1-b2));
}
我没有看过,但也许这些Java图像处理库之一也可能有一些用处:
如果速度真的很重要,我认为最好的方法是使用互相关或使用外部库的功能描述符来实现。