블로그

Elasticsearch와 SigLIP-2로 산봉우리에 대한 멀티모달 검색

SigLIP-2 임베딩과 Elasticsearch kNN 벡터 검색을 사용해 텍스트 대 이미지 및 이미지 대 이미지 다중 모드 검색을 구현하는 방법을 알아보세요. 프로젝트 초점: 에베레스트 트레킹에서 아마다블람 산 정상 사진 찾기.

사진 앨범을 의미별로 검색하고 싶었던 적이 있나요? "파란색 재킷을 입고 벤치에 앉아 있는 내 사진 보여줘", "에베레스트산 사진 보여줘", "사케와 초밥" 등의 검색어를 사용해 보세요. 커피 한 잔(또는 좋아하는 음료)을 들고 계속 읽으세요. 이 블로그에서는 멀티모달 하이브리드 검색 애플리케이션을 구축하는 방법을 설명합니다. 멀티모달이란 앱이 단어뿐만 아니라 텍스트, 이미지, 오디오 등 다양한 종류의 입력을 이해하고 검색할 수 있다는 뜻입니다. 하이브리드란 키워드 매칭, kNN 벡터 검색, 지오펜싱과 같은 기술을 결합하여 더 선명한 결과를 제공하는 것을 의미합니다.

에베레스트 산 등반에서 찍은 다양한 산봉우리 사진 라이브러리.

이를 위해 Google의 SigLIP-2를 사용해 이미지와 텍스트 모두에 대한 벡터 임베딩을 생성하고 이를 Elasticsearch 벡터 데이터베이스에 저장합니다. 쿼리 시 텍스트 또는 이미지와 같은 검색 입력을 임베딩으로 변환하고 빠른 kNN 벡터 검색을 실행하여 결과를 검색합니다. 이 설정을 통해 텍스트 대 이미지 및 이미지 대 이미지 검색을 효율적으로 수행할 수 있습니다. 스트림릿 UI는 텍스트 기반 검색을 통해 앨범에서 일치하는 사진을 찾아서 볼 수 있을 뿐만 아니라 업로드된 이미지에서 산봉우리를 식별하고 사진 앨범에서 해당 산의 다른 사진을 볼 수 있는 프론트엔드를 제공함으로써 이 프로젝트에 활기를 불어넣었습니다. 또한 검색 정확도를 개선하기 위해 취한 조치와 실용적인 팁과 요령에 대해서도 설명합니다. 더 자세히 살펴볼 수 있도록 GitHub 리포지토리와 Colab 노트북을 제공합니다.

시작 방법

이 블로그 게시물은 에베레스트 베이스캠프 트레킹에서 찍은 아마 다블람 산의 모든 사진을 보여 달라는 10살짜리 아이의 요청에 영감을 받아 작성했습니다. 사진첩을 훑어보면서 이름을 알 수 없는 다른 산봉우리도 몇 개 더 찾아달라는 요청을 받았습니다.

이를 통해 재미있는 컴퓨터 비전 프로젝트가 될 수 있겠다는 생각이 들었습니다. 우리가 달성하고자 했던 목표:

  • 이름으로 산봉우리 사진 찾기

  • 이미지에서 산봉우리 이름을 맞추고 사진 앨범에서 비슷한 봉우리를 찾습니다.

  • 컨셉 쿼리가 작동하도록 하기(사람, , 기도 깃발 등)

아마 다블람 산

아마 다블람 산

드림팀 구성: SigLIP-2, Elasticsearch & Streamlit

이 작업을 수행하려면 텍스트('Ama Dablam')와 이미지(내 앨범의 사진)를 모두 의미 있게 비교할 수 있는 벡터, 즉 동일한 벡터 공간으로 변환해야 한다는 것이 금방 분명해졌습니다. 이렇게 하면 검색은 "가장 가까운 이웃을 찾는 것"에 불과합니다.

SigLIP-2, Elasticsearch & Streamlit- 드림팀.

이미지 임베딩을 생성하기 위해 다국어 비전 언어 인코더를 사용하므로 산 사진과 "Ama Dablam"과 같은 문구가 동일한 벡터 공간에 배치됩니다.

