web-dev-qa-db-fra.com

Comparez la similarité des images avec OpenCV avec Python

J'essaie de comparer une image à une liste d'autres images et de renvoyer une sélection d'images (telles que des images de recherche Google) de cette liste avec jusqu'à 70% de similarité.

Je reçois ce code dans ce post et change pour mon contexte

# Load the images
img =cv2.imread(MEDIA_ROOT + "/uploads/imagerecognize/armchair.jpg")

# Convert them to grayscale
imgg =cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# SURF extraction
surf = cv2.FeatureDetector_create("SURF")
surfDescriptorExtractor = cv2.DescriptorExtractor_create("SURF")
kp = surf.detect(imgg)
kp, descritors = surfDescriptorExtractor.compute(imgg,kp)

# Setting up samples and responses for kNN
samples = np.array(descritors)
responses = np.arange(len(kp),dtype = np.float32)

# kNN training
knn = cv2.KNearest()
knn.train(samples,responses)

modelImages = [MEDIA_ROOT + "/uploads/imagerecognize/1.jpg", MEDIA_ROOT + "/uploads/imagerecognize/2.jpg", MEDIA_ROOT + "/uploads/imagerecognize/3.jpg"]

for modelImage in modelImages:

    # Now loading a template image and searching for similar keypoints
    template = cv2.imread(modelImage)
    templateg= cv2.cvtColor(template,cv2.COLOR_BGR2GRAY)
    keys = surf.detect(templateg)

    keys,desc = surfDescriptorExtractor.compute(templateg, keys)

    for h,des in enumerate(desc):
        des = np.array(des,np.float32).reshape((1,128))

        retval, results, neigh_resp, dists = knn.find_nearest(des,1)
        res,dist =  int(results[0][0]),dists[0][0]


        if dist<0.1: # draw matched keypoints in red color
            color = (0,0,255)

        else:  # draw unmatched in blue color
            #print dist
            color = (255,0,0)

        #Draw matched key points on original image
        x,y = kp[res].pt
        center = (int(x),int(y))
        cv2.circle(img,center,2,color,-1)

        #Draw matched key points on template image
        x,y = keys[h].pt
        center = (int(x),int(y))
        cv2.circle(template,center,2,color,-1)



    cv2.imshow('img',img)
    cv2.imshow('tm',template)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

Ma question est, comment puis-je comparer l'image avec la liste des images et obtenir uniquement les images similaires? Y at-il une méthode pour faire cela?

29
leeeandroo

Je vous suggère de jeter un coup d'œil à la distance entre les images de la machine de terrassement (EMD) ..__ Cette métrique donne une idée de la difficulté à transformer une image normalisée en niveaux de gris en une autre, mais elle peut être généralisée pour les images couleur. On trouvera une très bonne analyse de cette méthode dans l’article suivant:

robotics.stanford.edu/~rubner/papers/rubnerIjcv00.pdf

