Implementación de la recuperación de imágenes mediante visión artificial: La guía técnica para obtener resultados

Share
Implementación de la recuperación de imágenes mediante visión artificial: La guía técnica para obtener resultados

Durante décadas, la gestión de activos digitales se ha basado en una base frágil: etiquetado manual. Buscamos imágenes utilizando palabras clave, nombres de archivo y metadatos. Este enfoque es fundamentalmente no escalable, subjetivo y fracasa por completo cuando faltan o son incorrectos los metadatos. A medida que los datos empresariales crecen exponencialmente con información visual sin estructura (desde contenido generado por el usuario hasta fotografías de productos e imágenes satelitales), este paradigma basado en texto colapsa.

La recuperación de imágenes basada en contenido (CBIR) resuelve esto. En lugar de buscar metadatos sobre una imagen, CBIR busca el contenido visual de la propia imagen. Te permite "encontrar más imágenes que se parezcan a esta."

Implementar un sistema robusto de CBIR ya no es un problema de investigación; es un componente fundamental de los servicios de ingeniería de IA para empresas. Permite nuevas experiencias de usuario (búsqueda visual), automatiza la moderación (identificación de contenido perjudicial similar visualmente), facilita la deduplicación y potencia los motores de recomendación.

Este artículo es una guía técnica para directores de tecnología (CTOs) e ingenieros senior encargados de construir un sistema CBIR escalable y de alto rendimiento. Pasaremos por encima de diagramas de alto nivel y nos centraremos en las decisiones arquitectónicas, los componentes clave y el código de implementación necesarios para su creación.

Servicios de Ingeniería de LLM y IA

Ofrecemos una completa gama de soluciones impulsadas por la IA, que incluyen IA generativa, visión artificial, aprendizaje automático, procesamiento del lenguaje natural y automatización con respaldo de IA.

Learn more

Una tubería de tres fases

En esencia, un sistema de CBIR (Búsqueda por imágenes) es una línea de procesamiento de datos que transforma píxeles no estructurados en un índice estructurado y buscable de vectores de alta dimensión.

El sistema completo puede descomponerse en tres fases distintas:

  1. Fase 1: Extracción de características: Un modelo de aprendizaje profundo (típicamente una CNN) "analiza" una imagen y resume su contenido visual en una lista numérica llamada vector de características o incrustación. Este vector, que puede tener hasta 2048 dimensiones, es la "huella digital" visual de la imagen.
  2. Fase 2: Indexación: Almacenar millones o miles de millones de estos vectores de alta dimensionalidad no es tarea para una base de datos SQL tradicional. Utilizamos una base de datos especializada Vector Database que utiliza algoritmos de "Vecino Más Cercano Aproximado" (ANN) para crear un índice de búsqueda.
  3. Fase 3: Consulta y Recuperación: Cuando un usuario proporciona una imagen, ésta pasa por el mismo extractor de características (Fase 1). Su vector resultante se utiliza para buscar en la Base de Datos Vectorial (Fase 2), que devuelve los IDs de los vectores "más cercanos" (es decir, las imágenes visualmente más similares) en milisegundos.

Analicemos la implementación de cada fase.

Fase 1: Extracción de características con aprendizaje profundo

El objetivo es convertir una imagen $I$ en un vector $v \in \mathbb{R}^D$, de modo que imágenes visualmente similares como $I_1$ e $I_2$ produzcan vectores $v_1$ y $v_2$ que estén "cerca" en la distancia euclidiana ($L_2$).

Intentar utilizar valores de píxeles sin procesar es inútil debido al fenómeno de la dimensionalidad. Una simple imagen de 224x224x3 es un vector de 150,528 dimensiones, donde la mayor parte de los datos son ruido espacialmente correlacionado.

La herramienta: Redes neuronales convolucionales (CNNs)

Utilizamos aprendizaje por transferencia con una CNN pre-entrenada (por ejemplo, ResNet-50, EfficientNet, VGG-16) que fue entrenada en un conjunto de datos masivo como ImageNet. Estos modelos ya han aprendido una rica jerarquía de características visuales: desde bordes y texturas simples en las primeras capas hasta partes complejas de objetos en las capas más profundas.

