A pesquisa final com um exemplo pode ser encontrada aqui.
Parece assim:
Eu queria criar uma pesquisa reversa de imagens para OpenGameArt, pois a Pesquisa de imagens do Google e o TinEye não fornecem bons resultados. Eu já havia gerado um enorme mapa de blocos para fornecer uma visão geral de imagens semelhantes no OpenGameArt, mas não era muito amigável em termos de recursos na web ou no navegador de imagens e precisava ser dividido em arquivos menores, além de não ser pesquisável de forma alguma, apenas rolável. Então, eu queria uma maneira de as pessoas explorarem que tipo de arte está disponível no OpenGameArt e comecei a usar a pesquisa por similaridade para navegar no espaço da imagem.
A primeira coisa que tive que fazer foi recuperar os resultados da pesquisa que me interessava no OpenGameArt, principalmente a arte 2D. Então tive que recuperar cada página HTML que estava no índice de resultados da pesquisa e analisar o HTML em busca de links para arquivos. OpenGameArt contém muitos arquivos compactados, como arquivos zip e rar, então tive que descompactá-los para obter as imagens.
Por exemplo, aqui está um trecho que mostra como analisar a página de conteúdo e obter links de arquivos:
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 , " " ) ) ) ;
}
}
Usei o Azure Functions para executar as etapas de rastreamento, com algumas intervenções manuais para corrigir as coisas conforme necessário. Cada etapa tinha sua própria fila e então colocava o trabalho para a próxima etapa na próxima fila. No final, as invocações custam cerca de 50 dólares no Azure, digamos de 10 a 20 milhões de invocações de função, se bem me lembro.
Tentei usar o banco de dados Milvus de código aberto, mas ele travou no meu servidor DigitalOcean porque não tinha memória suficiente nele. Então, acidentalmente e felizmente, descobri o link para o Pinecone em uma seção de comentários do Hacker News e decidi usá-lo, já que o teste era gratuito e não precisei expandir a memória do servidor para usar o Milvus. No final, expandi meu servidor de qualquer maneira, mas não tentei o Milvus novamente (pelo menos ainda não).
Usei a extração de recursos VGG16 em meu script para isso. Consulte o artigo para obter mais informações, mas em essência são 4.096 números de ponto flutuante de 32 bits para cada imagem, que descrevem vários recursos da imagem, digamos, por exemplo, de uma forma muito simplificada, quantas listras ou quadrados ela possui ou quão verde ela é . Mas esses recursos são baseados em neurônios da rede neural do VGG16 (que geralmente é usado para classificação de imagens), portanto, os recursos podem ser mais complicados do que os descritos por simples tags de recursos. E a razão pela qual precisamos destes vetores é que é fácil usar a distância euclidiana ou a similaridade de cossenos ou outra medida em dois vetores para ver se eles são semelhantes e, consequentemente, as imagens são semelhantes. Além disso, existe uma tecnologia de busca nesses vetores que permite uma busca rápida em uma grande quantidade deles.
Aqui está um script python simplificado para mostrar como fazer a extração de recursos:
#!/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 ()
Aqui está a saída do 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
Eu também queria colocar todos os URLs de imagem em um banco de dados SQL no final, e ter um sinalizador para saber se fiz a extração do recurso VGG16 e se ele foi adicionado ao banco de dados vetorial (Milvus ou Pinecone. É essencial poder para mapeia para frente e para trás entre uma chave primária inteira, que é usada no Pineone, e a URL e talvez outros metadados que pertencem à imagem, já que [Pinecone](https://www.pinecone.io/ não armazena outros metadados do que a chave primária. No final, transformei o banco de dados SQL em um arquivo de texto separado por guias e carreguei-o na inicialização do servidor de consulta.
Acho que passei uma semana no total para executar todo o código para terminar, cada etapa tomando a ordem de um ou dois dias, rastreamento, computando vetores de recursos. Não me lembro quanto tempo demorou para inserir os vetores no banco de dados Pinecone, mas acho que não foi a etapa mais demorada.
No final, também adicionei uma solução rápida para remover resultados de imagens quase duplicados que tinham uma pontuação idêntica. Tive alguns problemas na página de pesquisa com codificação de URL "dupla", porque armazenei os arquivos usando codificação de URL no sistema de arquivos, mas resolvi isso com algum código de detecção no frontend para quando o navegador codificou duas vezes o Nomes de arquivos codificados em URL. Recomendo armazenar os arquivos rastreados sem codificação de URL. Lamento que meus scripts não sejam de alta qualidade ou polidos, por exemplo, há várias etapas nos scripts e eu mudo as coisas editando o script em vez de usar argumentos de linha de comando. Não tenho vontade de postar trechos dos scripts e explicar, pois eles são um pouco confusos. Além disso, movi os arquivos do armazenamento do Azure para meu servidor DigitalOcean no meio do caminho, antes de processar a extração de recursos, portanto, há algum tratamento inconsistente de localização de dados.