검색결과 리스트
글
FiftyOne 라이브러리로 Segmentation 데이터셋 볼 때 각 클래스별로 따로 볼 수 있도록 설정하는 방법

Vision AI엔지니어로서 8년째 일하고 있음에도 최근까지 AI모델에만 매달리다가 현장에서 갓 생산된 데이터를 AI모델이 쉽게 받아들이도록 가공해야 성능 향상에 도움이 된다는 것을 최근에야 깨닫게 되었고, 실제 데이터 중심의 연구를 수행하는 AI엔지니어들이 현장에서 더 많은 역할을 하고 있음을 확인할 수 있었습니다. 좋은 데이터를 통해 좋은 AI모델이 만들어짐을 알지만, 어떻게 하면 우리들이 원하는 데이터로 만들 수 있는지 탐색하는 것도 쉽지 않았는데, 이러단 Vision 데이터셋들을 쉽게 관리할 수 있는 FiftyOne 라이브러리가 존재함을 알게 되었습니다.
FiftyOne 라이브러리는 컴퓨터 비전 데이터셋을 구축, 시각화, 평가할 수 있는 강력한 오픈소스 데이터 관리 툴입니다. 대규모 이미지나 비디오 데이터를 효율적으로 탐색하고, 모델의 예측 결과를 직관적으로 분석할 수 있어 AI 연구 및 실무에서 실제 널리 활용되고 있다합니다. 다만, 라이브러리 자체에서 제공하는 기능들을 내가 원하는 대로 쓰기 위해서는 어느 정도 데이터 가공이 필요했습니다. 특히 Semantic Segmentation 모델을 주로 사용해온 저에게는 좋은 도구가 있음에도 쉽게 사용하지 못해 약간의 장벽을 느끼기도 하지만 우리의 훌륭하신 GPT 형님들이 친절하게 사용방법을 잘 알려주십니다.
본 글에서는 FiftyOne 환경에서 Semantic Segmentation 모델의 데이터를 다룰 때 발생하는 시각화의 한계를 짚어보고, 이를 극복하여 분석 효율을 극대화하는 실무적인 데이터 전처리 기법을 소개합니다.
Semantic Segmentation 모델을 Fiftyone에서 사용할 때 발생하는 문제점들
FiftyOne을 활용해 Semantic Segmentation 데이터셋을 분석하고자 할 때, 흔히 직면하는 구조적인 제약이 있습니다. Instance Segmentation 데이터와 달리, Semantic Segmentation은 일반적으로 하나의 마스크(Mask) 이미지 내에 모든 클래스(Class) 정보가 픽셀 값으로 통합되어 저장된다는 점입니다. 이로 인해 FiftyOne 뷰어 상에서 내가 원하는 특정 클래스만 독립적으로 분리하여 시각화하거나 분석하는 데 어려움이 따릅니다.
그래서 아래의 코드를 통해 GT 혹은 Pred 데이터에서 각각의 Class를 별도로 추출한 다음, 이를 FiftyOne DB에 적재하여 각 class별로 데이터를 확인할 수 있도록 하였습니다.
※ 본 소스코드는 Fiftyone 1.2.0 버전을 기준으로 작성되었습니다.
fiftyone_loader.py
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
|
import fiftyone as fo
import os
import numpy as np
from PIL import Image
# ==========================================
# 1. Dataset Classes (클래스 매핑)
# ==========================================
VOC_CLASSES = {
0: "background", 1: "aeroplane", 2: "bicycle", 3: "bird", 4: "boat",
5: "bottle", 6: "bus", 7: "car", 8: "cat", 9: "chair", 10: "cow",
11: "diningtable", 12: "dog", 13: "horse", 14: "motorbike", 15: "person",
16: "pottedplant", 17: "sheep", 18: "sofa", 19: "train", 20: "tvmonitor",
255: "void"
}
CITYSCAPES_CLASSES = {
0: "road", 1: "sidewalk", 2: "building", 3: "wall", 4: "fence",
5: "pole", 6: "traffic light", 7: "traffic sign", 8: "vegetation",
9: "terrain", 10: "sky", 11: "person", 12: "rider", 13: "car",
14: "truck", 15: "bus", 16: "train", 17: "motorcycle", 18: "bicycle",
255: "unlabeled"
}
ADE20K_CLASSES = {
0: "background", 1: "wall", 2: "building", 3: "sky", 4: "floor",
5: "tree", 6: "ceiling", 7: "road", 8: "bed", 9: "windowpane",
# 필요시 추가 클래스 확장 (최대 150개)
}
MAPILLARY_CLASSES = {
0: "Bird", 1: "Ground Animal", 2: "Curb", 3: "Fence", 4: "Guard Rail",
5: "Barrier", 6: "Wall", 7: "Bike Lane", 8: "Crosswalk", 9: "Curb Cut",
# 필요시 추가 클래스 확장 (최대 65/124개)
}
# ==========================================
# 2. 데이터셋 포맷별 디폴트 설정 (Suffix 꼬리표 포함)
# ==========================================
DATASET_DEFAULTS = {
"VOC": {
"image_folder": "JPEGImages", "gt_folder": "SegmentationClass",
"image_ext": ".jpg", "mask_ext": ".png",
"image_suffix": "", "gt_suffix": "",
"classes": VOC_CLASSES
},
"CITYSCAPES": {
"image_folder": "leftImg8bit", "gt_folder": "gtFine",
"image_ext": ".png", "mask_ext": ".png",
"image_suffix": "_leftImg8bit",
"gt_suffix": "_gtFine_labelIds",
"classes": CITYSCAPES_CLASSES
},
"ADE20K": {
"image_folder": "images", "gt_folder": "annotations",
"image_ext": ".jpg", "mask_ext": ".png",
"image_suffix": "", "gt_suffix": "",
"classes": ADE20K_CLASSES
},
"MAPILLARY": {
"image_folder": "images", "gt_folder": "instances",
"image_ext": ".jpg", "mask_ext": ".png",
"image_suffix": "", "gt_suffix": "",
"classes": MAPILLARY_CLASSES
}
}
# ==========================================
# 3. 데이터셋 로더 함수 (치환 및 마스크 분리 로직 포함)
# ==========================================
def load_custom_segmentation_dataset(
dataset_name,
dataset_format,
base_dir="",
# 사용자 지정 경로
custom_image_dir=None,
custom_gt_dir=None,
custom_pred_dir=None,
# 사용자 지정 꼬리표
custom_image_suffix=None,
custom_gt_suffix=None,
custom_pred_suffix=None,
# 적재 옵션
load_gt=True,
load_pred=True
):
format_info = DATASET_DEFAULTS.get(dataset_format.upper())
if not format_info:
raise ValueError(f"지원하지 않는 포맷입니다: {dataset_format}")
# 1. 경로 세팅 (사용자 지정 우선, 없으면 디폴트)
image_dir = custom_image_dir or os.path.join(base_dir, format_info["image_folder"])
gt_dir = custom_gt_dir or os.path.join(base_dir, format_info["gt_folder"])
pred_dir = custom_pred_dir or os.path.join(base_dir, "preds")
image_ext = format_info["image_ext"]
mask_ext = format_info["mask_ext"]
# 2. 꼬리표(Suffix) 세팅 (사용자 지정 우선, 없으면 디폴트)
img_suf = custom_image_suffix if custom_image_suffix is not None else format_info["image_suffix"]
gt_suf = custom_gt_suffix if custom_gt_suffix is not None else format_info["gt_suffix"]
pred_suf = custom_pred_suffix if custom_pred_suffix is not None else gt_suf
# 3. FiftyOne 데이터셋 초기화
if dataset_name in fo.list_datasets():
fo.delete_dataset(dataset_name)
dataset = fo.Dataset(dataset_name)
# 스키마 미리 선언 (클래스명_gt, 클래스명_pred 순서로 UI에 강제 생성)
ignore_classes = ["background", "void", "unlabeled"]
for cls_id, cls_name in format_info["classes"].items():
if cls_name.lower() in ignore_classes:
continue
if load_gt:
dataset.add_sample_field(f"{cls_name}_gt", fo.core.fields.EmbeddedDocumentField, embedded_doc_type=fo.Segmentation)
if load_pred:
dataset.add_sample_field(f"{cls_name}_pred", fo.core.fields.EmbeddedDocumentField, embedded_doc_type=fo.Segmentation)
# UI 색상 매핑 통일
dataset.default_mask_targets = format_info["classes"]
samples = []
if not os.path.exists(image_dir):
raise FileNotFoundError(f"이미지 폴더를 찾을 수 없습니다: {image_dir}")
# 4. 파일 순회 및 분리 적재 로직
for filename in os.listdir(image_dir):
if not filename.endswith(image_ext):
continue
image_path = os.path.join(image_dir, filename)
sample = fo.Sample(filepath=image_path)
# 순수 파일명 추출 및 꼬리표 조립
base_name = filename[:-len(image_ext)]
if img_suf and base_name.endswith(img_suf):
core_name = base_name[:-len(img_suf)]
else:
core_name = base_name
gt_filename = f"{core_name}{gt_suf}{mask_ext}"
pred_filename = f"{core_name}{pred_suf}{mask_ext}"
# GT 적재 (마스크를 클래스별로 쪼개기)
if load_gt:
gt_path = os.path.join(gt_dir, gt_filename)
if os.path.exists(gt_path):
gt_img = np.array(Image.open(gt_path))
for cls_id, cls_name in format_info["classes"].items():
if cls_name.lower() in ignore_classes:
continue
bin_mask = (gt_img == cls_id)
if np.any(bin_mask):
# 저장 필드명을 '클래스명_gt'로 변경
sample[f"{cls_name}_gt"] = fo.Segmentation(mask=bin_mask.astype(np.uint8) * cls_id)
else:
print(f"[경고] GT 마스크 누락: {gt_path}")
# Pred 적재 (마스크를 클래스별로 쪼개기)
if load_pred:
pred_path = os.path.join(pred_dir, pred_filename)
if os.path.exists(pred_path):
pred_img = np.array(Image.open(pred_path))
for cls_id, cls_name in format_info["classes"].items():
if cls_name.lower() in ignore_classes:
continue
bin_mask = (pred_img == cls_id)
if np.any(bin_mask):
# [수정됨] 저장 필드명을 '클래스명_pred'로 변경
sample[f"{cls_name}_pred"] = fo.Segmentation(mask=bin_mask.astype(np.uint8) * cls_id)
samples.append(sample)
dataset.add_samples(samples)
print(f"'{dataset_name}' 데이터셋 구축 완료! (총 {len(samples)}장 적재)")
return dataset
# ==========================================
# 4. 실행 예시 (main 함수)
# ==========================================
if __name__ == "__main__":
# [사용자 설정 영역] 직관적으로 이미지, GT, Pred 경로를 한 곳에서 관리
IMG_DIR = "/path/to/dataset/images"
GT_DIR = "/path/to/dataset/ground_truth"
PRED_DIR = "/path/to/dataset/model_predictions"
# 데이터셋 구축
my_dataset = load_custom_segmentation_dataset(
dataset_name="Segmentation_Analysis_Project",
dataset_format="CITYSCAPES", # VOC, CITYSCAPES, ADE20K, MAPILLARY 중 택 1
# 경로 연결
custom_image_dir=IMG_DIR, custom_gt_dir=GT_DIR, custom_pred_dir=PRED_DIR,
# 꼬리표(Suffix) 설정 (파일명이 모두 동일하다면 빈 문자열("") 지정)
custom_image_suffix="_leftImg8bit",
custom_gt_suffix="_gtFine_labelIds",
custom_pred_suffix="_pred",
# 적재 여부 선택
load_gt=True,
load_pred=False
)
# FiftyOne App 실행
session = fo.launch_app(my_dataset)
session.wait()
|
cs |
위에서 각각의 클래스를 별개로 저장한 다음 FiftyOne에 적재하면 각 Class마다 데이터를 별개로 제어할 수 있는 것을 확인할 수 있습니다.