Cela peut être fait à la fois sur l'image entière et sur l'histogramme (ce qui est vraiment plus rapide que la méthode de l'image entière). Je ne sais pas quelle méthode permet une comparaison complète des images, mais vous pouvez utiliser la fonction cv.CalcEMD2 pour la comparaison des histogrammes.

Le seul problème est que cette méthode ne définit pas un pourcentage de similarité, mais une distance sur laquelle vous pouvez filtrer.

Je sais que ce n'est pas un algorithme complet, mais qu'il en reste une base, alors j'espère que cela aidera.

MODIFIER:

Voici une parodie du principe de fonctionnement du DGE. L’idée principale est d’avoir deux matrices normalisées (deux images en niveaux de gris divisées par leur somme) et de définir une matrice de flux décrivant comment déplacer le gris d’un pixel à l’autre de la première image pour obtenir la seconde (elle peut être définie même pour un non normalisé, mais est plus difficile).

En termes mathématiques, la matrice de flux est en fait un tenseur quadridimensionnel qui donne le flux du point (i, j) de l'ancienne image au point (k, l) de la nouvelle, mais si vous aplatissez vos images, vous pouvez la transformer à une matrice normale, juste un peu plus difficile à lire.

Cette matrice de flux a trois contraintes: chaque terme doit être positif, la somme de chaque ligne doit renvoyer la même valeur du pixel souhaité et la somme de chaque colonne doit renvoyer la valeur du pixel de départ.

Compte tenu de cela, vous devez minimiser le coût de la transformation, donné par la somme des produits de chaque flux de (i, j) à (k, l) pour la distance entre (i, j) et (k, l).

Les mots semblent compliqués, alors voici le code de test. La logique est correcte, je ne sais pas pourquoi le solutionneur de scipy s'en plaint (vous devriez peut-être regarder openOpt ou quelque chose de similaire):

#original data, two 2x2 images, normalized
x = Rand(2,2)
x/=sum(x)
y = Rand(2,2)
y/=sum(y)

#initial guess of the flux matrix
# just the product of the image x as row for the image y as column
#This is a working flux, but is not an optimal one
F = (y.flatten()*x.flatten().reshape((y.size,-1))).flatten()

#distance matrix, based on euclidean distance
row_x,col_x = meshgrid(range(x.shape[0]),range(x.shape[1]))
row_y,col_y = meshgrid(range(y.shape[0]),range(y.shape[1]))
rows = ((row_x.flatten().reshape((row_x.size,-1)) - row_y.flatten().reshape((-1,row_x.size)))**2)
cols = ((col_x.flatten().reshape((row_x.size,-1)) - col_y.flatten().reshape((-1,row_x.size)))**2)
D = np.sqrt(rows+cols)

D = D.flatten()
x = x.flatten()
y = y.flatten()
#COST=sum(F*D)

#cost function
fun = lambda F: sum(F*D)
jac = lambda F: D
#array of constraint
#the constraint of sum one is implicit given the later constraints
cons  = []
#each row and columns should sum to the value of the start and destination array
cons += [ {'type': 'eq', 'fun': lambda F:  sum(F.reshape((x.size,y.size))[i,:])-x[i]}     for i in range(x.size) ]
cons += [ {'type': 'eq', 'fun': lambda F:  sum(F.reshape((x.size,y.size))[:,i])-y[i]} for i in range(y.size) ]
#the values of F should be positive
bnds = (0, None)*F.size

from scipy.optimize import minimize
res = minimize(fun=fun, x0=F, method='SLSQP', jac=jac, bounds=bnds, constraints=cons)

la variable res contient le résultat de la minimisation ... mais comme je l'ai dit, je ne sais pas pourquoi elle se plaint d'une matrice singulière.

Le seul problème avec cet algorithme est qu’il n’est pas très rapide, il n’est donc pas possible de le faire à la demande, mais vous devez le faire avec patience lors de la création de l’ensemble de données et stocker quelque part les résultats.

23
EnricoGiampieri

Vous vous lancez dans un énorme problème, appelé "récupération d’image basée sur le contenu", ou CBIR. C'est un domaine massif et actif. Il n’existe pas encore d’algorithmes finis ni d’approches standard, bien qu’il existe de nombreuses techniques ayant toutes des niveaux de réussite différents.

Même la recherche d'images Google ne le fait pas (encore) - elle effectue une recherche d'images basée sur du texte - par exemple, recherche du texte dans une page qui ressemble au texte que vous avez recherché. (Et je suis sûr qu'ils travaillent sur CBIR; c'est le Saint Graal pour beaucoup de chercheurs en traitement d'images)

Si vous avez un délai serré ou si vous avez besoin que cela soit fait et que vous travailliez bientôt ... beurk.

Voici une tonne d'articles sur le sujet:

http://scholar.google.com/scholar?q=content+based+image+retrieval

Généralement, vous devrez faire quelques choses:

  1. Extraire les caractéristiques (soit aux points d’intérêt locaux, soit globalement, ou d’une manière ou d’une autre, SIFT, SURF, histogrammes, etc.)
  2. Cluster/construire un modèle de distribution d'images

Cela peut impliquer des descripteurs de caractéristiques , des images gistes , un apprentissage à plusieurs instances . etc.

10
Pete

J'ai écrit un programme pour faire quelque chose de très similaire il y a peut-être 2 ans en utilisant Python/Cython. Plus tard, je l'ai réécrit pour aller pour obtenir de meilleures performances. L'idée de base vient de findimagedupes IIRC.

Il calcule fondamentalement une "empreinte digitale" pour chaque image, puis compare ces empreintes digitales à des images similaires.

L’empreinte digitale est générée en redimensionnant l’image en 160x160, en la convertissant en niveaux de gris, en ajoutant du flou, en la normalisant, puis en la redimensionnant en monochrome 16x16. À la fin, vous avez 256 bits de sortie: c'est votre empreinte digitale. C'est très facile à faire avec convert :

convert path[0] -sample 160x160! -modulate 100,0 -blur 3x99 \
    -normalize -equalize -sample 16x16 -threshold 50% -monochrome mono:-

(Le [0] dans path[0] sert uniquement à extraire la première image de fichiers GIF animés; si vous n'êtes pas intéressé par de telles images, vous pouvez simplement les supprimer.)

Après avoir appliqué cela à 2 images, vous aurez 2 empreintes digitales (256 bits), fp1 et fp2.

Le score de similarité de ces 2 images est ensuite calculé en XORing ces 2 valeurs et en comptant les bits mis à 1. Pour faire ce décompte en bits, vous pouvez utiliser la fonction bitsoncount() de cette réponse :

# fp1 and fp2 are stored as lists of 8 (32-bit) integers
score = 0
for n in range(8):
    score += bitsoncount(fp1[n] ^ fp2[n])

score sera un nombre compris entre 0 et 256 indiquant à quel point vos images sont similaires. Dans ma demande, je le divise par 2,56 (normalisé à 0-100) et j'ai constaté que les images avec un score normalisé de 20 ou moins sont souvent identiques.

Si vous voulez implémenter cette méthode et l'utiliser pour comparer beaucoup d'images, je fortement vous suggère d'utiliser autant que possible Cython (ou tout simplement C): XORing et le comptage des bits est très lent avec des entiers Python purs .

Je suis vraiment désolé mais je ne trouve plus mon code Python. Pour le moment, je n'ai qu'une version Go, mais j'ai bien peur de ne pas pouvoir la poster ici (étroitement intégrée à un autre code, et probablement un peu moche, car c'était mon premier programme sérieux dans Go ...).

Il existe également une très bonne fonction "trouver par similarité" dans GQView/Geeqie; sa source est ici .

10
Schnouki

Pour une implémentation plus simple de Distance de Earth Mover (aka Distance de Wasserstein) en Python, vous pouvez utiliser Scipy

from scipy.stats import wasserstein_distance
from scipy.ndimage import imread
import numpy as np

def get_histogram(img):
  '''
  Get the histogram of an image. For an 8-bit, grayscale image, the
  histogram will be a 256 unit vector in which the nth value indicates
  the percent of the pixels in the image with the given darkness level.
  The histogram's values sum to 1.
  '''
  h, w = img.shape
  hist = [0.0] * 256
  for i in range(h):
    for j in range(w):
      hist[img[i, j]] += 1
  return np.array(hist) / (h * w)

a = imread('a.jpg')
b = imread('b.jpg')
a_hist = get_histogram(a)
b_hist = get_histogram(b)
dist = wasserstein_distance(a_hist, b_hist)
print(dist)
1
duhaime