Comment puis-je détecter que deux images sont «identiques» même si l'une a un rapport / recadrage légèrement différent?


11

J'ai deux images différentes:

en 100px avec entrez la description de l'image iciou 400pxentrez la description de l'image ici

et

en largeur 100px entrez la description de l'image iciou 400pxentrez la description de l'image ici

Comme vous pouvez le voir, les deux sont clairement les "mêmes" d'un point de vue humain. Maintenant, je veux détecter par programme qu'ils sont les mêmes. J'utilise la magie de l'image via la gemme rubis appelée rmagickainsi:

img1 = Magick::Image.from_blob(File.read("image_1.jpeg")).first
img2 = Magick::Image.from_blob(File.read("image_2.jpeg")).first

if img1.difference(img2).first < 4000.0 # I have found this to be a good threshold, but does not work for cropped images
  puts "they are the same!!!"
end

Bien que cela fonctionne bien pour les images qui ont le même rapport / recadrage, ce n'est pas idéal lorsqu'elles ont un recadrage légèrement différent et qu'elles ont été redimensionnées à la même largeur.

Existe-t-il un moyen de le faire pour des images avec un recadrage différent? Je suis intéressé par une solution où je peux dire quelque chose comme: Une image est contenue à l'intérieur de l'autre et couvre quelque part par exemple 90% de celle-ci.

PS. Je peux obtenir les images en plus haute résolution si cela aide (par exemple le double)


2
Pas sûr de RMagick, mais l' compareoutil de ligne de commande d'ImageMagick a un -subimage-searchcommutateur.
Stefan

C'est intéressant, à quoi ressemblerait une telle commande?
Niels Kristian

2
Je ne l'ai jamais utilisé moi-même, cela aide peut-être: stackoverflow.com/q/29062811/477037
Stefan

Merci, c'est une excellente information. Je ne peux pas comprendre comment faire cela à partir de rubis cependant ...
Niels Kristian

1
Les images sont-elles de mauvaise qualité? Si non, veuillez partager une version plus grande des images, avec plus de qualité.
MH304

Réponses:


6

Vous voudrez peut-être jeter un œil à la correspondance des fonctionnalités. L'idée est de trouver des fonctionnalités dans deux images et de les faire correspondre. Cette méthode est couramment utilisée pour trouver un modèle (par exemple un logo) dans une autre image. Une caractéristique, en substance, peut être décrite comme des choses que les humains trouveraient intéressantes dans une image, comme des coins ou des espaces ouverts. Il existe de nombreux types de techniques de détection de fonctionnalités, mais ma recommandation est d'utiliser une transformation de caractéristique invariante à l'échelle (SIFT) comme algorithme de détection de fonctionnalité. SIFT est invariant à la translation d'image, à l'échelle, à la rotation, partiellement invariant aux changements d'éclairage et robuste à la distorsion géométrique locale. Cela semble correspondre à vos spécifications où les images peuvent avoir des rapports légèrement différents.

Compte tenu des deux images que vous avez fournies, voici une tentative de faire correspondre les fonctionnalités à l'aide de l' outil de mise en correspondance des fonctionnalités FLANN . Pour déterminer si les deux images sont identiques, nous pouvons la baser sur un certain seuil prédéterminé qui suit le nombre de correspondances qui réussissent le test de rapport décrit dans Caractéristiques distinctives de l'image à partir de points clés invariants à l'échelle par David G. Lowe . Une explication simple du test est que le test de rapport vérifie si les correspondances sont ambiguës et doivent être supprimées, vous pouvez le traiter comme une technique de suppression des valeurs aberrantes. Nous pouvons compter le nombre de correspondances qui réussissent ce test pour déterminer si les deux images sont identiques. Voici les résultats de correspondance des fonctionnalités:

Matches: 42

Les points représentent toutes les correspondances détectées tandis que les lignes vertes représentent les «bonnes correspondances» qui réussissent le test de rapport. Si vous n'utilisez pas le test de rapport, tous les points seront tirés. De cette façon, vous pouvez utiliser ce filtre comme seuil pour ne conserver que les fonctionnalités les mieux adaptées.


Je l'ai implémenté en Python, je ne connais pas très bien Rails. En espérant que ça aide, bonne chance!

Code

import numpy as np
import cv2

# Load images
image1 = cv2.imread('1.jpg', 0)
image2 = cv2.imread('2.jpg', 0)

# Create the sift object
sift = cv2.xfeatures2d.SIFT_create(700)