최근 Google에서 출시한 SigLIP-2가 여기에 잘 맞습니다. 작업별 교육( 제로 샷 설정) 없이 임베딩을 생성할 수 있으며, 라벨이 없는 사진과 이름과 언어가 다른 봉우리라는 사용 사례에 적합하게 작동합니다. 텍스트 ↔ 이미지 매칭을 위해 학습되었기 때문에 쿼리 언어나 철자가 다르더라도 트레킹에서 찍은 산 사진과 짧은 텍스트 프롬프트가 임베딩으로 비슷하게 표시됩니다.

SigLIP-2는 강력한 속도 대비 품질 균형을 제공하고, 다양한 입력 해상도를 지원하며, CPU와 GPU 모두에서 실행됩니다. SigLIP-2는 기존 CLIP과 같은 이전 모델에 비해 야외 촬영에 더욱 견고하게 설계되었습니다. 테스트하는 동안 SigLIP-2는 일관되게 신뢰할 수 있는 결과를 생성했습니다. 또한 지원도 매우 잘 되어 있어 이 프로젝트의 확실한 선택이 될 것입니다.

다음으로 임베딩과 파워 검색을 저장할 벡터 데이터베이스가 필요합니다. 이미지 임베딩에 대한 코사인 kNN 검색을 지원할 뿐만 아니라 단일 쿼리에서 지오펜스 및 텍스트 필터를 적용할 수 있어야 합니다. Elasticsearch는 벡터(dense_vector 필드의 HNSW kNN)를 매우 잘 처리하고 텍스트, 벡터, 위치 기반 쿼리를 결합하는 하이브리드 검색을 지원하며 필터링과 정렬 기능을 기본으로 제공합니다. 또한 수평으로 확장할 수 있어 몇 장의 사진에서 수천 장으로 쉽게 늘릴 수 있습니다. 공식 Elasticsearch Python 클라이언트는 배관을 단순하게 유지하며 프로젝트와 깔끔하게 통합됩니다. 마지막으로 검색 쿼리를 입력하고 결과를 볼 수 있는 경량 프론트엔드가 필요합니다. 파이썬 기반의 빠른 데모를 원한다면 Streamlit이 적합합니다. 파일 업로드, 반응형 이미지 그리드, 정렬 및 지오펜싱을 위한 드롭다운 메뉴 등 우리에게 필요한 기본 요소를 제공합니다. 로컬에서 쉽게 복제하고 실행할 수 있으며 Colab 노트북에서도 작동합니다.

구현

Elasticsearch 인덱싱 설계 및 인덱싱 전략

이 프로젝트에는 peaks_catalogphotos 의 두 가지 인덱스를 사용할 것입니다.

Peaks_catalog 인덱스

이 색인은 에베레스트 베이스캠프 트레킹 중에 볼 수 있는 주요 산봉우리를 간결하게 정리한 카탈로그 역할을 합니다. 이 색인에 포함된 각 문서는 에베레스트 산과 같은 하나의 산봉우리에 해당합니다. 각 산봉우리 문서에는 이름/별칭, 위도-경도 좌표(선택 사항), SigLIP-2 텍스트 프롬프트(+ 참조 이미지 옵션)를 혼합하여 구축한 단일 프로토타입 벡터가 저장됩니다.

인덱스 매핑:

필드

유형

목적/참고 사항

벡터/인덱싱

id

키워드

아마다블람

안정적인 슬러그/ID

-

이름

텍스트 + 키워드 하위 필드

["아마다블람","아마다블람"]

별칭/다국어 이름; 정확한 필터를 위한 names.raw

-

latlon

geo_point

{"lat":27.8617,"lon":86.8614}

위도/경도 조합의 피크 GPS 좌표(선택 사항)

-

elev_m

정수

6812

고도(선택 사항)

-

text_embed

dense_vector

768

이 피크에 대한 혼합 프로토타입(프롬프트 및 선택적으로 1~3개의 참조 이미지)

index:true, 유사성:"코사인", index_옵션:{type:"hnsw", m:16, ef_construction:128}

이 색인은 주로 이미지에서 산봉우리를 식별하는 등 이미지 대 이미지 검색에 사용됩니다. 또한 이 인덱스를 사용하여 텍스트-이미지 검색 결과를 개선합니다.

