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

이를 위해 Google의 SigLIP-2를 사용해 이미지와 텍스트 모두에 대한 벡터 임베딩을 생성하고 이를 Elasticsearch 벡터 데이터베이스에 저장합니다. 쿼리 시 텍스트 또는 이미지와 같은 검색 입력을 임베딩으로 변환하고 빠른 kNN 벡터 검색을 실행하여 결과를 검색합니다. 이 설정을 통해 텍스트 대 이미지 및 이미지 대 이미지 검색을 효율적으로 수행할 수 있습니다. 스트림릿 UI는 텍스트 기반 검색을 통해 앨범에서 일치하는 사진을 찾아서 볼 수 있을 뿐만 아니라 업로드된 이미지에서 산봉우리를 식별하고 사진 앨범에서 해당 산의 다른 사진을 볼 수 있는 프론트엔드를 제공함으로써 이 프로젝트에 활기를 불어넣었습니다. 또한 검색 정확도를 개선하기 위해 취한 조치와 실용적인 팁과 요령에 대해서도 설명합니다. 더 자세히 살펴볼 수 있도록 GitHub 리포지토리와 Colab 노트북을 제공합니다.
시작 방법
이 블로그 게시물은 에베레스트 베이스캠프 트레킹에서 찍은 아마 다블람 산의 모든 사진을 보여 달라는 10살짜리 아이의 요청에 영감을 받아 작성했습니다. 사진첩을 훑어보면서 이름을 알 수 없는 다른 산봉우리도 몇 개 더 찾아달라는 요청을 받았습니다.
이를 통해 재미있는 컴퓨터 비전 프로젝트가 될 수 있겠다는 생각이 들었습니다. 우리가 달성하고자 했던 목표:
이름으로 산봉우리 사진 찾기
이미지에서 산봉우리 이름을 맞추고 사진 앨범에서 비슷한 봉우리를 찾습니다.
컨셉 쿼리가 작동하도록 하기(사람, 강, 기도 깃발 등)

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

이미지 임베딩을 생성하기 위해 다국어 비전 언어 인코더를 사용하므로 산 사진과 "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_catalog 와 photos 의 두 가지 인덱스를 사용할 것입니다.
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 필드에 저장하는 것입니다. 강력한 임베딩을 생성하기 위해 다음 기술을 사용합니다:
다음을 사용하여 텍스트 프로토타입을 만듭니다:
피크의 참조 이미지가 제공된 경우 선택적으로 이미지 프로토타입을 생성합니다.
그런 다음 텍스트와 이미지 프로토타입을 혼합하여 최종 임베딩을 생성합니다. 마지막으로 모든 필수 필드가 포함된 문서가 색인됩니다:
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 색인의 샘플 문서입니다:

사진 색인
이 기본 색인은 앨범의 모든 사진에 대한 자세한 정보를 저장합니다. 각 문서는 다음 정보를 포함하는 단일 사진을 나타냅니다:
사진 앨범에서 사진의 상대 경로입니다. 검색 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_time 및 gps 정보를 추출합니다.
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")사진 색인의 샘플 문서입니다:

요약하자면, 사진 인덱스는 앨범에 있는 모든 사진을 빠르고 필터링이 가능하며 kNN으로 저장할 수 있는 저장소입니다. 매핑은 일부러 최소화하여 빠르게 검색하고, 깔끔하게 표시하고, 공간과 시간별로 결과를 분류할 수 있는 구조로만 구성했습니다. 이 인덱스는 두 가지 검색 사용 사례를 모두 지원합니다. 두 인덱스를 생성하는 Python 스크립트는 여기에서 찾을 수 있습니다.
아래의 Kibana 지도 시각화에서는 사진 앨범의 문서를 녹색 점으로, peaks_catalog 인덱스의 산봉우리를 빨간색 삼각형으로 표시하며, 녹색 점이 에베레스트 베이스캠프 트레킹 코스와 잘 정렬되어 있습니다.

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

지리적 필터를 사용한 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 쿼리:
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 점수, 예상 피크 배지, 캡처 시간을 보여줍니다. 각 이미지에는 전체 해상도 미리 보기를 위한 이미지 보기 버튼이 포함되어 있습니다.
이미지를 업로드하여 검색합니다: 피크를 예측하고 사진 앨범에서 일치하는 피크를 찾습니다.

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

결론
아마 다블람 사진만 볼 수 있나요? 를 작고 작동하는 멀티모달 검색 시스템으로 전환했습니다. 우리는 원시 트레킹 사진을 찍어 SigLIP-2 임베딩으로 변환하고, Elasticsearch를 사용해 벡터를 통한 빠른 kNN과 간단한 지리적/시간 필터를 통해 의미별로 적합한 이미지를 표시했습니다. 그 과정에서 저희는 혼합된 프로토타입의 작은 peaks_catalog 인덱스(식별용)와 이미지 벡터 및 EXIF의 확장 가능한 photos 인덱스(검색용) 등 두 가지 인덱스를 사용하여 문제를 분리했습니다. 실용적이고 재현 가능하며 쉽게 확장할 수 있습니다.
튜닝을 원한다면 몇 가지 설정으로 조정할 수 있습니다:
쿼리 시간 설정:
k(반환할 이웃 수) 및num_candidates(최종 점수 산출 전 검색 범위). 이러한 설정은 여기 블로그에서 설명합니다.인덱스 시간 설정:
m(그래프 연결성) 및ef_construction(빌드 시간 정확도 대 메모리). 쿼리의 경우ef_search을 너무 높게 설정하면 일반적으로 약간의 지연 시간 절충을 통해 더 나은 리콜을 얻을 수 있습니다. 이러한 설정에 대한 자세한 내용은 이 블로그를 참조하세요.
앞으로 멀티모달 및 다국어 검색을 위한 기본 모델/랭커가 곧 Elastic 생태계에 출시될 예정이므로 이미지/텍스트 검색과 하이브리드 랭킹이 더욱 강력해질 것입니다. ir.elastic.co+1
직접 체험해보고 싶으신가요?
이것으로 우리의 여정은 끝났고 이제 돌아올 시간입니다. 도움이 되었기를 바라며, 이 기능을 중단(또는 개선)하신다면 어떤 점이 달라졌는지 알려주시기 바랍니다.