Nosotros no nos preocupamos por la clasificación final del modelo (por ejemplo, "gato", "perro"). Nos preocupa la capa de "pre-clasificación", que representa el resumen interno del modelo de la imagen. Lo logramos eliminando la última capa totalmente conectada (de clasificación) y utilizando la salida de la capa precedente (que suele ser una capa Global Average Pooling) como nuestro vector de características.eliminando la última capa completamente conectada (de clasificación)y utilizando la salida de la capa anterior (normalmente una capa de "Global Average Pooling") como nuestro vector de características.

Implementación: Modelo de Extracción de Características (Python / TensorFlow)

Aquí le mostramos cómo construir un extractor de características utilizando un ResNet-50 pre-entrenado con TensorFlow/Keras. Este modelo recibirá una imagen de 224x224x3 y producirá un vector de características de 2048 dimensiones.

import tensorflow as tf
from tensorflow.keras.applications import resnet50
from tensorflow.keras.models import Model
from tensorflow.keras.layers import GlobalAveragePooling2D, Input
from tensorflow.keras.preprocessing import image
import numpy as np

def get_feature_extractor(model_input_shape=(224, 224, 3)):
    """
    Creates a feature extractor model from a pre-trained ResNet-50.
    """
    # Load ResNet-50 pre-trained on ImageNet
    # `include_top=False` strips the final classification layer
    base_model = resnet50.ResNet50(weights='imagenet', 
                                   include_top=False, 
                                   input_shape=model_input_shape)
    
    # We don't need to re-train the model
    base_model.trainable = False

    # Define the new model
    # Use the base_model's input
    model_input = base_model.input
    
    # Add a Global Average Pooling layer to flatten the features
    # This converts the (7, 7, 2048) output of ResNet's conv_base
    # into a (1, 2048) vector.
    x = base_model.output
    x = GlobalAveragePooling2D()(x)
    
    # Create the new model mapping input image to 2048-dim vector
    feature_extractor_model = Model(inputs=model_input, outputs=x)
    
    return feature_extractor_model

def extract_features(img_path, model):
    """
    Loads an image, preprocesses it, and extracts its feature vector.
    """
    # Load image, ensuring target size matches model input
    img = image.load_img(img_path, target_size=(224, 224))
    
    # Convert image to array
    img_array = image.img_to_array(img)
    
    # Expand dimensions to create a "batch" of 1
    img_batch = np.expand_dims(img_array, axis=0)
    
    # Pre-process the image for ResNet-50
    # (e.g., mean subtraction, channel re-ordering)
    processed_img = resnet50.preprocess_input(img_batch)
    
    # Get the feature vector (prediction)
    features = model.predict(processed_img)
    
    # Normalize the vector (L2 normalization)
    # This is critical for many vector search algorithms
    features_normalized = features / np.linalg.norm(features)
    
    return features_normalized.flatten()

# --- Example Usage ---
#
# 1. Initialize the model (do this once)
# extractor = get_feature_extractor()
#
# 2. Extract features for an image
# vector = extract_features('path/to/my_image.jpg', extractor)
# print(f"Extracted vector of shape: {vector.shape}") 
# Output: Extracted vector of shape: (2048,)

Consideraciones para el puesto de CTO: Este paso de extracción de características es increíblemente paralelo. Es una función sin estado. Esto lo convierte en un candidato perfecto para la escalabilidad horizontal utilizando funciones sin servidor (como AWS Lambda o Google Cloud Run) tanto para el rellenado inicial de imágenes existentes como para el procesamiento de nuevas subidas.

Fase 2: Indexación con una base de datos vectorial

Ahora tiene una función para generar vectores de dimensión 2048. Su tarea es procesar toda su biblioteca (por ejemplo) de 50 millones de imágenes, generando 50 millones de vectores.

El problema: ¿Cómo se pueden buscar?

Una búsqueda por fuerza bruta requiere calcular la distancia $L_2$ entre tu vector de consulta $q$ y todos los 50 millones de vectores en la base de datos. Esto es una operación de tipo $O(N \cdot D)$ —demasiado lenta para una aplicación en tiempo real.

La Solución: Vecino más cercano aproximado (ANN)

Debemos priorizar la precisión perfecta sobre una velocidad inmensa. Utilizamos un algoritmo de Vecino Más Cercano Aproximado (ANN).10 vectores más cercanos, garantiza que con gran probabilidad son los 10 vectores más cercanos, o quizás 9 de ellos más uno que es el undécimo más cercano.

Los algoritmos ANN preprocesan los vectores en una estructura de datos inteligente (un índice) que permite la búsqueda en tiempo sublineal, a menudo $O(\log N)$.