peaks_catalog 요약하면, "어떤 산입니까?" 라는 질문을 가장 가까운 이웃에 초점을 맞춘 문제로 변환하여 이미지 데이터의 복잡성에서 개념적 이해를 효과적으로 분리하는 것입니다.

peaks_catalog 인덱스의 인덱싱 전략: EBC 트레킹 중 가장 눈에 띄는 봉우리 목록을 만드는 것부터 시작합니다. 각 봉우리에 대해 지리적 위치, 이름, 동의어, 고도를 yaml 파일에 저장합니다. 다음 단계는 각 피크에 대한 임베딩을 생성하여 text_embed 필드에 저장하는 것입니다. 강력한 임베딩을 생성하기 위해 다음 기술을 사용합니다:

  • 다음을 사용하여 텍스트 프로토타입을 만듭니다:

    • 봉우리 이름

    • 프롬프트 앙상블 (여러 개의 다른 프롬프트를 사용하여 동일한 질문에 답하기) 등을 예로 들 수 있습니다:

      • "네팔 히말라야의 산봉우리 {name} 의 자연 사진"

      • "{name} 쿰부 지역의 랜드마크 봉우리, 고산 풍경"

      • "{name} 산 정상, 눈, 바위 능선"

    • 선택적 안티 콘셉트 (SigLIP-2에 일치하지 않을 대상을 알려줌): '그림, 일러스트, 포스터, 지도, 로고'에 대해 작은 벡터를 빼서 실제 사진에 편향되도록 합니다.

  • 피크의 참조 이미지가 제공된 경우 선택적으로 이미지 프로토타입을 생성합니다.

그런 다음 텍스트와 이미지 프로토타입을 혼합하여 최종 임베딩을 생성합니다. 마지막으로 모든 필수 필드가 포함된 문서가 색인됩니다:

def l2norm(v: np.ndarray) -> 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,
) -> 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 (<= 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 > 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]

peaks_catalog 색인의 샘플 문서입니다:

Elasticsearch의 peaks_catalog 인덱스에 있는 샘플 문서입니다.

사진 색인

이 기본 색인은 앨범의 모든 사진에 대한 자세한 정보를 저장합니다. 각 문서는 다음 정보를 포함하는 단일 사진을 나타냅니다:

  • 사진 앨범에서 사진의 상대 경로입니다. 검색 UI에서 일치하는 이미지를 보거나 이미지를 로드하는 데 사용할 수 있습니다.

  • 사진의 GPS 및 시간 정보.

  • SigLIP-2에서 생성된 이미지 인코딩을 위한 고밀도 벡터입니다.

  • predicted_peaks 를 사용하면 피크 이름을 기준으로 필터링할 수 있습니다. 인덱스 매핑

필드

유형

목적/참고 사항

벡터 / 인덱싱

경로

키워드

데이터/이미지/IMG_1234.HEIC

UI에서 썸네일/전체 이미지를 여는 방법

-

clip_image

dense_vector

768

SigLIP-2 이미지 임베딩

index:true, 유사성:"코사인", index_옵션:{type:"hnsw", m:16, ef_construction:128}

예측된 피크

키워드

["아마다블람","푸모리"]

인덱스 시점의 Top-K 추측(저렴한 UX 필터/패싯)

-

gps

geo_point

{"lat":27.96,"lon":86.83}

지리적 필터 사용

-

shot_time

date

2023-10-18T09:41:00Z

캡처 시간: 정렬/필터링

-

사진 색인 색인 전략: 앨범의 각 사진에 대해 다음을 수행합니다: 이미지 메타데이터에서 이미지 shot_timegps 정보를 추출합니다.

  • SigLIP-2 이미지 임베딩: 이미지를 모델에 전달하고 벡터를 L2 정규화합니다. clip_image 필드에 임베딩을 저장합니다.

  • 피크를 예측하여 predicted_peaks 필드에 저장합니다. 이를 위해 먼저 이전 단계에서 생성된 사진의 이미지 벡터를 가져온 다음 peaks_catalog 인덱스의 text_embed 필드에 대해 빠른 kNN 검색을 실행합니다. 상위 3~4개 봉우리는 유지하고 나머지는 무시합니다.

  • 이미지 이름과 경로에 해시를 수행하여 _id 필드를 계산합니다. 이렇게 하면 여러 번 실행한 후에도 중복이 발생하지 않습니다.

