211 votes

Comment puis-je quantifier la différence entre deux images ?

Voici ce que je voudrais faire :

Je prends des photos avec une webcam à intervalles réguliers. C'est un peu comme un laps de temps. Cependant, si rien n'a vraiment changé, c'est-à-dire que la photo a à peu près regarde la même, je ne veux pas stocker le dernier snapshot.

J'imagine qu'il existe un moyen de quantifier la différence, et il faudrait que je détermine empiriquement un seuil.

Je recherche la simplicité plutôt que la perfection. J'utilise Python.

0 votes

0 votes

Deux approches quantitatives : 1) l'utilisation de SSIM pour la comparaison d'images de mêmes dimensions et 2) les représentations vectorielles denses pour les images à échelle invariante et indifférentes à la transformation. Les deux méthodes renvoient un score de similarité entre 0 et 100 et peuvent être utilisées pour déterminer les images dupliquées/identiques ou quasi-similaires. Voir aussi Détecter et visualiser les différences entre deux images

292voto

sastanin Points 16061

Idée générale

Chargez les deux images sous forme de tableaux ( scipy.misc.imread ) et calculer une différence par élément. Calculez la norme de la différence.

Cependant, il y a des décisions à prendre.

Questions

Vous devez d'abord répondre à ces questions :

  • Les images ont-elles la même forme et la même dimension ?

    Sinon, vous devrez peut-être les redimensionner ou les recadrer. La bibliothèque PIL vous aidera à le faire en Python.

    Si elles sont prises avec les mêmes paramètres et le même appareil, elles sont probablement identiques.

  • Les images sont-elles bien alignées ?

    Si ce n'est pas le cas, vous pouvez d'abord effectuer une corrélation croisée pour trouver le meilleur alignement. SciPy dispose de fonctions pour le faire.

    Si la caméra et la scène sont immobiles, les images sont susceptibles d'être bien alignées.

  • L'exposition des images est-elle toujours la même ? (La luminosité/contraste est-elle la même ?)

    Si non, vous pouvez vouloir pour normaliser des images.

    Mais attention, dans certaines situations, cela peut faire plus de mal que de bien. Par exemple, un seul pixel clair sur un fond sombre rendra l'image normalisée très différente.

  • Les informations sur la couleur sont-elles importantes ?

    Si vous voulez remarquer les changements de couleur, vous aurez un vecteur de valeurs de couleur par point, plutôt qu'une valeur scalaire comme dans une image en niveaux de gris. Vous devez faire plus attention en écrivant un tel code.

  • Y a-t-il des bords distincts dans l'image ? Sont-ils susceptibles de se déplacer ?

    Si oui, vous pouvez d'abord appliquer un algorithme de détection des bords (par exemple, calculer le gradient avec la transformée de Sobel ou de Prewitt, appliquer un certain seuil), puis comparer les bords de la première image à ceux de la seconde.

  • Y a-t-il du bruit dans l'image ?

    Tous les capteurs polluent l'image avec une certaine quantité de bruit. Les capteurs bon marché ont plus de bruit. Vous pouvez appliquer une réduction du bruit avant de comparer les images. Le flou est l'approche la plus simple (mais pas la meilleure) ici.

  • Quel genre de changements voulez-vous constater ?

    Cela peut affecter le choix de la norme à utiliser pour la différence entre les images.

    Envisagez d'utiliser la norme Manhattan (la somme des valeurs absolues) ou la norme zéro (le nombre d'éléments non égaux à zéro) pour mesurer l'ampleur du changement de l'image. La première vous dira de combien l'image est décalée, la seconde vous dira seulement combien de pixels diffèrent.

Exemple

Je suppose que vos images sont bien alignées, de même taille et de même forme, avec éventuellement une exposition différente. Pour simplifier, je les convertis en niveaux de gris même s'il s'agit d'images en couleur (RVB).

Vous aurez besoin de ces importations :

import sys

from scipy.misc import imread
from scipy.linalg import norm
from scipy import sum, average

Fonction principale, lecture de deux images, conversion en niveaux de gris, comparaison et impression des résultats :

def main():
    file1, file2 = sys.argv[1:1+2]
    # read images as 2D arrays (convert to grayscale for simplicity)
    img1 = to_grayscale(imread(file1).astype(float))
    img2 = to_grayscale(imread(file2).astype(float))
    # compare
    n_m, n_0 = compare_images(img1, img2)
    print "Manhattan norm:", n_m, "/ per pixel:", n_m/img1.size
    print "Zero norm:", n_0, "/ per pixel:", n_0*1.0/img1.size

