web-dev-qa-db-fra.com

Quelle est la façon la plus rapide de comparer deux bitmaps de taille égale pour déterminer s'ils sont identiques?

J'essaie d'écrire une fonction pour déterminer si deux bitmaps de taille égale sont identiques ou non. La fonction que j'ai en ce moment compare simplement un pixel à la fois dans chaque bitmap, retournant faux au premier pixel non égal.

Bien que cela fonctionne et fonctionne bien pour les petits bitmaps, en production, je vais l'utiliser dans une boucle étroite et sur des images plus grandes, j'ai donc besoin d'un meilleur moyen. Quelqu'un a-t-il des recommandations?

Le langage que j'utilise est C # d'ailleurs - et oui, j'utilise déjà la méthode .LockBits. =)

Edit: J'ai codé les implémentations de certaines des suggestions données, et voici les repères. La configuration: deux bitmaps identiques (dans le pire des cas), de taille 100 x 100, avec 10 000 itérations chacune. Voici les résultats:

CompareByInts (Marc Gravell) :   1107ms
CompareByMD5  (Skilldrick)   :   4222ms
CompareByMask (GrayWizardX)  :    949ms

Dans CompareByInts et CompareByMask, j'utilise des pointeurs pour accéder directement à la mémoire; dans la méthode MD5, j'utilise Marshal.Copy pour récupérer un tableau d'octets et le passer comme argument à MD5.ComputeHash. CompareByMask n'est que légèrement plus rapide, mais étant donné le contexte, je pense que toute amélioration est utile.

Merci tout le monde. =)

Edit 2: J'ai oublié d'activer les optimisations - cela donne encore plus de boost à la réponse de GrayWizardX:

CompareByInts   (Marc Gravell) :    944ms
CompareByMD5    (Skilldrick)   :   4275ms
CompareByMask   (GrayWizardX)  :    630ms
CompareByMemCmp (Erik)         :    105ms

Il est intéressant de noter que la méthode MD5 ne s'est pas améliorée du tout.

Edit: Posté ma réponse (MemCmp) qui a fait sauter les autres méthodes hors de l'eau. o.O

40
Erik Forbes

Edit 8-31-12: per Joey's commentaire ci-dessous, faites attention au format des bitmaps que vous comparez. Ils peuvent contenir un remplissage sur les foulées qui rendent les bitmaps inégaux, même s'ils sont équivalents au niveau des pixels. Voir cette question pour plus de détails.


La lecture cette réponse à une question concernant la comparaison des tableaux d'octets a donné une méthode BEAUCOUP PLUS RAPIDE: en utilisant P/Invoke et l'appel API memcmp dans msvcrt. Voici le code:

[DllImport("msvcrt.dll")]
private static extern int memcmp(IntPtr b1, IntPtr b2, long count);

