画像の一致と見なされるものについては、人によってさまざまな要件があることが多いため、一般的にこの問題に答えるのは困難です。提供するテンプレート画像とは異なるサイズまたは向きの画像を検索したい人もいるかもしれません。その場合、スケールまたは回転不変のアプローチが必要です。類似したテクスチャ、機能、または形状を探すなど、さまざまなオプションがありますが、テンプレート画像とまったく同じ位置にある類似した色のピクセルのみを探すアプローチに焦点を当てます。これは、テンプレート マッチングのカテゴリに該当すると思われる例に最も適しているようです。
可能なアプローチ
この場合、問題は相互相関と畳み込みの信号処理の概念に密接に関連しています。これは、非常に高速であるため、 FFTを使用して実装されることがよくあります (その名前です!)。これは、リンク先のアプローチで使用されたものであり、Java のラッパーがあるため、そのような実装を試みるときにFFTWライブラリが役立つ可能性があります。この質問や有名なワルドの質問に見られるように、相互相関の使用は非常にうまく機能します。
もう 1 つのオプションは、すべてのピクセルを比較に使用するのではなく、見つけやすく、固有である可能性が高い特徴のみを使用することです。これには、 SIFTやSURFなどの機能記述子が必要です。両方の画像ですべてのフィーチャを見つけてから、テンプレート画像のフィーチャと同様の位置にあるフィーチャを探す必要があります。このアプローチでは、JavaCVを使用することをお勧めします。
あなたが言及したランダムな推測アプローチは、可能な場合は高速に機能するはずですが、残念ながら、正しい場所の近くで密接に一致する特定の画像の組み合わせでのみ役立つため、一般的には適用できません。
外部ライブラリを使用しない限り、Java で最も単純な方法は、私がブルート フォース アプローチと呼んでいるものですが、少し時間がかかります。力ずくのアプローチでは、探している画像に最もよく一致する部分領域を画像全体から検索するだけです。このアプローチについてさらに説明します。最初に、同じサイズの 2 つの画像の類似性を判断する方法を定義する必要があります。これは、RGB 値の違いの定義を必要とするピクセルの色の違いを合計することで実行できます。
色の類似性
2 つの RGB 値の差を決定する 1 つの方法は、ユークリッド距離を使用することです。
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 の間であると仮定a1
) a2
。白い領域の代わりに透過性を使用し、PNG ファイル形式を使用することをお勧めします。これは、画像の色を微妙に歪める非可逆圧縮を使用しないためです。
画像の比較
同じサイズの画像を比較するには、個々のピクセルの差を合計できます。この合計は差の測定値であり、最小の差測定値を持つ画像内の領域を検索できます。画像にサブ画像が含まれているかどうかさえわからない場合はさらに難しくなりますが、これは、差分測定値が高いベスト マッチによって示されます。必要に応じて、サブイメージのサイズと最大可能 RGB 差 (ユークリッド距離と 0 から 1 の RGB 値を持つ sqrt(3)) で除算することにより、差分測定値を 0 と 1 の間に正規化することもできます。 )。ゼロは同一の一致であり、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 画像処理ライブラリの 1 つも役立つ可能性があります。
速度が本当に重要な場合、最良のアプローチは、外部ライブラリを使用する相互相関または機能記述子を使用した実装になると思います。