web-dev-qa-db-ja.com

Javaで写真の中の写真を見つける?

画面からの入力を写真の形式で分析したいのです。大きな画像で画像の一部を識別し、大きな画像内でその座標を取得できるようにしたいと考えています。例:

box

に配置する必要があります

big picture

その結果は、大きな画像では画像の右上隅になり、大きな画像では部品の左下になります。ご覧のとおり、画像の白い部分は関係ありません。基本的に必要なのは緑のフレームだけです。このようなことができるライブラリはありますか?ランタイムは本当に問題ではありません。

これで私がしたいことは、いくつかのランダムなピクセル座標を生成し、その位置で大きな画像の色を認識して、後で緑色のボックスをすばやく認識することです。そして、真ん中の白いボックスが透明である場合、それはどのようにパフォーマンスを低下させますか?

SOで質問が何度も尋ねられました。単一の回答がないようです。解決策は http://werner.yellowcouch.org/Papers/subimg /index.html 。残念ながらそれはC++であり、私は事を理解していません。SOにJava実装があるとよいでしょう。

20
tarrasch

画像の一致と見なされるものにはさまざまな要件があるため、一般に問題を解くのは困難です。一部の人々は、提供するテンプレート画像とは異なるサイズまたは向きを持つ可能性がある画像を検索したい場合があります。その場合、スケールまたは回転不変のアプローチが必要です。類似のテクスチャ、機能、または形状を検索するなど、さまざまなオプションがありますが、テンプレート画像とまったく同じ位置にある類似の色のピクセルのみを検索するアプローチに焦点を当てます。これは テンプレートマッチング のカテゴリに分類される例に最も適しているようです。

可能なアプローチ

この場合、問題は 相互相関 および 畳み込み の信号処理の概念に密接に関連しています。これは、多くの場合 [〜#〜] fftを使用して実装されます[〜#〜] 非常に高速であるため(その名の通り!)これは、あなたが linked を使用するアプローチで使用されたものであり、 [〜#〜] fftw [〜#〜] ライブラリは、次のような実装を試みるときに役立ちます。 Java用のラッパーがあります。 this の質問や有名な waldo の質問に見られるように、相互相関の使用は非常にうまく機能します。

別のオプションは、すべてのピクセルを比較に使用するのではなく、見つけやすく、一意である可能性が高い機能のみを使用することです。 [〜#〜] 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 )

これは、色に透明度がある場合、より小さい値になります(a1およびa2は0〜1の間です。画像の色を微妙に歪める不可逆圧縮を使用しないため、白い領域ではなく透明度を使用し、PNGファイル形式を使用することをお勧めします。

画像の比較

等しいサイズの画像を比較するには、個々のピクセル間の差を合計できます。この合計は、差異の測定値であり、最も低い差異測定値を持つ画像内の領域を検索できます。画像にサブ画像が含まれているかどうかさえわからない場合は、さらに難しくなりますが、これは、差が大きい最良の一致によって示されます。必要に応じて、サブ画像のサイズと可能な最大RGB差(sqrt(3)とユークリッド距離と0から1までのRGB値)で除算して、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つも、いくつかの用途に使用できます。

速度が本当に重要な場合、最善のアプローチは、相互相関または外部ライブラリを使用する機能記述子を使用した実装だと思います。

26
Silveri

必要なのは、マスク/境界で画像ブロックを見つけることです。

外部ライブラリなしで実行できます。低レベルでは、すべての画像は数値の行列であり、マスクも数値の行列です。大きなマトリックスを線形スキャンして、マスクで定義されたルールに従う領域を見つけることができます。

大きな行列:

1 0 1 1 1 1 
0 1 0 1 0 0
0 0 0 1 1 1
0 1 1 0 0 0

マスク:

1 1 1
1 0 0
1 1 1

右上隅の大きなマトリックスで一致するブロックを検出するこのアルゴリズムを適用します。これにより、マトリックスの開始/終了インデックスが提供され、これらの値をピクセルで計算できます。

実際の問題では、番号セットがありません[0, 1]がはるかに大きい-byte例([0, 256])。アルゴリズムをよりよく機能させるために、一致とは正確な​​数値の一致ではなく、多少の偏差+ -5またはこのようなもので可能であることを意味します。

7
mishadoff