Comment comparer. img1 et img2 sont des tableaux SciPy 2D ici :

def compare_images(img1, img2):
    # normalize to compensate for exposure difference, this may be unnecessary
    # consider disabling it
    img1 = normalize(img1)
    img2 = normalize(img2)
    # calculate the difference and its norms
    diff = img1 - img2  # elementwise for scipy arrays
    m_norm = sum(abs(diff))  # Manhattan norm
    z_norm = norm(diff.ravel(), 0)  # Zero norm
    return (m_norm, z_norm)

Si le fichier est une image couleur, imread retourne un tableau 3D, moyenne les canaux RVB (le dernier axe du tableau) pour obtenir l'intensité. Il n'est pas nécessaire de le faire pour les images en niveaux de gris (par ex. .pgm ) :

def to_grayscale(arr):
    "If arr is a color image (3D array), convert it to grayscale (2D array)."
    if len(arr.shape) == 3:
        return average(arr, -1)  # average over the last axis (color channels)
    else:
        return arr

La normalisation est triviale, vous pouvez choisir de normaliser à [0,1] au lieu de [0,255]. arr est un tableau SciPy ici, donc toutes les opérations se font par élément :

def normalize(arr):
    rng = arr.max()-arr.min()
    amin = arr.min()
    return (arr-amin)*255/rng

Exécuter le main fonction :

if __name__ == "__main__":
    main()

Maintenant, vous pouvez mettre tout cela dans un script et l'exécuter contre deux images. Si on compare l'image à elle-même, il n'y a pas de différence :

$ python compare.py one.jpg one.jpg
Manhattan norm: 0.0 / per pixel: 0.0
Zero norm: 0 / per pixel: 0.0

Si nous floutons l'image et la comparons à l'original, il y a une certaine différence :

$ python compare.py one.jpg one-blurred.jpg 
Manhattan norm: 92605183.67 / per pixel: 13.4210411116
Zero norm: 6900000 / per pixel: 1.0

P.S. Entire compare.py script.

Mise à jour : techniques pertinentes

Comme la question porte sur une séquence vidéo, où les images sont susceptibles d'être presque identiques, et que vous recherchez quelque chose d'inhabituel, je voudrais mentionner quelques approches alternatives qui peuvent être pertinentes :

  • soustraction de l'arrière-plan
  • flux optique clairsemé

Je vous recommande vivement de jeter un coup d'œil au livre "Learning OpenCV", chapitres 9 (Parties d'image et segmentation) et 10 (Suivi et mouvement). Le premier apprend à utiliser la méthode de soustraction d'arrière-plan, le second donne des informations sur les méthodes de flux optique. Toutes les méthodes sont implémentées dans la bibliothèque OpenCV. Si vous utilisez Python, je vous suggère d'utiliser OpenCV ≥ 2.3, et son cv2 Module Python.

La version la plus simple de la soustraction de l'arrière-plan :

  • apprendre la valeur moyenne μ et l'écart-type σ pour chaque pixel de l'arrière-plan
  • comparer les valeurs actuelles des pixels à la plage de (μ-2σ,μ+2σ) ou (μ-σ,μ+σ).

