<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Navneet Kumar - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Navneet Kumar - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/es/search-labs/author/navneet-kumar</link>
    </image>
    <link>https://www.elastic.co/es/search-labs/author/navneet-kumar</link>
    <atom:link href="https://www.elastic.co/es/search-labs/rss/author/navneet-kumar.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[es]]></language>
    <lastBuildDate>Tue, 22 Sep 2026 17:53:22 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Búsqueda multimodal de picos montañosos con Elasticsearch y SigLIP-2 ]]></title>
    <description><![CDATA[Aprende a implementar búsqueda multimodal texto a imagen e imagen a imagen usando incrustaciones SigLIP-2 y búsqueda vectorial kNN en Elasticsearch. Enfoque del proyecto: encontrar fotos del pico del Monte Ama Dablam durante una travesía por el Everest.]]></description>
    <content:encoded><![CDATA[<p>¿Alguna vez quisiste buscar en tu álbum de fotos por significado? Prueba con preguntas como "muéstrame mis fotos donde llevo una chaqueta azul y estoy sentado en un banco", "muéstrame fotos del Monte Everest" o "sake y sushi". Toma una taza de café (o tu bebida favorita) y sigue leyendo. En este blog, te mostramos cómo construir una aplicación de búsqueda híbrida multimodal. Multimodal significa que la app puede entender y buscar entre diferentes tipos de entradas—texto, imágenes y audio—no solo palabras. Híbrido significa que combina técnicas como la coincidencia de palabras clave, la búsqueda vectorial kNN y el geofencing para ofrecer resultados más precisos.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa1ec1ccd450e94/6a17da751d1b8308ee93e344/0ec6bbb45013846b59ee00d2bf73ee2182ee7392-1920x1080.gif" alt="Recopilatorio de diferentes fotos de las cumbres de montaña de la ruta al Everest." /><p>Para lograrlo, empleamos SigLIP-2 de Google para generar incrustaciones vectoriales tanto para imágenes como para texto, y las almacenamos en la base de datos vectorial Elasticsearch. En el momento de la consulta, convertimos la entrada de búsqueda, texto o imagen, en incrustaciones y realizamos búsquedas rápidas con vectores kNN para obtener resultados. Esta configuración permite una búsqueda eficiente de texto a imagen y de imagen a imagen. Una interfaz Streamlit da vida a este proyecto proporcionándonos una interfaz no solo para hacer búsquedas por texto para encontrar y ver las fotos coincidentes del álbum, sino también para identificar la cima de la montaña a partir de la imagen subida y ver otras fotos de esa montaña en el álbum.
También cubrimos los pasos que seguimos para mejorar la precisión de las búsquedas, junto con consejos y trucos prácticos. Para una exploración más profunda, proporcionamos un <a href="https://github.com/navneet83/multimodal-mountain-peak-search">repositorio de GitHub</a> y un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">cuaderno de Colab</a>.</p><h2>Cómo empezó todo</h2><p>Esta entrada del blog fue inspirada por un niño de 10 años que me pidió que les mostrara todas las fotos del Monte Ama Dablam de mi travesía al campamento base del Everest. Mientras revisábamos el álbum de fotos, también me pidieron que identificara varias otras cumbres montañosas, algunas de las cuales no podía nombrar.</p><p>Eso me dio la idea de que esto puede ser un proyecto divertido de visión por computadora. Lo que queríamos conseguir:</p><ul><li><p>Encuentra fotos de un pico montañoso por nombre</p></li><li><p>Adivina el nombre de la cima de la montaña a partir de una imagen y también encuentra picos similares en el álbum de fotos</p></li><li><p>Haz que las consultas conceptuales funcionen (<em>persona</em>, <em>río</em>, <em>banderas de oración</em>, <em>etc.)</em></p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf82df9d7005fc3fe/6a17da78abe0f2e77bdfe8b9/e9d0d720a9b565d5b749bdc915068852d4f157ad-1200x1600.png" alt="Monte Ama Dablam " /><h2>Formando el equipo soñado: SigLIP-2, Elasticsearch y Streamlit</h2><p>Pronto quedó claro que, para que esto funcionara, tendríamos que convertir tanto el texto ("Ama Dablam") como las imágenes (fotos de mi álbum) en vectores que puedan comparar de forma significativa, es decir, en el mismo espacio vectorial. Una vez que hacemos eso, la búsqueda es simplemente "encontrar a los vecinos más cercanos".</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f80b69a9d5bd28a/6a17da7a4b055ddd1243209e/20e6f8b7d4fa48414f407ec200adbe00ee28d517-1536x1024.png" alt="SigLIP-2, Elasticsearch y Streamlit: equipo de ensueño." /><p>Para generar incrustaciones de imágenes, usamos un<a href="https://huggingface.co/blog/vlms-2025"> codificador multilingüe visión-lenguaje</a>, de modo que una foto de una montaña y una frase como "Ama Dablam" caen en el mismo espacio vectorial.</p><p><a href="https://huggingface.co/blog/siglip2"><strong>SigLIP-2</strong></a>, lanzado recientemente por Google, encaja bien aquí. Puede generar incrustaciones sin entrenamiento específico de tarea (un ajuste <strong>de cero disparos</strong> ) y funciona bien para nuestro caso: fotos sin etiqueta y picos con diferentes nombres e idiomas. Como está capacitado para la coincidencia de imágenes de texto ↔, una foto de montaña de la travesía y un breve prompt de texto acaban siendo similares a incrustaciones, incluso cuando el idioma de consulta o la ortografía varían.</p><p>SigLIP-2 ofrece un fuerte equilibrio calidad-velocidad, soporta múltiples resoluciones de entrada y funciona tanto en CPU como en GPU. El SigLIP-2 está diseñado para ser más robusto para fotos exteriores en comparación con modelos anteriores como el CLIP original. Durante nuestras pruebas, SigLIP-2 generó resultados fiables de forma constante. Además, está muy bien apoyado, lo que lo convierte en la opción obvia para este proyecto.</p><p>A continuación, necesitamos una base de datos vectorial para almacenar los embebidos y la búsqueda de potencia. Debe soportar no solo búsqueda kNN coseno sobre incrustaciones de imágenes, sino también aplicar filtros de geocerca y texto en una sola consulta. Elasticsearch encaja bien aquí: maneja vectores (HNSW kNN en campos dense_vector), soporta búsqueda híbrida que combina texto, vectores y consultas geográficas, y ofrece filtrado y ordenación desde el principio. Además, escala horizontalmente, lo que facilita crecer de unas pocas fotos a miles. El cliente oficial <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/python">de Python de Elasticsearch</a> mantiene la fontanería sencilla y se integra perfectamente con el proyecto. Por último, necesitamos un frontend ligero donde podamos introducir consultas de búsqueda y ver resultados. Para una demostración rápida basada en Python, Streamlit es una opción ideal. Proporciona las primitivas que necesitamos: carga de archivos, una cuadrícula de imágenes responsiva y menús desplegables para ordenar y geovaller. Es fácil de clonar y ejecutar localmente, y también funciona en un cuaderno de Colab.</p><h2>Implementación</h2><h3>Diseño y estrategia de indexación de Elasticsearch</h3><p>Emplearemos dos índices para este proyecto: <code>peaks_catalog</code> y <code>photos</code>.</p><h4>Peaks_catalog índice</h4><p>Este índice sirve como un catálogo compacto de picos montañosos prominentes visibles durante la travesía al Campamento Base del Everest. Cada documento de este índice corresponde a una sola cima montañosa, como el Monte Everest. Para cada documento de pico de montaña, almacenamos nombres/alias, coordenadas opcionales de latitud-longitud y un único vector prototipo construido mediante la mezcla de prompts de texto SigLIP-2 (+ imágenes de referencia opcionales).</p><p><strong>Mapeo indexado:</strong></p><p>Campo</p><p>Tipo</p><p>Ejemplo</p><p>Propósito/Notas</p><p>Vector/Indexación</p><p>identificación</p><p>palabra clave</p><p>ama-dablam</p><p>Slug/id estable</p><p>—</p><p>Nombres</p><p>Subcampo texto + palabra clave</p><p>["Ama Dablam","Amadablam"]</p><p>Alias / nombres multilingües; names.raw para filtros exactos</p><p>—</p><p>Latlon</p><p>geo_point</p><p>{"lat":27.8617,"lon":86.8614}</p><p>Coordenadas GPS de pico como combinación de latitud/longitud (opcional)</p><p>—</p><p>elev_m</p><p>entero</p><p>6812</p><p>Elevación (opcional)</p><p>—</p><p>text_embed</p><p>dense_vector</p><p>768</p><p>Prototipo mezclado (prompts y, opcionalmente, 1–3 imágenes de referencia) para este pico</p><p>Index:True, Similitud:"Coseno", index_options:{type:"hnsw", m:16, ef_construction:128}</p><p>Este índice se emplea principalmente para búsquedas imagen a imagen, como identificar picos montañosos a partir de imágenes. También empleamos este índice para mejorar los resultados de búsqueda de texto a imagen.</p><p>En resumen, el <code>peaks_catalog</code> transforma la pregunta "¿Qué montaña es esta?" en un problema enfocado del vecino más cercano, separando efectivamente la comprensión conceptual de las complejidades de los datos de imagen.</p><p><strong>Estrategia de indexación para el índice peaks_catalog: </strong>Comenzamos creando una lista de los picos más destacados visibles durante la travesía por el EBC. Para cada pico, almacenamos su ubicación geográfica, nombre, sinónimos y elevación en un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/data/peaks.yaml">archivo yaml</a>. El siguiente paso es <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L351">generar la incrustación</a> de cada pico y almacenarla en <code>text_embed</code> campo. Para generar incrustaciones robustas, empleamos la siguiente técnica:</p><ul><li><p>Crea un prototipo de texto usando:</p><ul><li><p>Nombres de los picos</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L301">Conjunto de prompts</a> (usando varios prompts diferentes para intentar responder a la misma pregunta), por ejemplo:</p><ul><li><p>"una foto natural de la cima de la montaña {name} en el Himalaya, Nepal"</p></li><li><p>"{name} pico emblemático en la región del Khumbu, paisaje alpino"</p></li><li><p>"{name} cima de montaña, nieve, cresta rocosa"</p></li></ul></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L333">anticoncepto</a> opcional (indicar a SigLIP-2 en qué no debe coincidir): resta un pequeño vector para "pintura, ilustración, afiche, mapa, logo" para inclinarnos hacia fotos reales.</p></li></ul></li><li><p>Opcionalmente <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L388C13-L388C29">, crea un prototipo de imagen</a> si se proporcionan imágenes de referencia del pico.</p></li></ul><p>Luego <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L392">mezclamos el prototipo de texto e imagen</a> para generar la incrustación final. Finalmente, el documento está <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L396">indexado</a> con todos los campos requeridos:</p>def l2norm(v: np.ndarray) -&gt; np.ndarray:
    return v / (np.linalg.norm(v) + 1e-12)
def compute_blended_peak_vec(
        emb: Siglip2,
        names: List[str],
        peak_id: str,
        peaks_images_root: str,
        alpha_text: float = 0.5,
        max_images: int = 3,
) -&gt; Tuple[np.ndarray, int, int, List[str]]:
    """
    Build blended vector for a single peak.

    Returns:
      vec           : np.ndarray (L2-normalized)
      found_count   : number of reference images discovered
      used_count    : number of references used (&lt;= max_images)
      used_filenames: list of filenames used (for logging)
    """
    # 1) TEXT vector
    tv = embed_text_blend(emb, names)

    # 2) IMAGE refs: prefer folder by id; fallback to slug of the primary name
    root = Path(peaks_images_root)
    candidates = [root / peak_id]
    if names:
        candidates.append(root / slugify(names[0]))

    all_refs: List[Path] = []
    for c in candidates:
        if c.exists() and c.is_dir():
            all_refs = list_ref_images(c)
            if all_refs:
                break

    found = len(all_refs)
    used_list = all_refs[:max_images] if (max_images and found &gt; max_images) else all_refs
    used = len(used_list)

    img_v = embed_image_mean(emb, used_list) if used_list else None

    # 3) Blend TEXT and IMAGE vectors, clamp alpha to [0,1]
    a = max(0.0, min(1.0, float(alpha_text)))
    vec = l2norm(tv if img_v is None else (a * tv + (1.0 - a) * img_v)).astype("float32")
    return vec, found, used, [p.name for p in used_list]<p>Documento de ejemplo de <code>peaks_catalog</code> índice:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1219f5d0e39b512c/6a17da7c57726263161bcace/bc05fbd0c4f8d721d5170c28a3884a9eda80bb7d-1210x1132.png" alt="Un documento de ejemplo del índice de peaks_catalog en Elasticsearch." /><h4>Índice de fotos</h4><p>Este índice principal almacena información detallada sobre todas las fotos del álbum. Cada documento representa una sola foto, que contiene la siguiente información:</p><ul><li><p>Camino relativo a la foto del álbum. Esto puede usar para ver la imagen correspondiente o cargarla en la interfaz de búsqueda.</p></li><li><p>GPS e información horaria de la imagen.</p></li><li><p>Vector denso para codificación de imágenes generado por SigLIP-2.</p></li><li><p><code>predicted_peaks</code> Eso nos permite filtrar por nombre de pico.

<strong>Mapeo de índices</strong></p></li></ul><p>Campo</p><p>Tipo</p><p>Ejemplo</p><p>Propósito/Notas</p><p>Vector / Indexación</p><p>camino</p><p>palabra clave</p><p>datos/imágenes/IMG_1234.HEIC</p><p>Cómo se abre la interfaz en miniatura/imagen completa</p><p>—</p><p>clip_image</p><p>dense_vector</p><p>768</p><p>Incrustación de imágenes SigLIP-2</p><p>Index:True, Similitud:"Coseno", index_options:{type:"hnsw", m:16, ef_construction:128}</p><p>predicted_peaks</p><p>palabra clave</p><p>["ama-dablam", "pumori"]</p><p>Top-K suposiciones en el tiempo del índice (filtro UX barato / facet)</p><p>—</p><p>GPS</p><p>geo_point</p><p>{"lat":27.96,"lon":86.83}</p><p>Activa los filtros geográficos</p><p>—</p><p>shot_time</p><p>date</p><p>2023-10-18T09:41:00Z</p><p>Tiempo de captura: ordenar/filtrar</p><p>—</p><p><strong>Estrategia de indexación para el índice de fotos: </strong>Para cada foto del álbum, hacemos lo siguiente:
Extrae <code>shot_time</code> de imagen y <code>gps</code> información <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L526">de los metadatos de las imágenes</a>.</p><ul><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L511">Embedding de imagen SigLIP-2</a>: pasar la imagen por el modelo y normalizar el vector en modo L2. Almacena el embedding en <code>clip_image</code> campo.</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L519">Predecir los picos</a> y almacenarlos en el campo <code>predicted_peaks</code> . Para ello, primero tomamos el vector de imagen de la foto generado en el paso anterior y luego ejecutamos una búsqueda rápida kNN en el campo text_embed en el índice de <code>peaks_catalog</code> . Mantenemos los 3-4 primeros picos e ignoramos el resto.</p></li><li><p>Calculamos el campo <code>_id</code> haciendo un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L509">hash</a> en el nombre y el camino de la imagen. Esto cerciora que no acabemos con duplicados tras varias partidas.</p></li></ul><p>Una vez que determinamos todos los campos para la foto, los documentos fotográficos se indexan en lotes usando <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L530">indexación masiva</a> :</p>def bulk_index_photos(
        es: Elasticsearch,
        images_root: str,
        photos_index: str = "photos",
        peaks_index: str = "peaks_catalog",
        topk_predicted: int = 5,
        batch_size: int = 200,
        refresh: str = "false",
) -&gt; None:
    """Walk a folder of images, embed + enrich, and bulk index to Elasticsearch."""
    root = Path(images_root)
    if not root.exists():
        raise SystemExit(f"Images root not found: {images_root}")

    emb = Siglip2()
    batch: List[Dict[str, Any]] = []
    n_indexed = 0

    for p in iter_images(root):
        rel = relpath_within(root, p)
        _id = id_for_path(rel)

        # 1) Image embedding (and reuse it for predicted_peaks)
        try:
            with Image.open(p) as im:
                ivec = emb.image_vec(im.convert("RGB")).astype("float32")
        except (UnidentifiedImageError, OSError) as e:
            print(f"[skip] {rel} — cannot embed: {e}")
            continue

        # 2) Predict top-k peak names
        try:
            top_names = predict_peaks(es, ivec.tolist(), peaks_index=peaks_index, k=topk_predicted)
        except Exception as e:
            print(f"[warn] predict_peaks failed for {rel}: {e}")
            top_names = []

        # 3) EXIF enrichment (safe)
        gps = get_gps_decimal(str(p))
        shot = get_shot_time(str(p))

        # 4) Build doc and stage for bulk
        doc = {"path": rel, "clip_image": ivec.tolist(), "predicted_peaks": top_names}
        if gps:
            doc["gps"] = gps
        if shot:
            doc["shot_time"] = shot

        batch.append(
            {"_op_type": "index", "_index": photos_index, "_id": _id, "_source": doc}
        )

        # 5) Periodic flush
        if len(batch) &gt;= batch_size:
            helpers.bulk(es, batch, refresh=refresh)
            n_indexed += len(batch)
            print(f"[photos] indexed {n_indexed} (last: {rel})")
            batch.clear()

    # Final flush
    if batch:
        helpers.bulk(es, batch, refresh=refresh)
        n_indexed += len(batch)
        print(f"[photos] indexed {n_indexed} total.")

    print("[done] photos indexing")<p>Documento de ejemplo del índice de fotos:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt744b7e6326937cfc/6a17da7e6df731d3040a0da8/1dc1406ac2a97440b6804838795b3c2205c4c6b2-1080x1234.png" alt="Un documento de ejemplo del índice de fotos en Elasticsearch." /><p>En resumen, el índice de las fotos es el almacén rápido, filtrable y listo para kNN de todas las fotos del álbum. Su mapeo es mínimo a propósito: la estructura justa para recuperar rápidamente, mostrar limpiamente y recortar los resultados por espacio y tiempo. Este índice sirve tanto para casos de búsqueda como para el uso. Aquí se <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/create_indices.py">puede encontrar un</a> script en Python para crear ambos índices.</p><p>La visualización de mapas de Kibana que aparece a continuación muestra documentos del álbum de fotos como puntos verdes y picos montañosos del índice de <code>peaks_catalog</code> como triángulos rojos, con los puntos verdes alinear bien con el sendero de la ruta del campamento base del Everest.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5bf016e8d9c3e84/6a17da80be608681f10045e6/1c75d0ed0ce53d28a94bf2f47a354e25581d2baf-1600x1402.png" alt="Una visualización de Kibana muestra documentos del álbum de fotos como puntos verdes y las cumbres de montaña del índice de peaks_catalog como triángulos rojos, con los puntos verdes alinear bien con el sendero de la ruta de trekking al Campamento Base del Everest." /><h2>Casos de uso de búsqueda</h2><p><strong>Buscar por nombre (texto a imagen):</strong> Esta función permite a los usuarios localizar fotos de picos montañosos (e incluso conceptos abstractos como "banderas de oración") mediante consultas de texto. Para lograrlo, la entrada de texto <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L87C5-L87C20">se convierte en un vector de texto</a> usando SigLIP-2. Para una generación robusta de vectores de texto, empleamos la misma estrategia que se usa para crear incrustaciones de texto en el índice <code>peaks_catalog</code> : <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104">combinar</a> la entrada de texto con un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L100">pequeño conjunto de prompts</a>, restar un<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L103"> pequeño vector anti-concepto</a> y aplicar la <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104">normalización L2</a> para producir el vector de consulta final. A continuación, se ejecuta una <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L140">consulta</a> kNN en el campo <code>photos.clip_image</code> para recuperar los picos que coinciden con la parte superior, basar en la similitud coseno para encontrar las imágenes más cercanas. Opcionalmente, los resultados de búsqueda pueden ser más relevantes aplicando <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L152">filtros</a> geográficos y de fecha, y/o un filtro de <code>photos.predicted_peaks</code> términos como parte de la consulta (ver ejemplos de consultas más abajo). Esto ayuda a excluir picos que se parecen y que en realidad no se ven durante la travesía.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9bb9abf5ce64fcbb/6a17da81e8fbce20db3a17da/b5fac28ffdbedb820505365ca07df125cd01b939-946x370.png" alt="Qué tan multimodal funciona, la búsqueda por nombre (texto a imagen) en Elasticsearch." /><p><strong>Consulta de Elasticsearch con filtro geográfico:</strong></p>POST photos/_search
{
  "knn": {
    "field": "clip_image",
    "query_vector": [ ... ],
    "k": 60,
    "num_candidates": 2000
  },
  "query": {
    "bool": {
      "filter": [
        { "geo_bounding_box": { "gps": { "top_left": "...", "bottom_right": "..." } } }
      ]
    }
  },
  "_source": ["path","predicted_peaks","gps","shot_time"]
}

Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}<p><strong>Buscar por imagen (imagen a imagen):</strong> Esta función nos permite identificar una montaña en una imagen y encontrar otras imágenes de esa misma montaña dentro del álbum. Cuando se sube una imagen, el codificador de imagen SigLIP-2 la procesa para generar un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L228">vector de imagen</a>. A continuación, se realiza una <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L234">búsqueda kNN</a> en el campo <code>peaks_catalog.text_embed</code> para identificar los nombres de picos que mejor coinciden. Posteriormente, <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L257">se genera un vector de texto</a> a partir de estos nombres de picos coincidentes, y se realiza otra <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L263">búsqueda kNN</a> en el índice de fotos para localizar las imágenes correspondientes.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab9d16333e2a9e69/6a17da827f6f155448c099cc/3a3d5635bee7a222b95529dd7f9fbee016381610-1226x550.png" alt="Cómo funciona la búsqueda multimodal por imagen (imagen a imagen) en Elasticsearch." /><p><strong>Consulta Elasticsearch:</strong></p><p>Paso 1: Encontrar los nombres de picos que coincidan</p>GET peaks_catalog/_search
{
 "knn": {
   "field": "text_embed",
   "query_vector": [...image-vector... ],
   "k": 3,
   "num_candidates": 500
 },
 "_source": [
   "id",
   "names",
   "latlon",
   "text_embed"
 ]
}