사진의 모든 필드를 결정한 후에는 일괄 색인을 사용하여 사진 문서를 일괄 색인합니다:

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",
) -> 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) >= 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")

사진 색인의 샘플 문서입니다:

Elasticsearch의 사진 색인에서 가져온 샘플 문서입니다.

요약하자면, 사진 인덱스는 앨범에 있는 모든 사진을 빠르고 필터링이 가능하며 kNN으로 저장할 수 있는 저장소입니다. 매핑은 일부러 최소화하여 빠르게 검색하고, 깔끔하게 표시하고, 공간과 시간별로 결과를 분류할 수 있는 구조로만 구성했습니다. 이 인덱스는 두 가지 검색 사용 사례를 모두 지원합니다. 두 인덱스를 생성하는 Python 스크립트는 여기에서 찾을 수 있습니다.

아래의 Kibana 지도 시각화에서는 사진 앨범의 문서를 녹색 점으로, peaks_catalog 인덱스의 산봉우리를 빨간색 삼각형으로 표시하며, 녹색 점이 에베레스트 베이스캠프 트레킹 코스와 잘 정렬되어 있습니다.

사진 앨범의 문서를 녹색 점으로, peaks_catalog 인덱스의 산봉우리를 빨간색 삼각형으로 표시하는 Kibana 지도 시각화에서 녹색 점이 에베레스트 베이스캠프 트레킹 코스와 잘 정렬되어 있습니다.

검색 사용 사례

이름으로 검색(텍스트-이미지): 이 기능을 사용하면 텍스트 검색을 통해 산봉우리 사진(및 '기도 깃발'과 같은 추상적인 개념까지)을 찾을 수 있습니다. 이를 위해 텍스트 입력은 SigLIP-2를 사용하여 텍스트 벡터로 변환됩니다. 강력한 텍스트 벡터 생성을 위해 인덱스에서 텍스트 임베딩을 생성할 때와 동일한 전략을 peaks_catalog 사용합니다. 즉, 텍스트 입력을 작은 프롬프트 앙상블과 결합하고, 작은 반개념 벡터를 뺀 다음 L2 정규화를 적용하여 최종 쿼리 벡터를 생성하는 것입니다. 그런 다음 photos.clip_image 필드에서 kNN 쿼리를 실행하여 가장 가까운 이미지를 찾기 위해 코사인 유사성을 기반으로 가장 일치하는 상위 피크를 검색합니다. 선택적으로 지역 및 날짜 필터를 적용하거나 photos.predicted_peaks 용어 필터를 쿼리의 일부로 적용하여 검색 결과의 연관성을 높일 수 있습니다(아래 쿼리 예시 참조). 이렇게 하면 트레킹에서 실제로 보이지 않는 유사 봉우리를 제외하는 데 도움이 됩니다.

Elasticsearch에서 다중 모드, 이름별 검색(텍스트-이미지 변환)이 작동하는 방식.

지리적 필터를 사용한 Elasticsearch 쿼리:

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"
       }
     }
}

이미지로 검색(이미지 대 이미지): 이 기능을 사용하면 사진에서 산을 식별하고 사진 앨범 내에서 같은 산의 다른 이미지를 찾을 수 있습니다. 이미지가 업로드되면 SigLIP-2 이미지 인코더가 이미지를 처리하여 이미지 벡터를 생성합니다. 그런 다음 peaks_catalog.text_embed 필드에서 kNN 검색을 수행하여 가장 일치하는 피크 이름을 식별합니다. 그 후, 일치하는 피크 이름에서 텍스트 벡터를 생성하고 사진 인덱스에서 또 다른 kNN 검색을 수행하여 해당 사진을 찾습니다.

Elasticsearch에서 이미지별(이미지 대 이미지) 멀티모달 검색이 작동하는 방식.

Elasticsearch 쿼리:

1단계: 일치하는 피크 이름 찾기

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...
         ]
       }
     }
   ]
 }
}

2단계: photos 색인에서 검색을 수행하여 일치하는 사진을 찾습니다(텍스트-이미지 검색 사용 사례에 표시된 것과 동일한 쿼리):

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"
       }
     }
}

스트림라이트 UI

