출처: R. Girshick et al.(2014), Rich feature hierarchies for accurate object detection and semantic segmentation Tech report (v5)
json 기반 Annotation File 예시
images: 이미지 메타데이터annotations: 객체의 바운딩 박스, 클래스, 세그멘테이션 마스크 등categories: 클래스 정보licenses 및 info: 데이터셋 라이선스와 기타 정보{
"info": { // 일반 정보
"year": 2025,
"version": "1.0",
"description": "COCO-style dataset",
"contributor": "Your Name",
"date_created": "2025-01-16"
},
"licenses": [ // 라이센스 정보
{
"id": 1,
"name": "Creative Commons Attribution 4.0",
"url": "http://creativecommons.org/licenses/by/4.0/"
}
],
"images": [ // 이미지 파일 메타데이터
{
"id": 1,
"file_name": "image1.jpg",
"height": 480,
"width": 640,
"date_captured": "2025-01-15",
"license": 1
}
],
"annotations": [
{
"id": 1,
"image_id": 1,
"category_id": 1, // 객체의 클래스 ID
// 바운딩 박스 좌표와 크기 (x_min, y_min, width, height)
"bbox": [50, 100, 150, 200],
"area": 30000, // 객체 면적
// 세그멘테이션 마스크(픽셀 좌표)
"segmentation": [
[50, 100, 200, 100, 200, 300, 50, 300]
],
"iscrowd": 0
}
],
"categories": [
{
"id": 1,
"name": "cat", // 클래스 이름
"supercategory": "animal"
}
]
}
wget -c https://github.com/matterport/Mask_RCNN/releases/download/v2.1/balloon_dataset.zip
# unzip이 설치되지 않은 경우 apt-get install unzip으로 선설치 필요
unzip balloon_dataset.zip -d ./balloondataset/
import glob
import os
MMDET_PATH = "/workspace/mmdetection"
print(f"train size: {len(glob.glob(os.path.join(MMDET_PATH, 'balloondataset/balloon/train/*.jpg')))}")
print(f"val size: {len(glob.glob(os.path.join(MMDET_PATH, 'balloondataset/balloon/val/*.jpg')))}")
# train size: 61
# val size: 13
import mmcv
import matplotlib.pyplot as plt
fname = os.listdir(os.path.join(MMDET_PATH, "balloondataset/balloon/train/"))[0]
img = mmcv.imread(os.path.join(MMDET_PATH, "balloondataset/balloon/train", fname))
plt.figure(figsize=(8,4))
plt.imshow(mmcv.bgr2rgb(img))
plt.axis(False)
plt.show()
import mmengine
annotation = mmengine.load(os.path.join(MMDET_PATH, "balloondataset/balloon/train/via_region_data.json"))
ann_sample = annotation[fname+"1115004"] # Annotation File 참조
print(ann_sample)
# {'fileref': '',
# 'size': 1115004,
# 'filename': '34020010494_e5cb88e1c4_k.jpg',
# 'base64_img_data': '',
# 'file_attributes': {},
# 'regions': {'0': {'shape_attributes': {'name': 'polygon',
# 'all_points_x': [1020,
# 1000,
# 994,
# (후략)
def COCOConverter(ann_file, out_file, img_prefix):
# Annotation File 읽어오기
data_infos = mmengine.load(ann_file)
annots, images = list(), list()
obj_cnt = 0
# Annotation File 값을 하나씩 불러오기
for idx, v in enumerate(mmengine.track_iter_progress(list(data_infos.values()))):
file_name = v["filename"]
img_path = os.path.join(img_prefix, file_name)
height, width = mmcv.imread(img_path).shape[:2] # (높이, 너비, 채널) 중 채널 제외
images.append(
dict(
id = idx,
file_name = file_name,
height = height,
width = width,
)
)
bboxes, labels, masks = list(), list(), list()
for _, obj in v["regions"].items():
assert not obj["region_attributes"]
obj = obj["shape_attributes"]
px = obj["all_points_x"]
py = obj["all_points_y"]
# COCO와 Balloon 데이터셋의 좌표 보정
poly = [(x+0.5, y+0.5) for x, y in zip(px, py)] # [(1.5, 2.5), (2.5, 7.5) ...]
poly = [p for x in poly for p in x] # [1.5, 2.5, 2.5, 7.5, ...]
x_min, y_min, x_max, y_max = (min(px), min(py), max(px), max(py))
annot = dict(
image_id = idx,
id = obj_cnt,
category_id = 0,
bbox = [x_min, y_min, x_max-x_min, y_max-y_min],
area = (x_max - x_min) * (y_max - y_min),
segmentation = [poly],
iscrowd=0,
)
annots.append(annot)
obj_cnt += 1
coco = dict(
images=images,
annotations=annots,
categories=[{"id": 0, "name": "balloon"}], # 단일 Class
)
mmengine.dump(coco, out_file) # COCO 포맷 파일 저장
TRAIN_PATH = "balloondataset/balloon/train"
VAL_PATH = "balloondataset/balloon/val"
# Train 데이터 변환
COCOConverter(
os.path.join(MMDET_PATH, TRAIN_PATH, "via_region_data.json"),
os.path.join(MMDET_PATH, TRAIN_PATH, "annotation_coco.json"),
os.path.join(MMDET_PATH, TRAIN_PATH, ""),
)
# Val 데이터 변환
COCOConverter(
os.path.join(MMDET_PATH, VAL_PATH, "via_region_data.json"),
os.path.join(MMDET_PATH, VAL_PATH, "annotation_coco.json"),
os.path.join(MMDET_PATH, VAL_PATH, ""),
)
mim download mmdet --config faster-rcnn_r50_fpn_1x_coco --dest ./checkpoints
from mmengine import Config
# config 파일 로드
cfg = Config.fromfile(os.path.join(MMDET_PATH, "checkpoints/faster-rcnn_r50_fpn_1x_coco.py"))
from mmengine.runner import set_random_seed
# Class 정보 수정
cfg.metainfo = {
"classes": ("balloon",), # 단일 Class
"palette": [(220, 20, 60),], # Class 수와 일치
}
cfg.model.roi_head.bbox_head.num_classes = 1
# 데이터셋 정보 수정
cfg.data_root = os.path.join(MMDET_PATH, "balloondataset/balloon")
cfg.train_dataloader.dataset.ann_file = "train/annotation_coco.json" # data_root 이하
cfg.train_dataloader.dataset.data_root = cfg.data_root
cfg.train_dataloader.dataset.data_prefix.img = "train/"
cfg.train_dataloader.dataset.metainfo = cfg.metainfo
cfg.val_dataloader.dataset.ann_file = "val/annotation_coco.json"
cfg.val_dataloader.dataset.data_root = cfg.data_root
cfg.val_dataloader.dataset.data_prefix.img = "val/"
cfg.val_dataloader.dataset.metainfo = cfg.metainfo
cfg.test_dataloader = cfg.val_dataloader
# Evaluator 수정
cfg.val_evaluator.ann_file = cfg.data_root + "/" + "val/annotation_coco.json"
cfg.test_evaluator = cfg.val_evaluator
# 모델 경로 수정
cfg.load_from = "checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth"
# 학습결과 저장 경로 수정
cfg.work_dir = os.path.join(MMDET_PATH, "work_dir/balloon/objD")
# Val 인터벌 설정 (3 epoch 마다 validation 실시)
cfg.train_cfg.val_interval = 3
cfg.default_hooks.checkpoint.interval = 3
# Logging 인터벌 설정
cfg.default_hooks.logger.interval = 10
# Learning Rate 설정
cfg.optim_wrapper.optimizer.lr = 0.02 / 8
# Seed 설정
cfg.seed = 0
# Visualizer 설정
cfg.visualizer.vis_backends.append({"type": "TensorboardVisBackend"})
# 수정 config 저장
with open(os.path.join(MMDET_PATH, "checkpoints/balloon_objD.py"), "w") as f:
f.write(cfg.pretty_text)
python tools/train.py ./checkpoints/balloon_objD.py
# (앞부분 생략)
# Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=1000 ] = 0.808
# Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=1000 ] = 0.000
# Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=1000 ] = 0.775
# Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=1000 ] = 0.864
# 01/16 14:44:57 - mmengine - INFO - bbox_mAP_copypaste: 0.760 0.881 0.865 0.000 0.639 0.830
# 01/16 14:44:57 - mmengine - INFO - Epoch(val) [12][13/13] coco/bbox_mAP: 0.7600 coco/bbox_mAP_50: 0.8810 coco/bbox_mAP_75: 0.8650 coco/bbox_mAP_s: 0.0000 coco/bbox_mAP_m: 0.6390 coco/bbox_mAP_l: 0.8300 data_time: 0.0067 time: 0.0685
import tensorboard
%reload_ext tensorboard
%tensorboard --logdir "/workspace/mmdetection/work_dir/balloon/objD/20250116_144309"
import mmcv
from mmdet.apis import init_detector, inference_detector
import random
# Val 데이터셋 중 샘플 이미지 1개 추출
img_list = [img for img in os.listdir(os.path.join(MMDET_PATH, VAL_PATH)) if img.endswith("jpg")]
samp = random.sample(img_list, k=1)[0]
print(samp) # 3825919971_93fb1ec581_b.jpg
img = mmcv.imread(os.path.join(MMDET_PATH, VAL_PATH, samp), channel_order="rgb")
checkpoint_file = os.path.join(MMDET_PATH, "./work_dir/balloon/objD/epoch_12.pth")
# 모델 생성
model = init_detector(cfg, checkpoint_file, device="cuda:0")
# 추론 실시
pred = inference_detector(model, img)
from mmdet.visualization import DetLocalVisualizer
visualizer = DetLocalVisualizer()
visualizer.add_datasample(
'result',
img,
pred,
show=False
)
visualizer.show()
*이 글은 제로베이스 데이터 취업 스쿨의 강의 자료 일부를 발췌하여 작성되었습니다.