web-dev-qa-db-fra.com

Trouver une image dans une image avec java?

ce que je veux, c'est analyser les entrées de l'écran sous forme d'images. Je veux pouvoir identifier une partie d'une image dans une image plus grande et obtenir ses coordonnées dans une image plus grande. Exemple:

box

devrait être situé dans

big picture

Et le résultat serait le coin supérieur droit de l'image dans la grande image et le coin inférieur gauche de la pièce dans la grande image. Comme vous pouvez le voir, la partie blanche de l'image n'est pas pertinente, ce dont j'ai essentiellement besoin est juste le cadre vert. Y a-t-il une bibliothèque qui peut faire quelque chose comme ça pour moi? L'exécution n'est pas vraiment un problème.

Ce que je veux faire avec cela, c'est simplement générer quelques coordonnées de pixels aléatoires et reconnaître la couleur dans la grande image à cette position, pour reconnaître la boîte verte rapidement plus tard. Et comment cela diminuerait-il les performances, si la boîte blanche au milieu était transparente?

La question a été posée plusieurs fois sur SO comme il semble sans une seule réponse. J'ai trouvé que j'ai trouvé une solution sur http://werner.yellowcouch.org/Papers/subimg /index.html Malheureusement, c'est en C++ et je ne comprends rien. Ce serait bien d'avoir une implémentation Java sur SO.

20
tarrasch

Le problème est difficile à répondre en général car les gens ont souvent des exigences différentes pour ce qui compte comme une correspondance d'image. Certaines personnes peuvent vouloir rechercher une image qui pourrait avoir une taille ou une orientation différente de l'image de modèle qu'elles fournissent, auquel cas une approche invariante à l'échelle ou à la rotation est nécessaire. Il existe différentes options telles que la recherche de textures, de caractéristiques ou de formes similaires, mais je me concentrerai sur les approches qui ne recherchent que des pixels de couleur similaire qui se trouvent exactement aux mêmes positions que l'image du modèle. Cela semble plus adapté à votre exemple qui semble appartenir à la catégorie correspondance de modèle .

Approches possibles

Dans ce cas, le problème est étroitement lié aux concepts de traitement du signal de corrélation croisée et convolution , qui est souvent implémenté à l'aide d'un FFT car il est très rapide (c'est dans le nom!). C'est ce qui a été utilisé dans l'approche à laquelle vous lié , et la bibliothèque FFTW pourrait être utile lorsque vous tentez une implémentation telle que il a des wrappers pour Java. L'utilisation de la corrélation croisée fonctionne très bien, comme le montre la question this , ainsi que la fameuse question waldo .

Une autre option consiste à ne pas utiliser tous les pixels à des fins de comparaison, mais plutôt uniquement les fonctionnalités plus faciles à trouver et plus susceptibles d'être uniques. Cela nécessiterait un descripteur de fonctionnalité comme SIFT , SURF ou l'un des nombreux - autres . Vous devez rechercher toutes les fonctionnalités dans les deux images, puis rechercher des fonctionnalités qui ont des positions similaires à celles de l'image du modèle. Avec cette approche, je vous suggère d'utiliser JavaCV .

L'approche de devinettes aléatoires que vous avez mentionnée devrait fonctionner rapidement lorsque cela est possible, mais malheureusement, elle n'est généralement pas applicable car elle ne sera utile qu'avec certaines combinaisons d'images qui produisent une correspondance étroite près du bon emplacement.

À moins que vous n'utilisiez une bibliothèque externe, la méthode la plus simple de Java serait ce que j'appellerais une approche par force brute, bien qu'elle soit un peu lente. L'approche par force brute implique simplement de rechercher l'ensemble l'image de la sous-région qui correspond le mieux à l'image que vous recherchez. J'expliquerai cette approche plus avant. Vous devez d'abord définir comment déterminer la similitude entre deux images de taille égale. Cela peut être fait en additionnant les différences entre les les couleurs des pixels qui nécessitent une définition de la différence entre les valeurs RVB.

Similitude des couleurs

Une façon de déterminer la différence entre deux valeurs RVB consiste à utiliser la distance euclidienne:

sqrt( (r1-r2)^2 + (g1-g2)^2 + (b1-b2)^2 )

Il existe différents espaces colorimétriques que RVB qui peuvent être utilisés, mais comme votre sous-image est très probablement presque identique (au lieu d'être simplement visuellement similaire), cela devrait fonctionner correctement. Si vous avez un espace colorimétrique ARGB et que vous ne voulez pas que les pixels semi-transparents influencent autant vos résultats, vous pouvez utiliser:

a1 * a2 * sqrt( (r1-r2)^2 + (g1-g2)^2 + (b1-b2)^2 )

ce qui donnera une valeur plus petite si les couleurs sont transparentes (en supposant a1 et a2 sont compris entre 0 et 1). Je vous suggère d'utiliser la transparence au lieu des zones blanches et d'utiliser le format de fichier PNG car il n'utilise pas de compression avec perte qui déforme subtilement les couleurs de l'image.

Comparaison d'images

Pour comparer des images de taille égale, vous pouvez additionner la différence entre leurs pixels individuels. Cette somme est alors une mesure de la différence et vous pouvez rechercher la région dans l'image avec la mesure de différence la plus faible. Cela devient plus difficile si vous ne savez même pas si l'image contient la sous-image, mais cela serait indiqué par la meilleure correspondance avec une mesure de différence élevée. Si vous le souhaitez, vous pouvez également normaliser la mesure de différence entre 0 et 1 en la divisant par la taille de la sous-image et la différence RVB maximale possible (sqrt (3) avec la distance euclidienne et les valeurs RVB de 0 à 1 ). Zéro serait alors une correspondance identique et tout ce qui en serait proche serait aussi différent que possible.

Mise en œuvre de la force brute

Voici une implémentation simple qui utilise l'approche par force brute pour rechercher l'image. Avec vos images d'exemple, il a trouvé que l'emplacement (139,55) était l'emplacement en haut à gauche de la région avec la meilleure correspondance (qui semble correcte). Il a fallu environ 10 à 15 secondes pour fonctionner sur mon PC et la mesure de différence normalisée de l'emplacement était d'environ 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));
 }

Je n'ai pas regardé, mais peut-être qu'une de ces bibliothèques de traitement d'image Java Java pourrait également être utile:

Si la vitesse est vraiment importante, je pense que la meilleure approche serait une implémentation utilisant la corrélation croisée ou des descripteurs de fonctionnalités qui utilisent une bibliothèque externe.

26
Silveri

Ce que vous voulez, c'est trouver un bloc d'image par masque/limites.

Cela peut être fait sans bibliothèque externe. À bas niveau, chaque image est une matrice de nombres, votre masque est également la matrice de nombres. Vous pouvez simplement scanner une grande matrice linéaire et trouver la zone qui suit les règles définies par votre masque.

Exemple:

Grande matrice:

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

Masque:

1 1 1
1 0 0
1 1 1

Applique cet algorithme, vous détectez un bloc correspondant dans une grande matrice dans le coin supérieur droit, qui vous donne des indices de matrice de début/fin et vous pouvez calculer ces valeurs en pixels.

En vrai problème, vous n'aurez pas un ensemble de nombres [0, 1] mais beaucoup plus gros - byte par exemple ([0, 256]). Pour que l'algorithme fonctionne mieux, l'appariement ne signifie pas l'appariement exact des nombres, mais possible avec quelques écarts + -5 ou quelque chose comme ça.

7
mishadoff