Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit...

18
Votre premier moteur de recherche d’images en Python Adrian Rosebrock

Transcript of Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit...

Page 1: Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit l’argument parser et analyse les arguments 9. ap = argparse.ArgumentParser() 10. ap.add_argument("-d",

Votre premier moteur de recherche d’images enPython

Adrian Rosebrock

Page 2: Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit l’argument parser et analyse les arguments 9. ap = argparse.ArgumentParser() 10. ap.add_argument("-d",

Un anneau pour les gouverner tous, Un anneau pour les trouver ;Un anneau pour les amener tous et dans les ténèbres les lier.

Le moteur de recherche d’images que nous sommes sur le point de construiresera si impressionnant qu’il aurait pu détruire L’Anneau Unique lui-même, sansl’aide des feux du Mont. Condamner.

D’accord, j’ai évidemment regardé beaucoup de The Hobbit et le Seigneurdes Anneaux au cours de la semaine dernière.

Et je me suis dit, tu sais ce qui serait génial ?Construire un moteur de recherche d’images simple en utilisant des captures

d’écran des films. Et c’est exactement ce que j’ai fait.Voici un aperçu rapide :— Ce que nous allons faire : Construire un moteur de recherche d’images, du

début à la fin, en utilisant les captures d’écran du Hobbit et du Seigneurdes Anneaux.

— Ce que vous allez apprendre : Les 4 étapes nécessaires pour construire unmoteur de recherche d’images, avec des exemples de code inclus. À partirde ces exemples, vous pourrez créer vos propres moteurs de recherched’images.

— Ce dont vous avez besoin : Python, NumPy et OpenCV. Une petiteconnaissance des concepts d’image de base, tels que les pixels et les his-togrammes, serait utile, mais n’est absolument pas requise. Cet articlede blog est censé être un guide pratique pour construire un moteur derecherche d’images.

Hobbits et histogrammes - Un guide pratique pourconstruire votre premier moteur de recherche d’imagesen Python

Je n’ai jamais vu un guide « Comment faire »sur la construction d’un simplemoteur de recherche d’images avant. Mais c’est exactement ce que ce post est.Nous allons utiliser (sans doute) l’un des descripteurs d’image les plus basiquespour quantifier et décrire ces captures d’écran - l’histogramme des couleurs.

J’ai discuté de l’histogramme des couleurs dans mon post précédent, un guided’utilisation des histogrammes de couleur pour la vision par ordinateur 1et lesmoteurs de recherche d’images. Si vous ne l’avez pas lu, pas de soucis, mais jevous suggère de revenir en arrière et de le lire après avoir consulté ce blog pourmieux comprendre les histogrammes de couleurs.

1. thttps ://www.pyimagesearch.com/2014/01/22/clever-girl-a-guide-to-utilizing-color-histograms-for-computer-vision-and-image-search-engines/

1

Page 3: Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit l’argument parser et analyse les arguments 9. ap = argparse.ArgumentParser() 10. ap.add_argument("-d",

Mais avant de plonger dans les détails de la construction d’un moteur derecherche d’images, jetons un coup d’œil sur notre jeu de données des capturesd’écran du Hobbit et du Seigneur des Anneaux :

Comme vous pouvez le voir, nous avons un total de 25 images différentes dansnotre ensemble de données, cinq par catégorie. Nos catégories comprennent :

— Dol Guldur : "Les donjons du Nécromancien", le fief de Sauron à Mirk-wood.

— Goblin Town : Une ville Orc dans les Monts Brumeux, la maison du RoiGobelin.

— Mordor / The Black Gate : la forteresse de Sauron, entourée de chaînesde montagnes et de plaines volcaniques.

— Fondcombe : L’avant-poste elfique de la Terre du Milieu.— La Comté : Patrie des Hobbits.

Les images de Dol Guldur, de la ville gobeline et de Rivendell proviennent duHobbit : Un voyage inattendu. Nos images Shire proviennent du Seigneur desAnneaux : La Communauté de l’Anneau. Et enfin, nos captures d’écran Mordor/ Black Gate du Seigneur des Anneaux : Le Retour du Roi.

Le but:La première chose que nous allons faire est d’indexer les 25 images dans

notre ensemble de données. L’indexation est le processus de quantification denotre ensemble de données en utilisant un descripteur d’image pour extrairedes entités de chaque image et stocker les caractéristiques résultantes pour uneutilisation ultérieure, par exemple en effectuant une recherche.

Un descripteur d’image définit la façon dont nous quantifions une image, d’oùl’extraction de caractéristiques d’une image est appelée décrivant une image. Lasortie d’un descripteur d’image est un vecteur de caractéristiques, une abstrac-tion de l’image elle-même. En termes simples, il s’agit d’une liste de nombresutilisés pour représenter une image.

Deux vecteurs de caractéristiques peuvent être comparés en utilisant unemétrique de distance. Une métrique de distance est utilisée pour déterminercomment deux images "similaires" sont en examinant la distance entre les deuxvecteurs caractéristiques. Dans le cas d’un moteur de recherche d’images, nousdonnons à notre script une image de requête et nous lui demandons de classer

2

Page 4: Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit l’argument parser et analyse les arguments 9. ap = argparse.ArgumentParser() 10. ap.add_argument("-d",

les images dans notre index en fonction de leur pertinence par rapport à notrerequête.

Pensez-y de cette façon. Lorsque vous accédez à Google et tapez «Seigneurdes anneaux» dans la zone de recherche, vous vous attendez à ce que Google vousrenvoie des pages pertinentes pour les livres de Tolkien et la franchise de films.De même, si nous présentons un moteur de recherche d’images avec une image derequête, nous attendons des images pertinentes pour le contenu de l’image. Parconséquent, nous appelons parfois les moteurs de recherche d’images Systèmesde récupération d’images (CBIR).

Alors, quel est l’objectif global de notre moteur de recherche d’images Lordof the Rings ?

Le but, donné une image de requête d’une de nos cinq catégories différentes,est de renvoyer les images correspondantes de la catégorie dans le top 10 desrésultats.

C’était une bouchée. Prenons un exemple pour le rendre plus clair.Si je soumettais une image de requête de The Shire à notre système, je

m’attendrais à ce qu’il me donne toutes les 5 images de Shire dans notre jeu dedonnées dans les 10 premiers résultats. Et encore, si j’ai soumis une image derequête de Rivendell, je m’attendrais à ce que notre système me donne toutesles 5 images de Rivendell dans les 10 premiers résultats.

Avoir du sens ? Bien. Parlons des quatre étapes pour construire notre moteurde recherche d’images.

Les 4 étapes pour construire un moteur de re-cherche d’images

Au niveau le plus basique, il y a quatre étapes pour construire un moteurde recherche d’images :

1. Définissez votre descripteur : Quel type de descripteur allez-vous utiliser ?Décrivez-vous la couleur ? Texture ? Forme ?

2. Indexez votre ensemble de données : appliquez votre descripteur à chaqueimage de votre ensemble de données, en extrayant un ensemble de carac-téristiques.

3. Définissez votre métrique de similarité : Comment allez-vous définir lafaçon dont deux images "similaires" sont ? Vous utiliserez probablementune sorte de métrique de distance. Les choix communs incluent Euclidien,Cityblock (Manhattan), Cosine, et le chi-carré pour n’en nommer quequelques-uns.

4. Recherche : Pour effectuer une recherche, appliquez votre descripteur àvotre image de requête, puis demandez à votre métrique de distance declasser la similitude de vos images dans votre index avec vos images derequête. Triez vos résultats par similarité, puis examinez-les.

3

Page 5: Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit l’argument parser et analyse les arguments 9. ap = argparse.ArgumentParser() 10. ap.add_argument("-d",

Étape no 1 : Le descripteur - Un histogramme decouleur 3D RVB

Notre descripteur d’image est un histogramme de couleur 3D dans l’espacecolorimétrique RVB avec 8 compartiments par canal rouge, vert et bleu.

La meilleure façon d’expliquer un histogramme 3D est d’utiliser l’AND conjonc-tive. Ce descripteur d’image demandera à une image donnée combien de pixelsont une valeur rouge qui tombe dans la case n ◦ 1 ET une valeur verte qui tombedans la casier n ◦ 2 et combien de pixels bleus tombe dans la casier n ◦ 1. Ceprocessus sera répété pour chaque combinaison de bacs ; Cependant, cela serafait d’une manière efficace sur le plan informatique.

Lors du calcul d’un histogramme 3D avec 8 cases, OpenCV stockera le vec-teur de caractéristiques sous la forme d’un tableau (8, 8, 8). Nous allons simple-ment l’aplatir et le remodeler en (512,). Une fois qu’il est aplati, nous pouvonsfacilement comparer les vecteurs de caractéristiques pour les comparer.

Prêt à voir du code ? D’accord, on y va :

1. # import the necessary packages2. import imutils3. import cv24.5. class RGBHistogram:6. def __init__(self, bins):7. # stocke le nombre de cases que l’histogramme utilisera8. self.bins = bins9.10. def describe(self, image):11. # calculer un histogramme 3D dans l’espace colorimétrique RVB,12. # puis normalise l’histogramme pour que les images13. # avec le même contenu, mais soit mis à l’échelle plus grand14. # ou plus petit aura (à peu près) le même histogramme15. hist = cv2.calcHist([image], [0, 1, 2],16. None, self.bins, [0, 256, 0, 256, 0, 256])17.18. # normalise avec OpenCV 2.419. if imutils.is_cv2():20. hist = cv2.normalize(hist)21.22. # Normaliser avec OpenCV 3+23. else:24. hist = cv2.normalize(hist,hist)25.26. # renvoie l’histogramme 3D en tant que tableau array27. return hist.flatten()

Comme vous pouvez le voir, j’ai défini une classe RGBHistogram. J’ai ten-dance à définir mes descripteurs d’image comme des classes plutôt que commedes fonctions. La raison en est que vous extrayez rarement des entités d’uneseule image. Au lieu de cela, vous extrayez les caractéristiques d’un ensembled’images entier. De plus, vous vous attendez à ce que les caractéristiques ex-traites de toutes les images utilisent les mêmes paramètres - dans ce cas, lenombre de cases pour l’histogramme. Il ne serait pas judicieux d’extraire unhistogramme en utilisant 32 bins d’une image et ensuite 128 bins pour uneautre image si vous avez l’intention de les comparer pour la similarité.

Prenons le code à part et comprenons ce qui se passe :— Lignes 6-8 : Ici, je définis le constructeur pour le RGBHistogram. Le seul

4

Page 6: Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit l’argument parser et analyse les arguments 9. ap = argparse.ArgumentParser() 10. ap.add_argument("-d",

paramètre dont nous avons besoin est le nombre de classes pour chaquecanal dans l’histogramme. Encore une fois, c’est pourquoi je préfère uti-liser des classes au lieu de fonctions pour les descripteurs d’image - enplaçant les paramètres pertinents dans le constructeur, vous vous assurezque les mêmes paramètres sont utilisés pour chaque image.

— Ligne 10 : Vous l’avez deviné. La méthode describe est utilisée pour"décrire" l’image et retourner un vecteur de caractéristiques.

— Lignes 15 et 16 : Ici, nous extrayons l’histogramme RVB 3D actuel (ouen réalité, BGR puisque OpenCV stocke l’image sous forme de tableauNumPy, mais avec les canaux dans l’ordre inverse). Nous supposons queself.bins est une liste de trois nombres entiers, désignant le nombre decases pour chaque canal.

— Lignes 19-24 : Il est important de normaliser l’histogramme en termes denombre de pixels. Si nous utilisions les nombres de pixels bruts (entiers)d’une image, puis la rétrécissions de 50

— Ligne 27 : Lors du calcul d’un histogramme 3D, l’histogramme sera re-présenté par un tableau NumPy avec des cases (N, N, N). Afin de calcu-ler plus facilement la distance entre les histogrammes, nous avons sim-plement aplatir cet histogramme pour avoir une forme de (N ** 3,).Exemple : Lorsque nous instancions notre RGBHistogram, nous utilise-rons 8 bins par canal. Sans aplatir notre histogramme, la forme serait (8,8, 8). Mais en l’aplatissant, la forme devient (512,).

Maintenant que nous avons défini notre descripteur d’image, nous pouvons pas-ser au processus d’indexation de notre ensemble de données.

Étape no 2 : Indexation de notre jeu de donnéesOk, nous avons donc décidé que notre descripteur d’image est un histo-

gramme 3D RVB. L’étape suivante consiste à appliquer notre descripteur d’imageà chaque image de l’ensemble de données.

Cela signifie simplement que nous allons faire une boucle sur notre ensemblede 25 images, extraire un histogramme 3D RVB de chaque image, stocker lescaractéristiques dans un dictionnaire et écrire le dictionnaire dans un fichier.

Ouais, c’est tout.En réalité, vous pouvez rendre l’indexation aussi simple ou complexe que

vous le souhaitez. L’indexation est une tâche facilement réalisable en parallèle.Si nous avions une machine à quatre cœurs, nous pourrions diviser le travailentre les quatre cœurs et accélérer le processus d’indexation. Mais puisque nousn’avons que 25 images, c’est assez idiot, surtout vu la rapidité de calcul d’unhistogramme.

5

Page 7: Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit l’argument parser et analyse les arguments 9. ap = argparse.ArgumentParser() 10. ap.add_argument("-d",

Plongeons dans du code :

1. # importer les paquets nécessaires2. from pyimagesearch.rgbhistogram import RGBHistogram3. from imutils.paths import list_images4. import argparse5. import pickle6. import cv27.8. # construit l’argument parser et analyse les arguments9. ap = argparse.ArgumentParser()10. ap.add_argument("-d", "--dataset", required = True,11. help = "Path to the directory that contains the images to be indexed")12. ap.add_argument("-i", "--index", required = True,13. help = "Path to where the computed index will be stored")14. args = vars(ap.parse_args())15.

16. # initialise le dictionnaire d’index pour stocker notre notre quantifié17. # images, avec la "clé"du dictionnaire étant l’image18. # nom de fichier et la ’valeur’ de nos fonctionnalités calculées19. index = {}

D’accord, la première chose que nous allons faire est d’importer les paquetsdont nous avons besoin. J’ai décidé de stocker la classe RGBHistogram dansun module appelé pyimagesearch. Je veux dire, ça n’a de sens que ça, n’est-cepas ? Nous allons utiliser cPickle pour vider notre index sur le disque. Et nousutiliserons glob pour obtenir les chemins des images que nous allons indexer.

L’argument –dataset est le chemin vers lequel nos images sont stockées surle disque et l’option –index est le chemin vers lequel nous allons stocker notreindex une fois qu’il aura été calculé.

Enfin, nous allons initialiser notre index - un type de dictionnaire Pythonintégré. La clé pour le dictionnaire sera le nom de fichier de l’image. Nous avonsfait l’hypothèse que tous les noms de fichiers sont uniques, et en fait, pour cetensemble de données, ils le sont. La valeur du dictionnaire sera l’histogrammecalculé pour l’image.

L’utilisation d’un dictionnaire pour cet exemple est la plus logique, surtoutà des fins d’explication. Étant donné une clé, le dictionnaire pointe vers unautre objet. Lorsque nous utilisons un nom de fichier d’image comme une clé etl’histogramme comme valeur, nous supposons qu’un histogramme H donné estutilisé pour quantifier et représenter l’image avec le nom de fichier K.

Encore une fois, vous pouvez rendre ce processus aussi simple ou aussi com-pliqué que vous le souhaitez. Des descripteurs d’image plus complexes font appelà la pondération fréquentielle fréquentielle-fréquence inverse du document (tf-idf) et à un index inversé, mais nous allons rester à l’écart de cela pour l’instant.Ne vous inquiétez pas, j’aurai beaucoup de billets de blog sur la façon dont nouspouvons tirer parti de techniques plus complexes, mais pour le moment, gardonsles choses simples.

21. # initialise notre descripteur d’image - un histogramme 3D RVB avec22.# 8 bins par canal23. desc = RGBHistogram([8, 8, 8])

Ici, nous instancions notre RGBHistogram. Encore une fois, nous utiliserons

6

Page 8: Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit l’argument parser et analyse les arguments 9. ap = argparse.ArgumentParser() 10. ap.add_argument("-d",

8 bins pour chaque canal, rouge, vert et bleu, respectivement.

25. # utilise list_images pour saisir les chemins de l’image et les faire défiler26. for imagePath in list_images(args["dataset"]):27. # extrait notre identifiant d’image unique (c’est-à-dire le nom du fichier)28. k = imagePath[imagePath.rfind("/") + 1:]29.30. # chargez l’image, décrivez-la en utilisant notre histogramme RVB31. # descripteur et mettre à jour l’index32. image = cv2.imread(imagePath)33. features = desc.describe(image)34. index[k] = features

Voici où l’indexation réelle a lieu. Décomposons-le :— Ligne 26 : Nous utilisons list _images pour saisir les chemins de l’image

et commencer à faire une boucle sur notre ensemble de données.— Ligne 28 : Nous extrayons la "clé" pour notre dictionnaire. Tous les noms

de fichiers sont uniques dans cet exemple de jeu de données, de sorte quele nom de fichier lui-même sera suffisant pour servir de clé.

— Ligne 32-34 : L’image est chargée sur le disque et nous utilisons ensuitenotre RGBHistogram pour extraire un histogramme de l’image. L’histo-gramme est ensuite stocké dans l’index.

36. # nous avons maintenant fini d’indexer notre image - maintenant nous pouvons écrire notre37. #index to disk38. f = open(args["index"], "wb")39. f.write(pickle.dumps(index))40. f.close()41.42. # montre combien d’images nous avons indexées43. print("[INFO] done...indexed {} images".format(len(index)))

Maintenant que notre index a été calculé, nous l’écrivons sur le disque afinque nous puissions l’utiliser pour chercher plus tard.

Pour indexer votre moteur de recherche d’images, entrez simplement ce quisuit dans votre terminal (en notant les arguments de la ligne de commande 2) :

$ python index.py --dataset images --index index.cpickle[INFO] done...indexed 25 images

Étape no 3 : La rechercheNous avons maintenant notre index assis sur le disque, prêt à être fouillé.Le problème est, nous avons besoin de code pour effectuer la recherche

réelle. Comment allons-nous comparer deux vecteurs caractéristiques et com-ment allons-nous déterminer à quel point ils sont similaires ?

2. www.pyimagesearch.com/2018/03/12/python-argparse-command-line-arguments/

7

Page 9: Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit l’argument parser et analyse les arguments 9. ap = argparse.ArgumentParser() 10. ap.add_argument("-d",

Cette question est mieux traitée d’abord avec du code, ensuite je vais ledécomposer.

1. # importer les paquets nécessaires2. import numpy as np3.4. class Searcher:5. def __init__(self, index):6. # stocker notre index d’images7. self.index = index8.9. def search(self, queryFeatures):10. # initialise notre dictionnaire de résultats11. results = {}12.13. # boucle sur l’index14. for (k, features) in self.index.items():15. # calculer la distance du chi-carré entre les entités16. # dans notre index et nos fonctionnalités de requête - en utilisant le17. # distance chi-carré qui est normalement utilisée dans le18. # champ de vision par ordinateur pour comparer les histogrammes19. d = self.chi2_distance(features, queryFeatures)20.21. # maintenant que nous avons la distance entre les deux caractéristiques22. # vecteurs, nous pouvons udpate le dictionnaire de résultats - le23. # La touche # est l’identifiant d’image actuel dans l’index et le24. # valeur est la distance que nous venons de calculer, représentant25. # comment ’similaire’ l’image dans l’index est à notre26. results[k] = d27.28. # trier nos résultats, de sorte que les plus petites distances (c.-à-d.29. # des images plus pertinentes sont en tête de liste)30. results = sorted([(v, k) for (k, v) in results.items()])31.32. # retourner nos résultats33. return results34.35. def chi2_distance(self, histA, histB, eps = 1e-10):36. # calcule la distance du chi-carré37. d = 0.5 * np.sum([((a - b) ** 2) / (a + b + eps)38. for (a, b) in zip(histA, histB)])39.40. # retourne la distance du chi-carré41. return d

Tout d’abord, la plupart de ce code est juste des commentaires. N’ayez paspeur que ce soit 41 lignes. Si vous ne l’avez pas déjà deviné, j’aime le code biencommenté. Voyons ce qui se passe :

— Lignes 4-7 : La première chose que je fais est de définir une classe Searcheret un constructeur avec un seul paramètre - l’index. Cet index est supposéêtre le dictionnaire d’index que nous avons écrit pour le fichier pendantl’étape d’indexation.

— Ligne 11 : Nous définissons un dictionnaire pour stocker nos résultats.La clé est le nom de fichier de l’image (à partir de l’index) et la valeurcorrespond à la similitude entre l’image donnée et l’image de la requête.

— Lignes 14-26 : Voici la partie où la recherche proprement dite a lieu.Nous bouclons sur les noms de fichiers d’images et les fonctionnalitéscorrespondantes dans notre index. Nous utilisons ensuite la distance duchi-carré pour comparer nos histogrammes de couleur. La distance cal-culée est ensuite stockée dans le dictionnaire des résultats, indiquant àquel point les deux images sont semblables les unes aux autres.

8

Page 10: Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit l’argument parser et analyse les arguments 9. ap = argparse.ArgumentParser() 10. ap.add_argument("-d",

— Lignes 30-33 : Les résultats sont triés en termes de pertinence (plus ladistance chi-carré est faible, pertinente / similaire) et renvoyés.

— Lignes 35-41 : Ici, nous définissons la fonction de distance chi-carré utili-sée pour comparer les deux histogrammes. En général, la différence entreles grands bacs et les petits bacs est moins importante et devrait êtrepondérée en tant que telle. C’est exactement ce que fait la distance duchi-carré. Nous fournissons une valeur faciale epsilon pour éviter ces er-reurs "diviser par zéro" embêtantes. Les images seront considérées commeidentiques si leurs vecteurs de caractéristiques ont une distance khi-carréde zéro. Plus la distance est grande, moins ils sont similaires.

Donc là vous l’avez, une classe Python qui peut prendre un index et effectuerune recherche. Il est maintenant temps de mettre ce chercheur au travail.

Remarque : Pour ceux qui sont plus enclins aux études, vous pouvez consulterla famille de l’histogramme quadratique Chi de la conférence ECCV ’10 3 si vousêtes intéressé par les métriques de distance d’histogramme.

Étape no 4 : Effectuer une rechercheFinalement. Nous nous rapprochons d’un moteur de recherche d’images fonc-

tionnel.Mais nous ne sommes pas encore là. Nous avons besoin d’un petit code

supplémentaire pour gérer le chargement des images sur le disque et effectuerla recherche :

1. # importer les paquets nécessaires2. from pyimagesearch.searcher import Searcher3. import numpy as np4. import argparse5. import os6. import pickle7. import cv28.9. # construit l’argument parser et analyse les arguments10. ap = argparse.ArgumentParser()11. ap.add_argument("-d", "--dataset", required = True,12. help = "Path to the directory that contains the images we just indexed")13. ap.add_argument("-i", "--index", required = True,14. help = "Path to where we stored our index")15. args = vars(ap.parse_args())16.17. # charge l’index et initialise notre chercheur18. index = pickle.loads(open(args["index"], "rb").read())19. searcher = Searcher(index)

Premières choses d’abord. Importez les paquets dont nous aurons besoin.Comme vous pouvez le voir, j’ai stocké notre classe Searcher dans le modulepyimagesearch. Nous définissons ensuite nos arguments de la même manièreque nous l’avons fait lors de l’étape d’indexation. Enfin, nous utilisons cPicklepour charger notre index sur le disque et initialiser notre Searcher.

3. http ://ofirpele.droppages.com/ECCV2010.pdf

9

Page 11: Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit l’argument parser et analyse les arguments 9. ap = argparse.ArgumentParser() 10. ap.add_argument("-d",

21. # boucle sur les images dans l’index - nous allons utiliser chacun comme22. # une image de requête23. for (query, queryFeatures) in index.items():24. # effectue la recherche en utilisant la requête en cours25. results = searcher.search(queryFeatures)26.27. # charger l’image de la requête et l’afficher28. path = os.path.join(args["dataset"], query)29. queryImage = cv2.imread(path)30. cv2.imshow("Query", queryImage)31. print("query: {}".format(query))32.33. # initialise les deux montages pour afficher nos résultats -34. # nous avons un total de 25 images dans l’index, mais seulement35. # afficher les 10 premiers résultats; 5 images par montage, avec36. # images de 400 x 166 pixels37. montageA = np.zeros((166 * 5, 400, 3), dtype = "uint8")38. montageB = np.zeros((166 * 5, 400, 3), dtype = "uint8")39.40. # boucle sur les dix premiers résultats41. for j in range(0, 10):42. # saisir le résultat (nous utilisons l’ordre row-major) et43. # charger l’image du résultat44. (score, imageName) = results[j]45. path = os.path.join(args["dataset"], imageName)46. result = cv2.imread(path)47. print("\t{}. {} : {:.3f}".format(j + 1, imageName, score))48.49. # vérifier pour voir si le premier montage doit être utilisé50. if j < 5:51. montageA[j * 166:(j + 1) * 166, :] = result52.53. # sinon, le second montage doit être utilisé54. else:55. montageB[(j - 5) * 166:((j - 5) + 1) * 166, :] = result56.57. # afficher les résultats58. cv2.imshow("Results 1-5", montageA)59. cv2.imshow("Results 6-10", montageB)60. cv2.waitKey(0)

La plupart de ce code gère l’affichage des résultats. La "recherche" réelle esteffectuée sur une seule ligne (# 31). Peu importe, examinons ce qui se passe :

— Ligne 23 : Nous allons traiter chaque image de notre index comme unerequête et voir quels résultats nous obtenons. Normalement, les requêtessont externes et ne font pas partie de l’ensemble de données, mais avantd’en arriver là, faisons quelques exemples de recherches.

— Ligne 25 : C’est ici que la recherche proprement dite a lieu. Nous traitonsl’image actuelle comme notre requête et effectuons la recherche.

— Lignes 28-31 : Charger et afficher notre image de requête.— Lignes 37-55 : Afin d’afficher les 10 premiers résultats, j’ai décidé d’utiliser

deux images de montage. Le premier montage montre les résultats 1-5et le second montage donne les résultats 6-10. Le nom de l’image et ladistance sont fournis sur la ligne 27.

— Lignes 58-60 : Enfin, nous affichons nos résultats de recherche à l’utilisa-teur.

Donc là vous l’avez. Un moteur de recherche d’images complet en Python.

10

Page 12: Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit l’argument parser et analyse les arguments 9. ap = argparse.ArgumentParser() 10. ap.add_argument("-d",

Voyons comment cette chose fonctionne :

$ python search.py –dataset images –index index.cpickle

Finalement, après avoir appuyé sur une touche avec une fenêtre active, vousserez présenté avec cette requête Mordor et les résultats :

query: Mordor-002.png1. Mordor-002.png : 0.0002. Mordor-004.png : 0.2963. Mordor-001.png : 0.5324. Mordor-003.png : 0.5645. Mordor-005.png : 0.7116. Goblin-002.png : 0.8257. Rivendell-002.png : 0.8388. Rivendell-004.png : 0.9809. Goblin-001.png : 0.99410. Rivendell-005.png : 0.996

Figure 2 : Résultats de la recherche en utilisant Mordor-002.png comme une requête.Notre moteur de recherche d’images est capable de renvoyer des images de Mordor etde la Porte Noire. Commençons à la fin du Retour du Roi en utilisant l’ascension deFrodon et de Sam dans le volcan comme image de requête. Comme vous pouvez leconstater, nos 5 premiers résultats proviennent de la catégorie "Mordor".

11

Page 13: Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit l’argument parser et analyse les arguments 9. ap = argparse.ArgumentParser() 10. ap.add_argument("-d",

Peut-être vous demandez-vous pourquoi l’image de la requête de Frodonet Sam est aussi l’image dans la position de résultat # 1 ? Bien, repensonsà notre distance du chi-carré. Nous avons dit qu’une image serait considérée« identique »si la distance entre les deux vecteurs caractéristiques est nulle.Puisque nous utilisons des images que nous avons déjà indexées en tant querequêtes, elles sont en fait identiques et auront une distance de zéro. Comme lavaleur zéro indique une similitude parfaite, l’image de la requête apparaît dansla position de résultat # 1.

Maintenant, essayons une autre image, cette fois en utilisant The GoblinKing dans Goblin Town. Lorsque vous êtes prêt, appuyez simplement sur unetouche pendant qu’une fenêtre OpenCV est active jusqu’à ce que vous voyiezcette requête de Goblin King :

query: Goblin-004.png1. Goblin-004.png : 0.0002. Goblin-003.png : 0.1033. Golbin-005.png : 0.1884. Goblin-001.png : 0.3355. Goblin-002.png : 0.3636. Mordor-005.png : 0.5947. Rivendell-001.png : 0.6778. Mordor-003.png : 0.8589. Rivendell-002.png : 0.99810. Mordor-001.png : 0.999

Figure 3 : Résultats de la recherche utilisant Goblin-004.png comme requête. Le top5 des images retournées proviennent de Goblin Town. Le roi gobelin n’a pas l’air trèsheureux. Mais nous sommes sûrs que les cinq images de la ville de Goblin sont dansles 10 premiers résultats.

12

Page 14: Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit l’argument parser et analyse les arguments 9. ap = argparse.ArgumentParser() 10. ap.add_argument("-d",

Enfin, voici trois autres exemples de recherches pour Dol-Guldur, Rivendell et TheShire. Encore une fois, nous pouvons clairement voir que les cinq images de leurscatégories respectives sont dans les 10 premiers résultats.

Figure 4 : Utilisation des images de Dol-Guldur (Dol-Guldur-004.png), de Rivendell(Rivendell-003.png) et de The Shire (Shire-002.png) en tant que requêtes.

Bonus : requêtes externesPour le moment, je vous ai seulement montré comment effectuer une recherche en

utilisant des images qui sont déjà dans votre index. Mais clairement, ce n’est pas commeça que fonctionnent tous les moteurs de recherche d’images. Google vous permet detélécharger une image de votre choix. TinEye vous permet de télécharger une image devotre choix. Pourquoi ne pouvons-nous pas ? Voyons comment nous pouvons effectuerune recherche en utilisant une image que nous n’avons pas encore indexée :

13

Page 15: Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit l’argument parser et analyse les arguments 9. ap = argparse.ArgumentParser() 10. ap.add_argument("-d",

1. # importer les paquets nécessaires2. from pyimagesearch.rgbhistogram import RGBHistogram3. from pyimagesearch.searcher import Searcher4. import numpy as np5. import argparse6. import os7. import pickle8. import cv29.10. # construit l’argument parser et analyse les arguments11.ap = argparse.ArgumentParser()12. ap.add_argument("-d", "--dataset", required = True,13. help = "Path to the directory that contains the images we just indexed")14. ap.add_argument("-i", "--index", required = True,15. help = "Path to where we stored our index")16. ap.add_argument("-q", "--query", required = True,17. help = "Path to query image")18. args = vars(ap.parse_args())19.20. # charge l’image de la requête et la montre21. queryImage = cv2.imread(args["query"])22. cv2.imshow("Query", queryImage)23. print("query: {}".format(args["query"]))24.25. # décrire la requête de la même manière que nous l’avons fait26. index.py - un histogramme 3D RVB avec 8 bins par27. # canal28. desc = RGBHistogram([8, 8, 8])29. queryFeatures = desc.describe(queryImage)30.31. # charger l’index effectuer la recherche32. index = pickle.loads(open(args["index"], "rb").read())33. searcher = Searcher(index)34. results = searcher.search(queryFeatures)35.36. # initialise les deux montages pour afficher nos résultats -37. # nous avons un total de 25 images dans l’index, mais seulement38. # afficher les 10 premiers résultats; 5 images par montage, avec39. # images de 400 x 166 pixels40. montageA = np.zeros((166 * 5, 400, 3), dtype = "uint8")41. montageB = np.zeros((166 * 5, 400, 3), dtype = "uint8")42.43. # charger l’index effectuer la recherche44. for j in range(0, 10):45. # saisir le résultat (nous utilisons l’ordre row-major) et46. # charger l’image du résultat47. (score, imageName) = results[j]48. path = os.path.join(args["dataset"], imageName)49. result = cv2.imread(path)50. print("\t{}. {} : {:.3f}".format(j + 1, imageName, score))51.52. # vérifier si le premier montage doit être utilisé53. if j < 5:54. montageA[j * 166:(j + 1) * 166, :] = result55.56. # sinon, le second montage doit être utilisé57. else:58. montageB[(j - 5) * 166:((j - 5) + 1) * 166, :] = result59.60. # afficher les résultats61. cv2.imshow("Results 1-5", montageA)62. cv2.imshow("Results 6-10", montageB)63. cv2.waitKey(0)

— Lignes 2-18 : Cela devrait ressembler à des choses assez standard maintenant.Nous importons nos paquets et installons notre analyseur d’arguments, bienque vous devriez noter le nouvel argument –query. C’est le chemin vers notreimage de requête.

— Lignes 21 et 22 : Nous allons charger votre image de requête et vous la montrer,

14

Page 16: Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit l’argument parser et analyse les arguments 9. ap = argparse.ArgumentParser() 10. ap.add_argument("-d",

juste au cas où vous auriez oublié quelle est l’image de votre requête.— Lignes 28 et 29 : Instancier notre RGBHistogram avec exactement le même

nombre de classes que lors de notre étape d’indexation. Je mets cela en graset en italique pour montrer à quel point il est important d’utiliser les mêmesparamètres. Nous extrayons ensuite les caractéristiques de notre image de re-quête.

— Lignes 32-34 : Charger notre index du disque en utilisant cPickle et effectuerla recherche.

— Lignes 40-63 : Tout comme dans le code ci-dessus pour effectuer une recherche,ce code nous montre simplement nos résultats.

Avant d’écrire cet article de blog, je suis allé sur Google et j’ai téléchargé deuximages non présentes dans notre index. Un de Rivendell et l’un des Shire. Ces deuximages seront nos questions.

Pour exécuter ce script, fournissez simplement les arguments de ligne de commandecorrects dans le terminal :

$ python search_external.py --dataset images --index index.cpickle \--query queries/rivendell-query.pngquery: queries/rivendell-query.png1. Rivendell-002.png : 0.1952. Rivendell-004.png : 0.4493. Rivendell-001.png : 0.6434. Rivendell-005.png : 0.7575. Rivendell-003.png : 0.7696. Mordor-001.png : 0.8097. Mordor-003.png : 0.8588. Goblin-002.png : 0.8759. Mordor-005.png : 0.89410. Mordor-004.png : 0.909$ python search_external.py --dataset images --index index.cpickle \--query queries/shire-query.pngquery: queries/shire-query.png1. Shire-004.png : 1.0772. Shire-003.png : 1.1143. Shire-001.png : 1.2784. Shire-002.png : 1.3765. Shire-005.png : 1.7796. Rivendell-001.png : 1.8227. Rivendell-004.png : 2.0778. Rivendell-002.png : 2.1469. Golbin-005.png : 2.17010. Goblin-001.png : 2.198

Consultez les résultats pour les deux images de requête ci-dessous :

15

Page 17: Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit l’argument parser et analyse les arguments 9. ap = argparse.ArgumentParser() 10. ap.add_argument("-d",

Figure 5 : Utilisation des images de requête externes Rivendell (à gauche) et Shire(à droite). Dans les deux cas, nous trouvons que les 5 premiers résultats de rechercheproviennent de la même catégorie.

Dans ce cas, nous avons cherché en utilisant deux images que nous n’avionspas vues précédemment. Celui de gauche est de Rivendell. Nous pouvons voir àpartir de nos résultats que les 5 autres images de Rivendell dans notre index ontété retournées, démontrant que notre moteur de recherche d’images fonctionnecorrectement.

Sur la droite, nous avons une image de requête de The Shire. Encore une fois,cette image n’est pas présente dans notre index. Mais lorsque nous regardonsles résultats de la recherche, nous pouvons voir que les 5 autres images de Shireont été renvoyées par le moteur de recherche d’images, démontrant une fois deplus que notre moteur de recherche d’images renvoie des images sémantiquementsimilaires.

RésuméDans cet article de blog, nous avons exploré comment créer un moteur de

recherche d’images du début à la fin. La première étape consistait à choisirun descripteur d’image - nous avons utilisé un histogramme 3D RVB pour ca-ractériser la couleur de nos images. Nous avons ensuite indexé chaque imagedans notre ensemble de données en utilisant notre descripteur en extrayant desvecteurs de caractéristiques (c’est-à-dire les histogrammes). De là, nous avonsutilisé la distance chi-carré pour définir la «similarité» entre deux images. Enfin,nous avons collé toutes les pièces et créé un moteur de recherche d’images duSeigneur des Anneaux.

Alors, quelle est la prochaine étape ?Nous commençons tout juste. En ce moment, nous ne faisons que gratter la

surface des moteurs de recherche d’images. Les techniques de ce blog sont as-sez élémentaires. Il y a beaucoup à construire. Par exemple, nous nous sommesconcentrés sur la description de la couleur en utilisant uniquement des histo-

16

Page 18: Votre premier moteur de recherche d’images en … et OpenCV...6. import cv2 7. 8. # construit l’argument parser et analyse les arguments 9. ap = argparse.ArgumentParser() 10. ap.add_argument("-d",

grammes. Mais comment pouvons-nous décrire la texture ? Ou forme ? Et quelest ce descripteur SIFT mystique ?

Toutes ces questions et plus, répondu dans les mois à venir.

17