Algoritmo clave: HNSW (Mundos Pequeños Navegables Jerárquicos)

HNSW es el estándar de la industria por defecto. Crea un grafo con múltiples capas. Las capas superiores contienen enlaces "a largo alcance" entre vectores distantes, y las capas inferiores contienen enlaces detallados entre vecinos cercanos. Una búsqueda comienza en la capa superior, "navega" el grafo para llegar al vecindario general correcto, y luego realiza una búsqueda detallada en la capa inferior. Es increíblemente rápido y eficiente en memoria.

Servicios de Ingeniería de Productos

Trabaje con nuestros gestores de proyectos, ingenieros de software y probadores de calidad internos para crear su nuevo producto de software personalizado o para apoyar su flujo de trabajo actual, siguiendo las metodologías Agile, DevOps y Lean.

Build with 4Geeks

Opciones de Tecnología: La base de datos vectorial

Esta es la decisión arquitectónica más importante que puede tomar.

  1. Bibliotecas (Autoadministradas):
    • Faiss (Búsqueda de similitud de Facebook AI): Una biblioteca increíblemente rápida, escrita en C++/Python. No es una "base de datos" sino un conjunto de herramientas para construir un índice, que usted es responsable de administrar, persistir y envolver mediante una API. Ideal para tener el máximo control y rendimiento.
    • ScaNN (Google): Otra biblioteca conocida por su rendimiento de última generación, especialmente con la cuantización.
  2. Bases de datos (Autohospedadas o gestionadas):
    • Milvus: Una base de datos vectorial de código abierto y nativa en la nube. Maneja el particionamiento, la replicación, la persistencia de datos y proporciona una API similar a la de una base de datos. Esta es la opción de código abierto "lista para producción".
    • Weaviate, Pinecone, Qdrant: Proveedores de "base de datos vectorial como servicio", totalmente gestionados. Manejan toda la infraestructura, el escalado y las operaciones de ML (MLOps), permitiendo que su equipo se concentre únicamente en la lógica de la aplicación. Esta es una excelente opción para equipos empresariales que necesitan avanzar rápidamente y externalizar la gestión compleja de la infraestructura.

Implementación: Indexación de Vectores (Python / Faiss)

Aquí hay un ejemplo simplificado de cómo crear y buscar un índice Faiss en memoria. En un sistema real, guardarías este índice en disco.

import faiss
import numpy as np

# Let's assume we have 10,000 vectors, each 2048 dimensions
D = 2048       # Vector dimensionality
N = 10000      # Number of vectors in the index

# 1. Generate some random fake vectors (for demonstration)
# In reality, these would come from your feature extractor
np.random.seed(123) 
vectors_to_index = np.random.random((N, D)).astype('float32')
# L2 normalize them, as we did with the extractor output
faiss.normalize_L2(vectors_to_index)

# 2. Build the Faiss Index (using HNSW)
# We choose an HNSW (Hierarchical Navigable Small Worlds) index
# `faiss.IndexHNSWFlat` stores the full vectors (high accuracy, high RAM)
# M = 32 is the number of neighbors per node in the graph
index = faiss.IndexHNSWFlat(D, 32, faiss.METRIC_L2)

# 3. Add vectors to the index
print("Training and adding vectors to the index...")
# For HNSW, "training" is optional or can be done on the fly
# We just add the vectors directly.
index.add(vectors_to_index)
print(f"Index built. Total vectors: {index.ntotal}")

# --- This index is now ready to be searched ---
# (See Phase 3 for the search part)

# --- Persisting the Index ---
# faiss.write_index(index, "my_image_index.faiss")
# ... later ...
# index = faiss.read_index("my_image_index.faiss")

Fase 3: El pipeline de consulta y recuperación

Con nuestro motor de extracción de características construido (Fase 1) y nuestro índice vectorial completado (Fase 2), ahora podemos implementar la búsqueda en tiempo real.