Response (first two documents):
{
 "took": 2,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 3,
     "relation": "eq"
   },
   "max_score": 0.58039916,
   "hits": [
     {
       "_index": "peaks_catalog",
       "_id": "pumori",
       "_score": 0.58039916,
       "_source": {
         "id": "pumori",
         "names": [
           "Pumori",
           "Pumo Ri"
         ],
         "latlon": {
           "lat": 28.01472,
           "lon": 86.82806
         },
         "text_embed": [
                  ... embeddings...
         ]
       }
     },
     {
       "_index": "peaks_catalog",
       "_id": "kyajo-ri",
       "_score": 0.57942784,
       "_source": {
         "id": "kyajo-ri",
         "names": [
           "Kyajo Ri",
           "Kyazo Ri"
         ],
         "latlon": {
           "lat": 27.909167,
           "lon": 86.673611
         },
         "text_embed": [
           ... embeddings...
         ]
       }
     }
   ]
 }
}<p>Paso 2: Realiza una búsqueda en el índice de <code>photos</code> para encontrar las imágenes coincidentes (misma consulta que se muestra en el caso de búsqueda text-to-image):</p>POST photos/_search
{
 "knn": {
   "field": "clip_image",
   "query_vector": [ ...image-vector... ],
   "k": 30,
   "num_candidates": 2000
 },
 "_source": [
   "path",
   "gps",
   "shot_time",
   "predicted_peaks",
   "clip_image"
 ],
 "query": {
   "bool": {
     "filter": [
       {
         "term": {
           "predicted_peaks": "Pumori"
         }
       }
     ]
   }
 }
}


Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}<h2>Interfaz Streamlit</h2><p>Para unir todo, creamos una interfaz sencilla de Streamlit que nos permite realizar ambos casos de uso de búsqueda. El riel izquierdo muestra una lista desplazable de picos (agregados a partir de <code>photos.predicted_peaks</code>) con casillas de verificación y un minimapa/filtro geográfico. En la parte superior hay una caja <strong>de búsqueda por nombre</strong> y un botón <strong>de identificación por subida de fotos</strong> . El panel central presenta una cuadrícula en miniatura sensible que muestra puntajes kNN, insignias de picos predichos y tiempos de captura. Cada imagen incluye un botón <strong>para ver imagen</strong> para vistas previas en resolución completa.</p><p><strong>Busca subiendo una imagen:</strong> Predecimos el pico y encontramos picos coincidentes del álbum de fotos.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1fb2b0304a310d2/6a17da8425daab7cda08a0fa/dca540cbf5279e6d6102c5a0c0351ddd4ac91cda-1600x1112.png" alt="Una interfaz sencilla de iluminación streamlit que permite buscar tanto texto a imagen como imagen a imagen multimodal los picos del Monte Ama Dablam." /><p><strong>Buscar por texto</strong>: Encuentra los picos coincidentes en el álbum a partir del texto</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt496c1ae8f7886320/6a17da86abe0f2da48dfe8bd/b1e8618db746cd49ea4962d3dc73031387b975dd-1600x1166.png" alt="Cómo buscar un pico del Monte Everest buscando por texto en la biblioteca de picos de montaña." /><h2>Conclusión</h2><p>¿Qué empezó como <em>, ¿podemos simplemente ver las </em>imágenes<em><strong>de Ama Dablam</strong></em><em>?</em> se convirtió en un pequeño sistema de <strong>búsqueda multimodal</strong> funcional. Tomamos fotos en bruto de trekking, las convertimos en <strong>incrustaciones SigLIP-2</strong> y usamos <strong>Elasticsearch</strong> para hacer <strong>kNN</strong> rápido sobre vectores, además de filtros geo/temporales simples para mostrar las imágenes <em>correctas por significado</em>. En el camino, separamos las preocupaciones con dos índices: un pequeño <code>peaks_catalog</code> de prototipos combinados (para identificación) y un índice escalable de <code>photos</code> de vectores de imagen y EXIF (para recuperación). Es práctica, reproducible y fácil de ampliar.</p><p>Si quieres afinarla, hay algunos ajustes con los que puedes experimentar:</p><ul><li><p><strong>Ajustes de tiempo de consulta:</strong> <code>k</code> (cuántos vecinos quieres que devuelvan) y <code>num_candidates</code> (qué ancho buscar antes del puntaje final). Estos ajustes se discuten en el <a href="https://www.elastic.co/search-labs/blog/elasticsearch-knn-and-num-candidates-strategies">blog aquí</a>.</p></li><li><p><strong>Ajustes de tiempo de índice:</strong> <code>m</code> (conectividad de grafos) y <code>ef_construction</code> (precisión en tiempo de construcción frente a memoria). Para consultas, experimenta también con <code>ef_search</code> : más alto suele significar mejor recordación con cierto compromiso de latencia. Consulta <a href="https://www.elastic.co/search-labs/blog/hnsw-graph">este blog</a> para más detalles sobre estos entornos.</p></li></ul><p>De cara al futuro, los modelos nativos/reclasificadores para búsqueda <strong>multimodal</strong> y <strong>multilingüe</strong> pronto llegarán al ecosistema Elastic, lo que debería mejorar aún más la recuperación de imágenes/texto y el ranking híbrido desde el primer momento.<a href="https://ir.elastic.co/news/news-details/2025/Elastic-Completes-Acquisition-of-Jina-AI-a-Leader-in-Frontier-Models-for-Multimodal-and-Multilingual-Search/default.aspx?utm_source=chatgpt.com"> ir.elastic.co+1</a></p><p>Si quieres probar esto tú mismo:</p><ul><li><p><strong>Repositorio de GitHub:</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search"><em>https://github.com/navneet83/multimodal-mountain-peak-search</em></a></p></li><li><p><strong>Inicio rápido de Colab:</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb</a></p></li></ul><p>Con esto, nuestro viaje llegó a su fin y es hora de volar de regreso. Espero que esto te fue útil y si lo rompes (o lo mejoras), me encantaría saber qué cambiaste.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdce2fff1569d2a8b/6a17da894b055dd1f24320a2/d324d1e1472f1bfbd8f25747f57bdeeb9c7f16b2-1600x1200.png" alt="" />]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/multimodal-search-siglip-2-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/multimodal-search-siglip-2-elasticsearch</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <category><![CDATA[Búsqueda híbrida]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Navneet Kumar]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltccb66279debb05f9/6a17da8b63baffe228741b15/ffcf93358a7c5dadcea82faf3de460bf060d003c-1600x1200.png" length="0" type="image/png"/>
    <pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>