public static bool CompareMemCmp(Bitmap b1, Bitmap b2)
{
    if ((b1 == null) != (b2 == null)) return false;
    if (b1.Size != b2.Size) return false;

    var bd1 = b1.LockBits(new Rectangle(new Point(0, 0), b1.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
    var bd2 = b2.LockBits(new Rectangle(new Point(0, 0), b2.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

    try
    {
        IntPtr bd1scan0 = bd1.Scan0;
        IntPtr bd2scan0 = bd2.Scan0;

        int stride = bd1.Stride;
        int len = stride * b1.Height;

        return memcmp(bd1scan0, bd2scan0, len) == 0;
    }
    finally
    {
        b1.UnlockBits(bd1);
        b2.UnlockBits(bd2);
    }
}
37
Erik Forbes

Si vous essayez de déterminer s'ils sont égaux à 100%, vous pouvez inverser l'un et l'ajouter à l'autre si son zéro, ils sont identiques. Étendre cela en utilisant du code dangereux, prendre 64 bits à la fois et faire le calcul de cette façon, toute différence peut entraîner un échec immédiat.

Si les images ne sont pas 100% identiques (en comparant png à jpeg), ou si vous ne cherchez pas une correspondance à 100%, vous avez encore du travail devant vous.

Bonne chance.

9
GrayWizardx

Eh bien, vous utilisez .LockBits, donc vous utilisez probablement du code dangereux. Plutôt que de traiter chaque ligne Origin (Scan0 + y * Stride) comme un byte*, envisagez de le traiter comme un int*; int l'arithmétique est assez rapide, et vous n'avez qu'à faire 1/4 de travail. Et pour les images en ARGB, vous pouvez toujours parler en pixels, ce qui simplifie les calculs.

8
Marc Gravell

Pourriez-vous prendre un hachage de chacun et comparer? Ce serait légèrement probabiliste, mais pratiquement pas.

Grâce à Ram, voici un exemple d'implémentation de cette technique.

6
Skilldrick

Si le problème d'origine est juste de trouver les doublons exacts parmi deux bitmaps, alors une comparaison de niveau binaire devra faire. Je ne connais pas C # mais en C j'utiliserais la fonction suivante:

int areEqual (long size, long *a, long *b)
{
    long start = size / 2;
    long i;
    for (i = start; i != size; i++) { if (a[i] != b[i]) return 0 }
    for (i = 0; i != start; i++) { if (a[i] != b[i]) return 0 }
    return 1;
}

Je commencerais à regarder au milieu parce que je soupçonne qu'il y a une bien meilleure chance de trouver des bits inégaux près du milieu de l'image qu'au début; bien sûr, cela dépendra vraiment des images que vous dédoublez, il peut être préférable de sélectionner un endroit aléatoire pour commencer.

Si vous essayez de trouver les doublons exacts parmi des centaines d'images, il n'est pas nécessaire de comparer toutes les paires. Calculez d'abord le hachage MD5 de chaque image et placez-le dans une liste de paires (md5Hash, imageId); puis triez la liste par m5Hash. Ensuite, faites uniquement des comparaisons par paires sur les images qui ont le même md5Hash.

3
Jeff Kubina

Si ces bitmaps sont déjà sur votre carte graphique, vous pouvez paralléliser une telle vérification en le faisant sur la carte graphique en utilisant un langage comme CUDA ou OpenCL .

Je vais vous expliquer en termes de CUDA, puisque c'est celui que je connais. Fondamentalement, CUDA vous permet d'écrire du code à usage général à exécuter en parallèle sur chaque nœud de votre carte graphique. Vous pouvez accéder aux bitmaps qui sont en mémoire partagée. Chaque invocation de la fonction reçoit également un index dans l'ensemble des exécutions parallèles. Donc, pour un problème comme celui-ci, il vous suffit d'exécuter l'une des fonctions de comparaison ci-dessus pour un sous-ensemble du bitmap - en utilisant la parallélisation pour couvrir l'intégralité du bitmap. Ensuite, écrivez simplement un 1 à un certain emplacement de mémoire si la comparaison échoue (et n'écrivez rien si elle réussit).

Si vous n'avez pas déjà les bitmaps sur votre carte graphique, ce n'est probablement pas la voie à suivre, car les coûts de chargement des deux bitmaps sur votre carte permettront facilement d'éclipser les économies qu'une telle parallélisation vous fera gagner.

Voici un exemple de code (assez mauvais) (cela fait un petit moment que je n'ai pas programmé CUDA). Il existe de meilleures façons d'accéder aux bitmaps qui sont déjà chargés sous forme de textures, mais je n'ai pas pris la peine ici.

// kernel to run on GPU, once per thread
__global__ void compare_bitmaps(long const * const A, long const * const B, char * const retValue, size_t const len)
{
 // divide the work equally among the threads (each thread is in a block, each block is in a grid)
 size_t const threads_per_block = blockDim.x * blockDim.y * blockDim.z;
 size_t const len_to_compare = len / (gridDim.x * gridDim.y * gridDim.z * threads_per_block);
# define offset3(idx3,dim3)  (idx3.x + dim3.x * (idx3.y + dim3.y * idx3.z))
 size_t const start_offset = len_to_compare * (offset3(threadIdx,blockDim) + threads_per_block * offset3(blockIdx,gridDim));
 size_t const stop_offset = start_offset + len_to_compare;
# undef offset3

 size_t i;
 for (i = start_offset; i < stop_offset; i++)
 {
  if (A[i] != B[i]) 
  {
   *retValue = 1;
   break;
  }
 }
 return;
}
3
rampion

Si vous pouvez implémenter quelque chose comme Duff's Device dans votre langue, cela pourrait vous donner une augmentation de vitesse significative sur une simple boucle. Habituellement, il est utilisé pour copier des données, mais il n'y a aucune raison qu'il ne puisse pas être utilisé à la place pour comparer les données.

Ou, d'ailleurs, vous pouvez simplement utiliser un équivalent de memcmp ().

0
rmeador

Vous pouvez essayer de les ajouter à un "blob" de base de données, puis utiliser le moteur de base de données pour comparer leurs fichiers binaires. Cela vous donnerait seulement une réponse oui ou non pour savoir si les données binaires sont les mêmes. Il serait très facile de faire 2 images qui produisent le même graphique mais qui ont des binaires différents.

Vous pouvez également sélectionner quelques pixels aléatoires et les comparer, puis s'ils sont identiques, continuez avec plus jusqu'à ce que vous ayez vérifié tous les pixels. Cela ne ferait que renvoyer une correspondance négative plus rapide, mais il faudrait encore autant de temps pour trouver des correspondances positives à 100%

0
Drew