El proceso es sencillo:

  1. Un usuario sube una imagen de consulta $I_q$.
  2. $I_q$ se pasa a través de la misma función extract_features para generar un vector de consulta $q$.Crucialmente, deben aplicarse las mismas etapas de preprocesamiento y normalización.
  3. El vector $q$ se envía a la Base de Datos Vectorial (o al índice Faiss).
  4. La base de datos realiza una búsqueda ANN para los "k vectores más cercanos" (p. ej., k=20).
  5. La base de datos devuelve una lista de ID de vectores (que corresponden a tus image_id<s10>, y sus distancias.
  6. Tu servidor de aplicaciones busca estos image_id en una base de datos estándar (p. ej., PostgreSQL) para obtener metadatos como la URL de la imagen, el título, etc., y los devuelve al usuario.

Implementación: Punto de acceso de búsqueda (Python / Faiss + Flask)

Este código combina todas las piezas en una API de búsqueda mínima y funcional. Asume que los archivos índice y extractor de los ejemplos anteriores están cargados en memoria.

from flask import Flask, request, jsonify
# Assume previous functions `get_feature_extractor` and 
# `extract_features` are defined here.
# Assume the `index` from Phase 2 is loaded.

app = Flask(__name__)

# --- Load models on startup ---
print("Loading feature extractor model...")
FEATURE_EXTRACTOR = get_feature_extractor()
print("Model loaded.")

# In a real app, load the pre-built Faiss index
# index = faiss.read_index("my_image_index.faiss")
# For this example, we'll use the 'index' from the Phase 2 code.

# Create a mapping from index-position (0, 1, 2...) to your
# actual database image ID (e.g., 'img_abc_123.jpg')
# For this demo, we'll just map 0 -> 'image_0.jpg'
image_id_map = [f"image_{i}.jpg" for i in range(N)]

@app.route("/search/visual", methods=["POST"])
def visual_search():
    if 'file' not in request.files:
        return "No file part", 400
    
    file = request.files['file']
    if file.filename == '':
        return "No selected file", 400

    try:
        # 1. Save file temporarily (in real app, use in-memory stream)
        img_path = "temp_query.jpg"
        file.save(img_path)

        # 2. Phase 1: Extract features from query image
        query_vector = extract_features(img_path, FEATURE_EXTRACTOR)
        
        # Reshape for Faiss search (batch of 1)
        query_vector = np.expand_dims(query_vector, axis=0)

        # 3. Phase 3: Search the index
        K = 10 # We want the top 10 results
        
        # D = distances, I = indices (plural of index)
        distances, indices = index.search(query_vector, K)
        
        # 4. Format and return results
        results = []
        for i, dist in zip(indices[0], distances[0]):
            # Map Faiss index 'i' back to our real image ID
            image_id = image_id_map[i]
            results.append({
                "image_id": image_id,
                "similarity_score": 1.0 - dist # Convert L2 dist to a 0-1 score
            })

        return jsonify(results)

    except Exception as e:
        return str(e), 500

# if __name__ == "__main__":
#    app.run(debug=True)

Principales consideraciones arquitectónicas y de rendimiento

Crear un prototipo es fácil. Crear un sistema de nivel empresarial requiere enfocarse en estos aspectos clave.

1. El triángulo Retención-Latencia-Costo

Este es el principal desafío de las redes neuronales artificiales (ANN).

  • Recuerde:"¿Qué porcentaje de los resultados verdaderos principales (top-K) encontramos?" El 95% es excelente, el 60% es malo.
  • Latencia: ¿Con qué rapidez se realiza la búsqueda? (un objetivo común es que la latencia del 99% sea inferior a 100 ms).
  • Costo: ¿Cuánta memoria RAM y CPU requiere el índice?

En HNSW, estos parámetros están controlados, como efConstruction (calidad en tiempo de construcción) y efSearch (calidad en tiempo de consulta). Los valores más altos aumentan la precisión y la latencia. Para las bases de datos administradas, esto a menudo se expone como un simple "precisión" frente a "velocidad". Su tarea es realizar pruebas comparativas e identificar la configuración más económica que cumpla con los requisitos de precisión y latencia de su producto.

2. Indexación: El problema de "Inicio en frío" frente a "Delta"

Tienes dos cargas de trabajo de indexación:

  • La reindexación (Inicio en frío): Una tarea masiva para procesar todas las imágenes existentes. Esto debe ejecutarse en un entorno de computación escalable y predecible (p. ej., Spark, AWS Batch).
  • El Delta (Indexación en tiempo real): Un flujo de datos que procesa nuevas subidas de imágenes de forma individual (p. ej., a través de Kafka, SQS). Esto debe ser rápido para garantizar que el contenido nuevo esté inmediatamente disponible.

Una arquitectura común utiliza una "arquitectura lambda" (que no debe confundirse con AWS Lambda), donde se tiene un índice principal grande y estático, optimizado, página principaly un índice delta pequeño, dinámico y menos optimizadoíndice delta. Se envían consultas a ambos índices, y los resultados se combinan. El índice delta se combina periódicamente con el índice principal.

Servicios de Ingeniería de Productos

Trabaje con nuestros gestores de proyectos, ingenieros de software y probadores QA para desarrollar su nuevo producto de software personalizado o para apoyar su flujo de trabajo actual, siguiendo metodologías Agile, DevOps y Lean.

Build with 4Geeks

3. Dimensionalidad vectorial frente al rendimiento

Un vector de 2048 dimensiones (proveniente de ResNet) es altamente descriptivo pero "pesado". Un vector de 512 dimensiones (proveniente de un modelo MobileNet más pequeño o una versión modificada) es "más ligero".

  • Vectores de alta dimensión: Mayor precisión, más memoria RAM, búsqueda más lenta.
  • Vectores de baja dimensión: Menor precisión, menos memoria RAM, búsqueda más rápida.

Si sus vectores de 2048 dimensiones son demasiado exigentes en cuanto a memoria, no simplemente los recorte. Utilice una técnica de reducción de dimensionalidad como PCA (Análisis de Componentes Principales). Puede entrenar un modelo PCA con una muestra de 1 millón de vectores para aprender una proyección de 2048 dimensiones a 256 dimensiones. Este vector de 256 dimensiones conservará mucha más información que simplemente tomar los primeros 256 valores. Esto es un paso común de preprocesamiento antes de la indexación.

Conclusión

Implementar un sistema de recuperación de imágenes basado en contenido es una tarea de ingeniería concreta que permite a una organización pasar de la búsqueda basada en texto a un paradigma moderno, centrado en el contenido. Al separar la arquitectura en tres fases distintas—Extracción, Indexación y Recuperación—cualquier equipo de ingeniería de alto rendimiento puede desarrollar esta capacidad.

Las decisiones principales no son si usar una CNN o una base de datos vectorial, sino qué modelo pre-entrenado utilizar (ResNet-50 es una opción segura), qué tecnología de base de datos vectorial (Faiss, Milvus o un servicio gestionado) se adapta a la madurez operativa de su equipo, y cómo ajustará el equilibrio crucial entre la precisión en la búsqueda y la latencia.

Este sistema es un elemento fundamental para una amplia gama de productos, y dominar su implementación es un factor diferenciador clave para cualquier empresa que invierta en servicios de IA aplicada.

Preguntas frecuentes

¿Qué es la recuperación de imágenes basada en contenido (CBIR)?

La recuperación de imágenes basada en contenido, o CBIR, es una tecnología de búsqueda que encuentra imágenes basándose en su contenido visual real, en lugar de depender de palabras clave, nombres de archivo o etiquetas manuales. En lugar de buscar texto sobre una imagen, permite a un usuario proporcionar una imagen de consulta y encontrar otras imágenes que "se parezcan" a ella. Este sistema analiza características visuales como colores, formas y texturas para determinar la similitud.

¿Cómo funciona un sistema de CBIR?

Un sistema de CBIR generalmente funciona en una serie de fases. Primero, en la fase de Extracción de Características, un modelo de aprendizaje profundo (como una CNN) analiza una imagen y convierte su contenido visual en un "huella" numérica única llamada vector de características. Segundo, en la fase de Indexación, estos vectores se almacenan en una base de datos vectorial especializada que utiliza algoritmos como HNSW (Hierarchical Navigable Small Worlds) para búsquedas eficientes. Finalmente, en la fase de Consulta y Recuperación, la imagen de consulta del usuario se pasa a través del mismo extractor de características, y su vector se utiliza para buscar en la base de datos los vectores más cercanos y visualmente similares, devolviendo las imágenes correspondientes.

¿Por qué es importante la recuperación de imágenes basada en contenido?

CBIR (Recuperación de Imágenes Basada en Contenido) resuelve las principales limitaciones de la búsqueda tradicional basada en texto, que es no escalable, subjetiva y falla por completo si falta o es incorrecta la información. A medida que aumenta el volumen de datos visuales sin estructura (como fotos de productos o contenido generado por usuarios), CBIR proporciona una forma escalable y precisa para organizar, buscar y gestionar estos activos. Permite funciones críticas como la búsqueda visual, la moderación automática del contenido, la detección de imágenes duplicadas y los motores de recomendación visual.

Read more