# Find keypoints and descriptors directly
kp1, des1 = sift.detectAndCompute(image2, None)
kp2, des2 = sift.detectAndCompute(image1, None)

# FLANN parameters
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50)   # or pass empty dictionary
flann = cv2.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1,des2,k=2)

# Need to draw only good matches, so create a mask
matchesMask = [[0,0] for i in range(len(matches))]

count = 0
# Ratio test as per Lowe's paper (0.7)
# Modify to change threshold 
for i,(m,n) in enumerate(matches):
    if m.distance < 0.15*n.distance:
        count += 1
        matchesMask[i]=[1,0]

# Draw lines
draw_params = dict(matchColor = (0,255,0),
                   # singlePointColor = (255,0,0),
                   matchesMask = matchesMask,
                   flags = 0)

# Display the matches
result = cv2.drawMatchesKnn(image2,kp1,image1,kp2,matches,None,**draw_params)
print('Matches:', count)
cv2.imshow('result', result)
cv2.waitKey()

2
Approche super intéressante, je vais essayer et revenir ...
Niels Kristian

PS. J'ai mis à jour les images à plus grande échelle
Niels Kristian

1
@nathancy Est-ce pour que sur votre exemple, les points verts correspondent, mais pas les bleus? On dirait qu'il y a trop de points inégalés?
Draco Ater

2
@DracoAter bonne question, les points bleus représentent tous les matchs alors que nous ne dessinons que les "bons matchs" qui passent le test de rapport en vert. Si vous n'utilisez pas le test de rapport, tous les points seront tirés mais nous filtrons en utilisant le test de rapport pour dessiner les «meilleures» correspondances. De cette façon, OP peut utiliser ce test comme seuil pour ne conserver que les fonctionnalités les mieux adaptées. Donc, tous les points bleus sont les fonctionnalités que SIFT a trouvées, mais nous filtrons pour conserver les bons qui sont dessinés en vert
nathancy

Merci. la concurrence a été difficile sur les réponses, beaucoup de bonnes réponses :-)
Niels Kristian

4

Parce que ImageMagick est très ancien, avancé et un outil à nombreuses fonctionnalités, il serait difficile de construire une interface qui couvre la plupart des fonctionnalités. Aussi formidable soit-il, rmagick ne (et les nombreuses tentatives que Python a effectuées) ne sont pas proches de couvrir toutes les fonctionnalités.

J'imagine que pour de nombreux cas d'utilisation, il sera suffisamment sûr et beaucoup plus facile d'exécuter simplement une méthode de ligne de commande et de lire à partir de cela. En rubis, cela ressemblera à ceci;

require 'open3'

def check_subimage(large, small)
    stdin, stdout, stderr, wait_thr = Open3.popen3("magick compare -subimage-search -metric RMSE #{large} #{small} temp.jpg")
    result = stderr.gets
    stderr.close
    stdout.close
    return result.split[1][1..-2].to_f < 0.2
end

if check_subimage('a.jpg', 'b.jpg')
    puts "b is a crop of a"
else
    puts "b is not a crop of a"
end

Je vais couvrir des choses importantes et parler ensuite de notes supplémentaires.

La commande utilise magick compare pour vérifier si la deuxième image ( small) est une sous-image de la première ( large). Cette fonction ne vérifie pas que petit est strictement plus petit que grand (à la fois en hauteur et en largeur). Le nombre que j'ai mis pour la similitude est 0,2 (erreur de 20%), et la valeur des images que vous avez fournies est d'environ 0,15. Vous voudrez peut-être affiner cela! Je trouve que les images qui sont un sous-ensemble strict obtiennent moins de 0,01.

  • Si vous voulez moins d'erreur (nombres plus petits) dans les cas où vous avez un chevauchement de 90% mais la deuxième image a des trucs supplémentaires que la première ne fait pas, vous pouvez l'exécuter une fois, puis recadrer la première grande image à l'endroit où la sous-image est contenue , puis exécutez-le à nouveau avec l'image recadrée comme la "petite" image et la "petite" image d'origine comme la grande.
  • Si vous vouliez vraiment une belle interface orientée objet dans Ruby, rmagick utilise l'API MagicCore. Cette commande (lien vers docs) est probablement ce que vous voulez utiliser pour l'implémenter, et vous pouvez ouvrir un pr pour rmagick ou empaqueter le cext vous-même.
  • L'utilisation d'Open3 lancera un thread ( voir la documentation ). Fermer stderret stdoutn'est pas "nécessaire" mais vous êtes censé le faire.
  • L'image "temp" qui est le troisième argument spécifie un fichier sur lequel sortir une analyse. Avec un regard rapide, je n'ai pas pu trouver un moyen de ne pas l'exiger, mais il écrase automatiquement et pourrait être bon à enregistrer pour le débogage. Pour votre exemple, cela ressemblerait à ceci;