위 데이터셋에서 'horse_gt'와 'car_gt'를 해제하면 말과 자동차의 Segment가 사라지는 것을 확인하실 수 있습니다.

'공대생의 팁' 카테고리의 다른 글
| [논문 리뷰] 처음 보는 물체도 덥석! 박스 모델링으로 해결하는 로봇의 손동작(Minimum Volume Bounding Box Decompos (0) | 2026.02.01 |
|---|---|
| Ubuntu로 apt 사용시 SSL 인증서 신뢰 문제 발생시 해결방법 (0) | 2025.11.28 |
| Ubuntu에서 Youtube 동영상 PIP모드에서 다른 창에 의해 화면이 가려질 때 해결방법 (0) | 2025.10.28 |
| 티스토리에서 카카오 로그인시 튀어나오는 팝업 광고 삭제 방법 (0) | 2025.05.20 |
| Huggingface에서 데이터셋 다운로드가 계속 끊길때 해결방법 (0) | 2025.01.14 |
설정
트랙백
댓글
글
[논문 리뷰] 처음 보는 물체도 덥석! 박스 모델링으로 해결하는 로봇의 손동작(Minimum Volume Bounding Box Decompos
인간에게는 아주 쉬운 '물건 집기'가 로봇에게는 왜 그렇게 어려울까요? 우리가 눈을 감고도 컵을 잡을 수 있는 이유는 컵의 전체적인 '형체'를 이미 알고 있기 때문입니다. 하지만 로봇은 처음 보는 물체를 마주하면 방대한 3D 데이터를 처리하느라 과부하에 걸리곤 합니다.
오늘 소개할 논문은 로봇에게 복잡한 계산 대신 '박스(Box)'라는 아주 단순한 안경을 씌워주는 기술을 제안합니다. 이 기술이 완성되면 로봇은 처음 보는 복잡한 장난감이나 도구도 망설임 없이 덥석 잡아 우리에게 건네줄 수 있게 될 것입니다.
정교함보다 중요한 것은 '속도'와 '단순함'
로봇이 실생활에서 우리를 도우려면 물체를 인식하는 데 몇 분씩 걸려서는 안 됩니다. 기존 연구들은 물체를 아주 정밀한 곡면으로 표현하려 했지만, 이는 계산이 너무 복잡하고 센서의 작은 노이즈에도 쉽게 무너지는 단점이 있었습니다.
이 논문은 "사람도 사과를 잡을 때나 컵을 잡을 때 손의 모양이 크게 다르지 않다"는 점에 주목했습니다. 즉, 물체의 아주 미세한 굴곡보다는 전체적인 '덩어리감'만 알아도 충분히 안정적으로 잡을 수 있다는 것이죠. 그래서 선택한 것이 바로 가장 계산하기 쉬운 '박스' 형태입니다.

왼쪽은 기존 방식대로 물체를 아주 정밀하게 묘사한 모습입니다. 보기엔 좋지만 계산이 너무 무겁죠. 오른쪽은 이 논문이 제안한 방식입니다. 토끼를 하나의 커다란 상자에 넣은 것 같은데, 이것만으로도 로봇은 "아, 이 정도 크기의 물체가 이 방향으로 있구나"라는 것을 즉시 알 수 있습니다.
물체를 박스로 쪼개는 'Fit-and-Split'의 마법
이 논문의 핵심은 물체를 가장 잘 설명하는 박스들의 조합을 찾는 것입니다. 그 과정은 크게 세 단계로 나뉩니다.
① 첫 번째 박스 씌우기 (Fit)
먼저 물체의 모든 점을 포함하는 가장 타이트한 박스 하나를 만듭니다. 이를 위해 \(O(n \log n)\)이라는 매우 빠른 효율을 가진 알고리즘을 사용합니다.
② 그림자를 이용한 분할 (Split)
박스 하나로는 물체의 모양을 다 설명할 수 없으니 이를 적절히 쪼개야 합니다. 이때 3D 공간을 직접 뒤지는 대신, 박스의 각 면에 물체의 그림자를 비추는 방식을 썼습니다. 그림자를 보면 어디를 잘라야 박스 두 개가 물체의 모양을 더 잘 감싸게 될지 한눈에 보이기 때문입니다.

3D 입체를 다루는 건 어렵지만, 종이에 비친 그림자를 다루는 건 쉽습니다. 토끼를 세 방향에서 바라본 그림자를 그리드 위에 뿌려놓은 모습입니다. 이 그림자들의 면적을 계산해서, 어느 지점을 싹둑 잘라야 가장 알짜배기 박스 두 개가 나올지 순식간에 찾아냅니다.
③ 똑똑한 중단 (Stopping Criterion)
무한정 박스를 쪼개는 것이 아니라, "박스를 더 쪼갰을 때 부피가 획기적으로 줄어드는가?"를 따집니다. 만약 더 쪼개도 부피 차이가 거의 없다면(즉, 물체의 모양을 더 잘 설명하지 못한다면) 계산을 멈춥니다. 또한 데이터가 너무 적은 부분은 노이즈로 간주하고 무시하여 안정성을 높였습니다.
실험 결과
이 논문은 이상적인 3D 모델뿐만 아니라, 실제 센서로 얻은 거칠고 불완전한 데이터로도 실험을 진행했습니다.

머그컵, 오리 인형, 전화기 등이 박스들로 쪼개지는 과정입니다. 처음에는 박스 1개였다가 설정값을 조절함에 따라 박스가 늘어나며 물체의 실루엣을 닮아갑니다. 지저분한 실제 카메라 데이터(가장 아래 이미지)에서도 꽤 정확하게 형태를 잡아내는 것을 볼 수 있습니다.
실험 결과, 박스로 요약된 모델을 바탕으로 로봇이 잡을 위치를 정했을 때, 아무 정보 없이 무작위로 잡으려 할 때보다 성공률과 안정성이 비약적으로 향상되었습니다. 특히 노이즈가 많은 실제 환경 데이터에서도 박스 모델은 형태의 뼈대를 놓치지 않고 잘 잡아냈습니다.
이 기술이 가져올 미래: 의미 있는 손길
이 기술의 진짜 매력은 로봇에게 '행동의 의미'를 가르칠 수 있다는 점입니다.
안정적 이동: 물체를 단단히 옮겨야 한다면 가장 큰 박스 부분을 꽉 잡습니다.
섬세한 전달: 사람에게 건네주어야 한다면 물체 끝부분의 가장 작은 박스를 잡아 사람이 잡을 자리를 남겨줍니다.
전시 및 검사: 카메라가 물체를 잘 볼 수 있도록 가장 구석진 박스를 잡습니다.

실제 로봇 손이 물체를 파지하는 시뮬레이션 장면입니다. 그래프의 점들이 우측 상단으로 갈수록 로봇이 물체를 안정적으로 꽉 잡았다는 뜻인데, 박스 모델을 사용한 결과(검은 점)가 무작위 시도(초록 점)보다 월등히 우수함을 보여줍니다.
결론
결국, 복잡한 세상을 단순한 박스들의 조합으로 바라보는 것만으로도 로봇은 훨씬 더 인간답고 효율적으로 일할 수 있게 됩니다. 완벽함보다는 '적당한 단순함'이 실생활 로봇에게 얼마나 큰 힘이 되는지 보여준 흥미로운 연구였습니다.
자세한 내용은 아래의 논문을 읽어주시기 바랍니다.
https://ieeexplore.ieee.org/document/4543434
Minimum volume bounding box decomposition for shape approximation in robot grasping
Thinking about intelligent robots involves consideration of how such systems can be enabled to perceive, interpret and act in arbitrary and dynamic environments. While sensor perception and model interpretation focus on the robot’s internal representatio
ieeexplore.ieee.org
'공대생의 팁' 카테고리의 다른 글
| FiftyOne 라이브러리로 Segmentation 데이터셋 볼 때 각 클래스별로 따로 볼 수 있도록 설정하는 방법 (0) | 2026.03.21 |
|---|---|
| Ubuntu로 apt 사용시 SSL 인증서 신뢰 문제 발생시 해결방법 (0) | 2025.11.28 |
| Ubuntu에서 Youtube 동영상 PIP모드에서 다른 창에 의해 화면이 가려질 때 해결방법 (0) | 2025.10.28 |
| 티스토리에서 카카오 로그인시 튀어나오는 팝업 광고 삭제 방법 (0) | 2025.05.20 |
| Huggingface에서 데이터셋 다운로드가 계속 끊길때 해결방법 (0) | 2025.01.14 |
설정
트랙백
댓글
글
GPT가 찾아준 초콜릿의 정석, 런던 메종 사마디(Maison Samadi) 방문기 [2025.10.11]
안녕하세요! 오늘은 런던에서 아주 특별한 미션을 수행하고 온 이야기를 들려드릴까 합니다. 바로 요즘 가장 핫한 '두바이 초콜릿'을 런던 한복판에서 찾아내는 미션이었죠! (사실 이 글을 쓰는 2026년 시점에서는 두쫀쿠가 유행이지만요)
런던에 여행중이었던 저에게 한국의 친한 친구가 톡을 보냈어요. "런던이면 혹시 그 유명한 두바이 초콜릿 스타일의 수제 초콜릿 구할 수 있는 곳 없어?"라며 간절한 부탁을 하더라고요.
요즘 워낙 인기라 구하기 쉽지 않을 것 같아 고민하다가, 저는 GPT에게 구체적으로 물어보기로 했습니다.
"런던에서 두바이 초콜릿 스타일의 수제 초콜릿을 구할 수 있는 곳 알려줘"
그러자 GPT가 런던 첼시(Chelsea)에 위치한 **'메종 사마디(Maison Samadi)'**를 강력 추천해 주었습니다. 중동의 정통 레시피와 유럽의 고급 초콜릿 기술이 만난 곳이라 친구가 찾는 그 맛을 가장 완벽하게 구현하는 곳이라는 설명과 함께요!
1872년부터 이어온 전통, 2016년 런던에 상륙하다
추천을 받고 찾아보니 이곳은 역사가 정말 깊은 곳이었습니다. 사마디 가문은 무려 1872년 베이루트에서 처음 디저트 사업을 시작했다고 해요. 150년 넘게 대를 이어온 장인 정신이 깃든 곳이죠.
런던 첼시에 이 멋진 부티크가 처음 문을 연 것은 2016년입니다. 오픈한 지 10년이 채 되지 않았지만, 이미 '런던 최고의 초콜릿' 상을 휩쓸며 현지인들이 가장 사랑하는 프리미엄 초콜릿 가게로 자리 잡았습니다.
메종 사마디는 런던에서 가장 우아한 거리 중 하나인 **킹스 로드(King's Road)**에 있습니다. 찾아가는 방법은 크게 두 가지예요.
방법 1. 슬론 스퀘어(Sloane Square) 역에서 걷기 (추천!): 지하철 역에서 나와 킹스 로드를 따라 쭉 걸으면 됩니다. 약 20분(1마일) 정도 소요되는데, 첼시 특유의 예쁜 부티크와 갤러리를 구경하다 보면 금방 도착해요.
방법 2. 사우스 켄싱턴(South Kensington) 역에서 버스 이용: 역 근처에서 버스를 타면 약 10분 내외로 도착할 수 있어 체력을 아끼기에 좋습니다.
주소: 301 King's Rd, London SW3 5EP

King's Road를 걷다 보면 아기자기한 가게들이 눈 앞에 펼쳐집니다

계속 걷다보면 우리가 찾던 초콜릿 가게인 '메종 사마디'를 찾으실 수 있습니다.

가게 안은 오래된 역사를 가진 가게 치고는 모던한 분위기입니다.
"배부를 때까지 드셔보세요" 감동의 시식 경험

매장에 들어서자마자 고급스러운 향기와 친절한 직원분들이 저를 맞이해 주셨습니다.

친구가 찾는 '두바이 초콜릿' 스타일의 피스타치오와 견과류가 들어간 라인을 문의하니, 직원분께서 망설임 없이 시식을 권해주셨어요.

그런데 시식 수준이 정말 남달랐습니다. "이게 시식인가요, 식사인가요?" 싶을 정도로 다양한 종류의 초콜릿을 맛볼 수 있었습니다..

하나하나 맛볼 때마다 입안 가득 퍼지는 고소한 견과류와 진한 초콜릿의 조화에 정말 배가 부를 지경이었답니다.

덕분에 친구에게 줄 선물을 고르는 게 훨씬 쉬워졌어요!

다양한 모양과 종류의 초콜릿들이 있고, 심지어 직원분이 직접 하나씩 건내주기도 해서 입안이 얼얼할 정도로 시식해보았습니다.
구매 후기: "런던에 간다면 무조건 들르세요!"

친구의 요청으로 우연히 GPT를 통해 알게 된 곳이지만, 제가 가본 전세계 초콜릿 가게 중 단연 최고였습니다.

단순히 물건을 파는 곳이 아니라, 시식부터 응대까지 대접받는다는 느낌이 확실히 드는 곳이었어요.

런던 여행 중 현지인들이 열광하는 진짜 초콜릿 맛집을 찾으신다면, 혹은 저처럼 특별한 '두바이 초콜릿'의 풍미를 느끼고 싶으시다면 메종 사마디는 필수 코스입니다. 저는 대만족이었고, 선물 받은 친구도 너무 좋아할 것 같아요!

이번 여행 GPT가 우연히 찾아준 보석같은 초콜릿 가게를 발견할 수 있어 너무 기뻤고 앞으로도 여행 도중에 GPT를 업무 수행시와 같이 적극적으로 사용해보고자 합니다. 어러분들도 GPT를 통해 색다른 여행을 해보시길 바랍니다!
'좌충우돌 여행기 > 해외여행' 카테고리의 다른 글
| 환승객에게 제공되는 호텔 혜택을 누리지 못했던 중국 정저우 환승여행기[2025.10.03] (0) | 2025.12.08 |
|---|---|
| 중국 쿤밍 환승여행기(3) - 창수이 국제공항 출국[2018.12.21] (0) | 2018.12.29 |
| 중국 쿤밍 환승여행기(2) - 쿤밍북부역 전통시장[2018.12.21] (0) | 2018.12.28 |
| 중국 쿤밍 환승여행기(1) - 쿤밍 지하철 타고 시내 나가기[2018.12.21] (0) | 2018.12.27 |
| 요코하마스터디움에서 공연관람기 - TUBE LIVE AROUND SPECIAL 2018(2)[2018.08.25] (0) | 2018.09.03 |