Les versions plus avancées prennent en compte les séries temporelles pour chaque pixel et traitent les scènes non statiques (comme les arbres ou l'herbe en mouvement).

L'idée du flux optique est de prendre deux images ou plus, et d'attribuer un vecteur de vitesse à chaque pixel (flux optique dense) ou à certains d'entre eux (flux optique clairsemé). Pour estimer le flux optique clairsemé, vous pouvez utiliser Méthode Lucas-Kanade (il est également implémenté dans OpenCV). Évidemment, s'il y a beaucoup de flux (valeurs moyennes et maximales élevées du champ de vitesse), alors quelque chose bouge dans l'image, et les images suivantes sont plus différentes.

0 votes

Je reçois un RuntimeWarning: invalid value encountered in double_scalars à la ligne 44 ( return (arr-amin)*255/rng ) et un ValueError: array must not contain infs or NaNs à la ligne 30 ( z_norm = norm(diff.ravel(), 0) )

0 votes

@BioGeek c'est si rng est égal à zéro. Il suffit d'ajouter une vérification et de mettre rng = 1

82voto

keparo Points 13747

Une solution simple :

Encoder l'image comme un jpeg et rechercher un changement substantiel dans taille des fichiers .

J'ai mis en œuvre quelque chose de similaire avec des vignettes vidéo, et j'ai eu beaucoup de succès et d'évolutivité.

5 votes

Il s'agit d'une solution très simple et facile à mettre en œuvre, qui est bien meilleure que toute comparaison pixel par pixel. S'il y a un peu de bruit dans l'image de votre webcam ou si l'image est décalée ne serait-ce que d'un pixel, une comparaison directe détectera tous ces changements sans signification. Une approche plus robuste consisterait à calculer la transformation en cosinus discret, puis à comparer les images dans le domaine des fréquences. En utilisant la compression JPEG de cette manière, vous obtenez la plupart des avantages sans vous plonger dans la théorie de Fourier.

2 votes

J'aime ça. Bien que d'autres solutions fonctionnent aussi, celle-ci a un grand avantage pour une situation courante : que faire si vous ne voulez pas sauvegarder l'image "de base" ? il suffit de sauvegarder la taille des fichiers comme un hash et ensuite comparer juste les nombres avec la soustraction. Dans mon cas, j'ai 4 images, l'une d'entre elles est très similaire et les 3 autres sont absolument différentes. Il suffit de mettre à l'échelle les mêmes dimensions, en jpg et de soustraire. Vraiment bien.

67voto

gooli Points 2132

Vous pouvez comparer deux images en utilisant les fonctions de LIP .

import Image
import ImageChops

im1 = Image.open("splash.png")
im2 = Image.open("splash2.png")

diff = ImageChops.difference(im2, im1)

L'objet diff est une image dans laquelle chaque pixel est le résultat de la soustraction des valeurs de couleur de ce pixel dans la deuxième image de la première image. En utilisant l'image diff, vous pouvez faire plusieurs choses. La plus simple est la diff.getbbox() fonction. Elle vous indiquera le rectangle minimal qui contient tous les changements entre vos deux images.

Vous pouvez probablement implémenter des approximations des autres éléments mentionnés ici en utilisant des fonctions de PIL également.

2 votes

Je veux sauvegarder l'image de différence, c'est-à-dire l'objet de différence qui contient la différence des images.

2 votes

@Anthony vous pouvez appeler save() sur l'objet diff en spécifiant le nom de l'image. comme ceci : diff.save("diff.png") cela sauvegardera l'image de différence pour vous.

1 votes

NOTE : L'importation a été modifiée et ne fonctionnera plus, du moins en ce qui concerne les tests effectués en python. 3.10 vous devez utiliser from PIL import Image, ImageChops

22voto

Mr Fooz Points 21092

Il existe deux méthodes populaires et relativement simples : (a) la distance euclidienne déjà suggérée, ou (b) la corrélation croisée normalisée. La corrélation croisée normalisée tend à être sensiblement plus robuste aux changements d'éclairage que la corrélation croisée simple. Wikipedia donne une formule pour la corrélation croisée normalisée . Il existe également des méthodes plus sophistiquées, mais elles nécessitent un travail plus important.

Utilisation d'une syntaxe de type numpy,

dist\_euclidean = sqrt(sum((i1 - i2)^2)) / i1.size

dist\_manhattan = sum(abs(i1 - i2)) / i1.size

dist\_ncc = sum( (i1 - mean(i1)) \* (i2 - mean(i2)) ) / (
  (i1.size - 1) \* stdev(i1) \* stdev(i2) )

en supposant que i1 et i2 sont des tableaux d'images en niveaux de gris en 2D.

3 votes

Les fonctions de corrélation croisée d'images sont intégrées à SciPy ( docs.scipy.org/doc/scipy/reference/generated/ ), et une version rapide utilisant la FFT est disponible dans stsci python ( stsci.edu/resources/logiciels_hardware/pyraf/stsci_python )

14voto

Ates Goral Points 47670

Une chose triviale à essayer :

Ré-échantillonnez les deux images en petites vignettes (par exemple 64 x 64) et comparez les vignettes pixel par pixel avec un certain seuil. Si les images originales sont presque identiques, les vignettes rééchantillonnées seront très similaires, voire exactement identiques. Cette méthode prend en charge le bruit qui peut se produire notamment dans les scènes à faible luminosité. Elle peut même être meilleure si vous optez pour les niveaux de gris.

0 votes

Mais comment comparez-vous les pixels ?

0 votes

Une fois que vous avez les vignettes, vous pouvez simplement comparer les pixels un par un. Vous calculerez la "distance" des valeurs RVB, si vous travaillez en couleur, ou simplement la différence entre les tons de gris, si vous travaillez en niveaux de gris.

1 votes

"comparer les pixels un par un". Qu'est-ce que cela signifie ? Le test doit-il échouer si UN des 64^2 tests pixel par pixel échoue ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X