모든 것을 하나로 모으기 위해 두 가지 검색 사용 사례를 모두 수행할 수 있는 간단한 Streamlit UI를 만들었습니다. 왼쪽 레일에는 스크롤 가능한 피크 목록( photos.predicted_peaks 에서 집계됨)이 체크박스와 미니맵/지리 필터와 함께 표시됩니다. 상단에는 이름으로 검색하기 상자와 사진 업로드에서 식별하기 버튼이 있습니다. 가운데 창에는 반응형 썸네일 그리드가 있어 kNN 점수, 예상 피크 배지, 캡처 시간을 보여줍니다. 각 이미지에는 전체 해상도 미리 보기를 위한 이미지 보기 버튼이 포함되어 있습니다.

이미지를 업로드하여 검색합니다: 피크를 예측하고 사진 앨범에서 일치하는 피크를 찾습니다.

아마다블람 산의 봉우리를 텍스트에서 이미지로, 이미지에서 이미지로 멀티모드 검색할 수 있는 간결한 UI를 제공합니다.

텍스트로 검색: 텍스트에서 앨범에서 일치하는 피크 찾기

산봉우리 라이브러리에서 텍스트로 검색하여 에베레스트산 봉우리를 검색하는 방법을 알아보세요.

결론

아마 다블람 사진만 볼 수 있나요? 를 작고 작동하는 멀티모달 검색 시스템으로 전환했습니다. 우리는 원시 트레킹 사진을 찍어 SigLIP-2 임베딩으로 변환하고, Elasticsearch를 사용해 벡터를 통한 빠른 kNN과 간단한 지리적/시간 필터를 통해 의미별로 적합한 이미지를 표시했습니다. 그 과정에서 저희는 혼합된 프로토타입의 작은 peaks_catalog 인덱스(식별용)와 이미지 벡터 및 EXIF의 확장 가능한 photos 인덱스(검색용) 등 두 가지 인덱스를 사용하여 문제를 분리했습니다. 실용적이고 재현 가능하며 쉽게 확장할 수 있습니다.

튜닝을 원한다면 몇 가지 설정으로 조정할 수 있습니다:

  • 쿼리 시간 설정: k (반환할 이웃 수) 및 num_candidates (최종 점수 산출 전 검색 범위). 이러한 설정은 여기 블로그에서 설명합니다.

  • 인덱스 시간 설정: m (그래프 연결성) 및 ef_construction (빌드 시간 정확도 대 메모리). 쿼리의 경우 ef_search 을 너무 높게 설정하면 일반적으로 약간의 지연 시간 절충을 통해 더 나은 리콜을 얻을 수 있습니다. 이러한 설정에 대한 자세한 내용은 이 블로그를 참조하세요.

앞으로 멀티모달다국어 검색을 위한 기본 모델/랭커가 곧 Elastic 생태계에 출시될 예정이므로 이미지/텍스트 검색과 하이브리드 랭킹이 더욱 강력해질 것입니다. ir.elastic.co+1

직접 체험해보고 싶으신가요?

이것으로 우리의 여정은 끝났고 이제 돌아올 시간입니다. 도움이 되었기를 바라며, 이 기능을 중단(또는 개선)하신다면 어떤 점이 달라졌는지 알려주시기 바랍니다.

관련 콘텐츠

LINQ to Elasticsearch ES|QL: C# 작성, Elasticsearch 쿼리

Florian Bernd

Elasticsearch에서 NLP와 벡터 검색으로 챗봇 기능 강화하기

Priscilla Parodi

Elasticsearch와 OpenSearch: 벡터 검색 성능 비교

Ugo Sangiorgi

AI 표절: Elasticsearch를 통한 표절 탐지

Priscilla Parodi

고급 RAG 기술 2부: 쿼리 및 테스트

Han Xiang Choong

최첨단 검색 환경을 구축할 준비가 되셨나요?

충분히 고급화된 검색은 한 사람의 노력만으로는 달성할 수 없습니다. Elasticsearch는 여러분과 마찬가지로 검색에 대한 열정을 가진 데이터 과학자, ML 운영팀, 엔지니어 등 많은 사람들이 지원합니다. 서로 연결하고 협력하여 원하는 결과를 얻을 수 있는 마법 같은 검색 환경을 구축해 보세요.

직접 사용해 보세요