entrez la description de l'image ici

  • La sortie complète est au format 10092,6 (0,154003) @ 0,31. Le premier nombre est la valeur rmse sur 655535, le second (que j'utilise) est un pourcentage normalisé. Les deux derniers chiffres représentent l'emplacement de l'image d'origine à partir de laquelle commence la petite image.
  • Puisqu'il n'y a pas de source objective de vérité sur la façon dont les images sont "similaires", j'ai choisi RMSE (voir plus d'options métriques ici ). C'est une mesure assez courante des différences entre les valeurs. Un nombre absolu d'erreurs (AE) peut sembler être une bonne idée, mais il semble que certains logiciels de recadrage ne préservent pas parfaitement les pixels, vous devrez peut-être ajuster le fuzz et ce n'est pas une valeur normalisée, vous devrez donc comparer le nombre d'erreurs avec la taille de l'image et ainsi de suite.

1
Cest des informations vraiment super là Carol. Merci
Niels Kristian

Curieux de savoir comment cela fonctionne pour vos autres cas!
Carol Chen

1
Merci pour la super bonne réponse. Si je le pouvais, je vous avais donné 100p de récompense pour celui-ci aussi :-)
Niels Kristian

3

Obtenez l'histogramme des deux images et comparez-les. Cela fonctionnerait très bien pour le recadrage et le zoom, sauf en cas de changement trop radical à cause de ceux-ci.

C'est mieux que l'approche actuelle où vous soustrayez directement les images. Mais cette approche en a encore peu.


Merci pour les conseils que je vais y jeter un œil.
Niels Kristian

Ce n'est pas une réponse très utile car elle ne montre pas comment atteindre l'objectif. C'est l'équivalent de "Google ce terme et le découvrir par vous-même."
anothermh

L'histogramme est l'une des premières choses que les gens apprennent en traitement d'image. Si certains doivent le rechercher sur Google, je m'excuse profondément.
Raviteja Narra

3

Habituellement , la mise en correspondance des modèles a un bon résultat dans ces situations. La mise en correspondance de modèles est une technique permettant de trouver des zones d'une image qui correspondent (sont similaires) à une image de modèle (deuxième image). Cet algorithme donne un score pour la meilleure position macthed dans l'image source (la seconde).

En opencv en utilisant TM_CCOEFF_NORMED méthode , donne le score entre 0 et 1. Si le score est 1, cela signifie que l'image modèle est exactement une partie (Rect) de l'image source, mais si vous avez un petit changement dans l'éclaircissement ou la perspective entre les deux images, le score serait inférieur à 1.

Maintenant En considérant un seuil pour le score de similitude, vous pouvez savoir s'ils sont identiques ou non. Ce seuil peut être obtenu par quelques essais et erreurs sur quelques exemples d'images. J'ai essayé vos images et obtenu le score de 0,823863 . Voici le code (opencv C ++) et l'espace commun entre les deux images, obtenu par l'appariement:

entrez la description de l'image ici

Mat im2 = imread("E:/1/1.jpg", 1);
//Mat im2;// = imread("E:/1/1.jpg", 1);
Mat im1 = imread("E:/1/2.jpg", 1);

//im1(Rect(0, 0, im1.cols - 5, im1.rows - 5)).copyTo(im2);

int result_cols = im1.cols - im2.cols + 1;
int result_rows = im1.rows - im2.rows + 1;

Mat result = Mat::zeros(result_rows, result_cols, CV_32FC1);

matchTemplate(im1, im2, result, TM_CCOEFF_NORMED);

double minVal; double maxVal;
Point minLoc; Point maxLoc;
Point matchLoc;

minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, Mat());

cout << minVal << " " << maxVal << " " << minLoc << " " << maxLoc << "\n";
matchLoc = maxLoc;

rectangle(im1, matchLoc, Point(matchLoc.x + im2.cols, matchLoc.y + im2.rows), Scalar::all(0), 2, 8, 0);
rectangle(result, matchLoc, Point(matchLoc.x + im2.cols, matchLoc.y + im2.rows), Scalar::all(0), 2, 8, 0);

imshow("1", im1);
imshow("2", result);
waitKey(0);

Merci pour la super bonne réponse. Si je le pouvais, je vous avais donné 100p de récompense pour celui-ci aussi :-)
Niels Kristian

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.