La recherche finale avec un exemple peut être trouvée ici.
Cela ressemble à ceci :
Je voulais créer une recherche d'images inversée pour OpenGameArt, car Google Image Search et TinEye ne donnent pas de bons résultats. J'avais précédemment généré une énorme carte de tuiles pour donner un aperçu des images similaires sur OpenGameArt, mais elle n'était pas très conviviale en ressources sur le Web ou dans le navigateur d'images et devait être divisée en fichiers plus petits. De plus, elle n'est en aucun cas consultable, juste défilable. Je voulais donc un moyen pour les gens d'explorer quel type d'art est disponible sur OpenGameArt, et j'ai décidé d'utiliser la recherche de similarité pour parcourir l'espace des images.
La première chose que j'ai dû faire a été de récupérer les résultats de recherche pour la requête qui m'intéressait sur OpenGameArt, principalement l'art 2D. Ensuite, j'ai dû récupérer chaque page HTML qui se trouvait dans l'index des résultats de recherche et analyser le code HTML à la recherche de liens vers des fichiers. OpenGameArt contient de nombreux fichiers d'archives tels que des fichiers zip et rar, j'ai donc dû les décompresser pour accéder aux images.
Par exemple, voici un extrait montrant comment analyser la page de contenu et obtenir des liens vers des fichiers :
responseBody = await Common . ReadURIOrCache ( blob , Common . BaseURI + page , client ) ;
var htmlDoc = new HtmlDocument ( ) ;
htmlDoc . LoadHtml ( responseBody ) ;
var htmlBody = htmlDoc . DocumentNode . SelectSingleNode ( " //body " ) ;
foreach ( var nNode in htmlBody . Descendants ( " a " ) )
{
if ( nNode . NodeType == HtmlNodeType . Element &&
nNode . Attributes [ " href " ] != null &&
nNode . Attributes [ " href " ] . Value . Contains ( " /default/files/ " ) )
{
msg . Add ( HttpUtility . HtmlDecode ( nNode . Attributes [ " href " ] . Value . Replace ( Common . FileURI , " " ) ) ) ;
}
}
J'ai utilisé Azure Functions pour effectuer les étapes d'exploration, avec quelques interventions manuelles de va-et-vient pour corriger les choses si nécessaire. Chaque étape avait sa propre file d'attente et plaçait ensuite le travail de l'étape suivante dans la file d'attente suivante. Au final les invocations coûtent environ 50 USD sur Azure, pour disons 10 à 20 millions d'invocations de fonction si je me souviens bien.
J'ai essayé d'utiliser la base de données open source Milvus, mais elle est tombée en panne sur mon serveur DigitalOcean car je n'avais pas assez de mémoire dessus. Ensuite, j'ai accidentellement et heureusement découvert le lien vers Pinecone dans une section de commentaires de Hacker News et j'ai décidé de l'utiliser à la place, car l'essai était gratuit et je n'avais pas besoin d'étendre la mémoire de mon serveur pour utiliser Milvus. En fin de compte, j'ai quand même étendu mon serveur, mais je n'ai pas réessayé Milvus (du moins pas encore).
J'ai utilisé l'extraction de fonctionnalités VGG16 dans mon script pour cela. Voir l'article pour plus d'informations, mais en substance, il s'agit de 4096 nombres à virgule flottante de 32 bits pour chaque image, qui décrivent diverses caractéristiques de l'image, par exemple de manière très simplifiée combien de rayures ou de carrés elle comporte ou à quel point elle est verte. . Mais ces fonctionnalités sont basées sur les neurones du réseau neuronal de VGG16 (qui est généralement utilisé pour la classification des images), de sorte que les fonctionnalités pourraient être plus compliquées que ce qui est décrit par de simples balises de fonctionnalités. Et la raison pour laquelle nous avons besoin de ces vecteurs est qu'il est facile d'utiliser la distance euclidienne ou la similarité cosinus ou une autre mesure sur deux vecteurs pour voir s'ils sont similaires, et par conséquent les images sont similaires. De plus, il existe une technologie de recherche sur ces vecteurs qui permet une recherche rapide sur un grand nombre d'entre eux.
Voici un script Python simplifié pour montrer comment effectuer l'extraction de fonctionnalités :
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vim: ft=python ts=4 sw=4 sts=4 et fenc=utf-8
from tensorflow . keras . applications . vgg16 import VGG16
from tensorflow . keras . preprocessing import image
from tensorflow . keras . applications . vgg16 import decode_predictions , preprocess_input
from tensorflow . keras . models import Model
from tensorflow . compiler import xla
import numpy as np
import time
import os
import sys
import PIL
import json
import math
import multiprocessing
from glob import glob
from PIL import Image
from io import BytesIO
model = VGG16 ( weights = 'imagenet' , include_top = True )
feat_extractor = Model ( inputs = model . input , outputs = model . get_layer ( "fc2" ). output )
def prepImage ( img ):
x = np . array ( img . resize (( 224 , 224 )). convert ( 'RGB' ))
x = np . expand_dims ( x , axis = 0 )
x = preprocess_input ( x )
return x
def main ():
'entry point'
fname = 'demo.jpg'
dt = Image . open ( fname )
pimg = prepImage ( dt )
print ( "Computing feature vector" , fname )
features = feat_extractor . predict ( pimg )
print ( features )
if __name__ == '__main__' :
main ()
Voici le résultat du script :
emh@frostpunk ~ /public_html/ogasearch 0% ./test.py (git)-[gh-pages]
2021-04-07 18:48:03.158023: W tensorflow/stream_executor/platform/default/dso_loader.cc:60] Could not load dynamic library ' libcudart.so.11.0 ' ; dlerror: libcudart.so.11.0: cannot open shared object file: No such file or directory
2021-04-07 18:48:03.158082: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.
2021-04-07 18:48:07.783109: I tensorflow/compiler/jit/xla_cpu_device.cc:41] Not creating XLA devices, tf_xla_enable_xla_devices not set
2021-04-07 18:48:07.783485: W tensorflow/stream_executor/platform/default/dso_loader.cc:60] Could not load dynamic library ' libcuda.so.1 ' ; dlerror: libcuda.so.1: cannot open shared object file: No such file or directory
2021-04-07 18:48:07.783530: W tensorflow/stream_executor/cuda/cuda_driver.cc:326] failed call to cuInit: UNKNOWN ERROR (303)
2021-04-07 18:48:07.783580: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:156] kernel driver does not appear to be running on this host (frostpunk): /proc/driver/nvidia/version does not exist
2021-04-07 18:48:07.784058: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations: AVX2 FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2021-04-07 18:48:07.784513: I tensorflow/compiler/jit/xla_gpu_device.cc:99] Not creating XLA devices, tf_xla_enable_xla_devices not set
2021-04-07 18:48:08.599925: W tensorflow/core/framework/cpu_allocator_impl.cc:80] Allocation of 411041792 exceeds 10% of free system memory.
2021-04-07 18:48:09.194634: W tensorflow/core/framework/cpu_allocator_impl.cc:80] Allocation of 411041792 exceeds 10% of free system memory.
2021-04-07 18:48:09.385612: W tensorflow/core/framework/cpu_allocator_impl.cc:80] Allocation of 411041792 exceeds 10% of free system memory.
2021-04-07 18:48:13.033066: W tensorflow/core/framework/cpu_allocator_impl.cc:80] Allocation of 411041792 exceeds 10% of free system memory.
Computing feature vector demo.jpg
2021-04-07 18:48:13.706621: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:116] None of the MLIR optimization passes are enabled (registered 2)
2021-04-07 18:48:13.717564: I tensorflow/core/platform/profile_utils/cpu_utils.cc:112] CPU Frequency: 2199995000 Hz
[[0. 3.1128967 1.5611947 ... 1.2625191 0.7709812 0. ]]
./test.py 12.20s user 4.66s system 132% cpu 12.731 total
Je voulais également mettre toutes les URL des images dans une base de données SQL à la fin, et avoir un indicateur indiquant si j'avais effectué l'extraction des fonctionnalités VGG16 et si elle avait été ajoutée à la base de données vectorielles (Milvus ou Pinecone. Il est essentiel de pouvoir mapper d'avant en arrière entre une clé primaire entière, qui est utilisée dans Pineone, et l'URL et peut-être d'autres métadonnées qui appartiennent à l'image, comme [Pinecone](https://www.pinecone.io/ ne stocke pas d'autres métadonnées que la clé primaire. En fin de compte, j'ai converti la base de données SQL dans un fichier texte séparé par des tabulations et je l'ai chargé au démarrage du serveur de requêtes.
Je pense que j'ai passé une semaine au total pour exécuter tout le code jusqu'à la fin, chaque étape prenant de l'ordre d'un jour ou deux, en explorant et en calculant les vecteurs de fonctionnalités. Je ne me souviens pas du temps qu'il a fallu pour insérer les vecteurs dans la base de données Pinecone, mais je pense que ce n'était pas l'étape la plus longue.
À la fin, j'ai également ajouté une solution rapide pour supprimer les résultats d'images quasi-dupliqués qui avaient un score identique. J'ai rencontré quelques problèmes sur la page de recherche avec le codage d'URL "double", car j'avais stocké les fichiers en utilisant le codage d'URL dans le système de fichiers, mais j'ai contourné ce problème avec un code de détection sur le frontend lorsque le navigateur codait deux fois le Noms de fichiers codés en URL. Je recommande de stocker les fichiers analysés sans encodage d'URL. Je regrette que mes scripts ne soient pas de très haute qualité ou raffinés, par exemple il y a plusieurs étapes dans les scripts et je change les choses en éditant le script au lieu de prendre des arguments de ligne de commande. Je n'ai pas envie de publier des extraits des scripts et de les expliquer car ils sont un peu compliqués. De plus, j'ai déplacé les fichiers du stockage Azure vers mon serveur DigitalOcean à mi-chemin, avant de traiter l'extraction des fonctionnalités, il y a donc une gestion incohérente de l'emplacement des données.