La búsqueda final con un ejemplo se puede encontrar aquí.
Se parece a esto:
Quería crear una búsqueda de imágenes inversa para OpenGameArt ya que Google Image Search y TinEye no dan buenos resultados. Anteriormente había generado un enorme mapa de mosaicos para brindar una descripción general de imágenes similares en OpenGameArt, pero no era muy amigable con los recursos en la web o en el navegador de imágenes y tenía que dividirse en archivos más pequeños, además no se puede buscar de ninguna manera, solo desplazable. Así que quería una forma para que la gente explorara qué tipo de arte está disponible en OpenGameArt y opté por utilizar la búsqueda por similitudes para explorar el espacio de imágenes.
Lo primero que tuve que hacer fue recuperar los resultados de la búsqueda que me interesaba en OpenGameArt, principalmente el arte 2D. Luego tuve que recuperar cada página HTML que estaba en el índice de resultados de búsqueda y analizar el HTML en busca de enlaces a archivos. OpenGameArt contiene muchos archivos como archivos zip y rar, así que tuve que descomprimirlos para acceder a las imágenes.
Por ejemplo, aquí hay un fragmento que muestra cómo analizar la página de contenido y obtener enlaces de archivos:
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 , " " ) ) ) ;
}
}
Utilicé Azure Functions para realizar los pasos de rastreo, con alguna intervención manual de ida y vuelta para corregir las cosas según fuera necesario. Cada paso tenía su propia cola y luego colocaba el trabajo para el siguiente paso en la siguiente cola. Al final, las invocaciones cuestan alrededor de 50 USD en Azure, digamos entre 10 y 20 millones de invocaciones de funciones, si no recuerdo mal.
Intenté utilizar la base de datos Milvus de código abierto, pero falló en mi servidor DigitalOcean porque no tenía suficiente memoria. Luego, accidental y afortunadamente, descubrí el enlace a Pinecone en una sección de comentarios de Hacker News y decidí usarlo en su lugar, ya que la prueba era gratuita y no tenía que expandir la memoria de mi servidor para usar Milvus. Al final amplié mi servidor de todos modos, pero no volví a probar Milvus (al menos no todavía).
Utilicé la extracción de funciones VGG16 en mi script para esto. Consulte el artículo para obtener más información, pero en esencia son 4096 números de punto flotante de 32 bits para cada imagen, que describen varias características de la imagen, digamos, por ejemplo, de una manera muy simplificada, cuántas franjas o cuadrados tiene o qué tan verde es. . Pero estas características se basan en neuronas de la red neuronal para VGG16 (que generalmente se usa para la clasificación de imágenes), por lo que las características podrían ser más complicadas que lo que se describe mediante etiquetas de características simples. Y la razón por la que necesitamos estos vectores es que es fácil usar la distancia euclidiana o la similitud del coseno u otra medida en dos vectores para ver si son similares y, en consecuencia, las imágenes son similares. Además, existe una tecnología de búsqueda sobre estos vectores que permite una búsqueda rápida en una gran cantidad de ellos.
Aquí hay un script de Python simplificado para mostrar cómo realizar la extracción de funciones:
#!/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 ()
Aquí está el resultado del 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
También quería colocar todas las URL de las imágenes en una base de datos SQL al final y tener una marca que indique si había realizado la extracción de características VGG16 y si se agregó a la base de datos vectorial (Milvus o Pinecone. Es esencial poder mapee de un lado a otro entre una clave primaria entera, que se usa en Pineone, y la URL y quizás otros metadatos que pertenecen a la imagen, ya que [Pinecone](https://www.pinecone.io/ no almacena otros metadatos que la clave principal Al final, convertí la base de datos SQL en un archivo de texto separado por tabulaciones y lo cargué al iniciar el servidor de consultas.
Creo que pasé una semana en total para ejecutar todo el código para finalizar, cada paso tomó el orden de uno o dos días, rastreo y cálculo de vectores de características. No recuerdo cuánto tiempo tomó insertar los vectores en la base de datos de Pinecone, pero creo que no fue el paso que llevó más tiempo.
Al final, también agregué una solución rápida para eliminar resultados de imágenes casi duplicadas que tenían una puntuación idéntica. Tuve algunos problemas en la página de búsqueda con codificación de URL "doble", porque había almacenado los archivos usando codificación de URL en el sistema de archivos, pero lo solucioné con algún código de detección en la interfaz para cuando el navegador codificaba dos veces el Nombres de archivos codificados en URL. Recomiendo almacenar los archivos rastreados sin codificación URL. Lamento que mis scripts no sean de tan alta calidad o no estén pulidos; por ejemplo, hay varios pasos en los scripts y cambio cosas editando el script en lugar de tomar argumentos de línea de comando. No tengo ganas de publicar fragmentos de los guiones y explicarlos porque son un poco confusos. Además, moví los archivos del almacenamiento de Azure a mi servidor DigitalOcean a mitad de camino, antes de procesar la extracción de funciones, por lo que hay un manejo inconsistente de la ubicación de los datos.