경구약제 이미지 객체 검출
경구약제 이미지에서 약제를 검출하는 부트캠프 팀 프로젝트. 데이터 전처리와 YOLOv8m 학습·실험을 담당했습니다.
Repository팀 공동 저장소
malit9999/Oral-Medication-Project(새 탭에서 열림)여러 명이 함께 만든 저장소입니다. 제가 맡은 범위는 아래 Role·Scope에 있습니다.
- Problem
- 73개 클래스가 섞인 경구약제 이미지에서, 인스턴스가 적은 클래스까지 검출해 내는 것
- Decision
- 희소 클래스를 TARGET_COUNT=62까지 복제 오버샘플링하고, 색상·회전·반전 증강은 끈 채 mosaic·mixup만 사용
- Evidence
- Kaggle 제출 점수 mAP@[0.75:0.95] 0.99414 (최종 v9) — 부트캠프 팀 프로젝트 결과
- Role
- 데이터 전처리 및 YOLOv8m 학습·실험
- Scope
- 이미지 데이터 전처리와 오버샘플링, YOLOv8m 학습·실험 비교, 베스트 모델 산출
문제 정의
경구약제 이미지에서 개별 약제의 위치와 종류를 찾아내는 객체 검출 과제입니다. 저장소의 전처리 노트북 출력 기준으로 클래스 73개 · 이미지 1,489장 · 어노테이션 4,526개 규모였고, 클래스마다 인스턴스 수가 고르지 않아 적게 등장하는 약제를 어떻게 학습시킬지가 전처리의 쟁점이었습니다.
담당 범위
부트캠프 팀 프로젝트이고, 저장소에서 제 커밋으로 확인되는 담당 범위는 다음과 같습니다.
- 데이터 전처리와 오버샘플링 (
notebooks/oral_medication.ipynb) - YOLOv8m 학습과 실험 (
notebooks/YOLOv8m_best.ipynb) - 학습 결과 중 베스트 모델 산출
아래 설명은 이 두 노트북에서 확인되는 내용만 담았습니다. 팀의 다른 파트는 다루지 않습니다.
데이터 전처리
분할 — MultilabelStratifiedShuffleSplit으로 나눴습니다. 한 이미지에 여러 클래스가
함께 등장하기 때문에, 단순 무작위 분할로는 학습·검증 세트의 클래스 분포가 어긋납니다.
오버샘플링 — 인스턴스가 TARGET_COUNT = 62에 못 미치는 클래스를 대상으로, 그 클래스가
들어 있는 이미지를 어노테이션째 복제했습니다. 복제할 때 이미지 안의 모든 클래스 카운터를
함께 올려서 이미 충분한 클래스가 과도하게 늘어나지 않도록 멈춤 조건을 뒀습니다.
결과는 노트북 출력에 남아 있습니다 — 학습 세트 1,182장 → 2,547장, 추가된 이미지 1,365장과 어노테이션 4,247개.
포맷 변환 — COCO 형식의 bbox를 YOLO 형식으로 바꾸는 coco_to_yolo를 거쳐
data.yaml과 라벨 파일을 생성했습니다.
학습 설정
# notebooks/YOLOv8m_best.ipynb — 저장소에서 확인되는 YOLOv8m 학습 설정
results = model.train(
data = YOLO_YAML_PATH,
imgsz = 1280,
rect = True,
epochs = 50,
batch = 16,
optimizer = "AdamW",
lr0 = 0.001,
lrf = 0.01,
weight_decay = 0.0005,
warmup_epochs= 3,
mosaic = 1.0,
mixup = 0.1,
hsv_h = 0.0, # 색상 증강 끔
hsv_s = 0.0,
hsv_v = 0.0,
degrees = 0.0, # 회전 끔
fliplr = 0.0, # 좌우 반전 끔
flipud = 0.0,
patience = 20,
)
imgsz=1280에 rect=True로 원본 비율을 유지하고, 색상·회전·반전 증강은 모두 0으로 끈 채
mosaic과 mixup만 남긴 구성입니다. 저장소의 학습 노트북에서 확인되는 설정은 여기까지입니다.
결과
Kaggle 제출 점수 0.99414 mAP@[0.75:0.95] 기준 · 부트캠프 팀 프로젝트 최종 결과 (v9)
중간 버전 v3의 Kaggle 제출 점수는 0.99109였고, 최종 v9에서 0.99414가 됐습니다. 두 값 모두 Kaggle 제출 점수이며 로컬 검증 점수와는 평가 조건이 달라 함께 두지 않았습니다. 최종 점수는 팀 전체의 결과입니다 — 아래에서 제 노트북으로 확인되는 구간과 그렇지 않은 구간을 나눠 적었습니다.
v3에서 v9까지 — 기록으로 복원한 구간
위에 연결한 저장소는 제 포크입니다. 루트에 팀 최종 노트북(oral_medication_final.ipynb)이 있고,
제가 맡은 노트북 두 개는 notebooks/에 있습니다. 제출 CSV와 버전별 노트북은
팀 원본 저장소(tpfud126/Oral-Medication-Image-Object-Detection-Project)의
submission · Model-Comparison 브랜치에 31개와 함께 남아 있습니다.
파일 해시, 노트북 안의 경로 변수, 최종 노트북의 기록으로 되짚을 수 있는 만큼만 적었습니다.
| 구간 | 저장소에서 확인되는 변경 | Kaggle mAP@[0.75:0.95] | 근거 |
|---|---|---|---|
| v3 | YOLOv8m 학습 설정 — imgsz=1280, rect=True, 색상·회전·반전 증강 끔 | 0.99109 | 제 notebooks/YOLOv8m_best.ipynb가 Model-Comparison 브랜치 pre_code_v3_YOLOv8m.ipynb와 SHA-256 동일 |
| v4 | 희소 클래스 복제 오버샘플링 추가 (TARGET_COUNT=62) | 0.98288 | 설계는 제 notebooks/oral_medication.ipynb 셀 [3-1]·[12]. 점수는 팀 최종 노트북 §6-1 기록이며, 그 노트북의 v4 검출 수 3,195건이 0326_v4_over.csv와 일치 |
| v5 | 오버샘플링 + 온라인 증강 | 0.92666 | 팀 최종 노트북 §6-1 기록. 제출 파일명 0326_v5_over&augm.csv — 노트북에 검출 수가 없어 파일과는 이름으로만 연결 |
| v6 | 오프라인 증강 (HSV + 회전 10°) | 미제출 | 팀 최종 노트북 §6-1 기록. submission 브랜치의 v6 CSV는 21건짜리 부분 출력 |
| v7 | 오프라인 증강 (회전 3%) | 0.97857 | 팀 최종 노트북 §6-1 기록, 검출 수 3,160건이 steve_submission_v7.csv와 일치 |
| tm_final | v7 파이프라인 + 팀원 설정 | 0.91987 | 팀 최종 노트북 §6-1 기록, 검출 수 2,985건이 0330_submission_data& model_final.csv와 일치 |
| 별도 계열 | 앙상블 · TTA+WBF · ResNet-50 · Faster R-CNN 비교 | 기록 없음 | submission_v4_ensemble.csv · submission_v4_tta_wbf.csv · Model-Comparison 브랜치 노트북 |
| v9 | 최종 제출 | 0.99414 | submission_v9.csv가 다른 팀원 이름이 붙은 2026-03-30 제출본과 SHA-256 동일 |
v3와 v9 점수는 팀이 보관한 Kaggle 제출 기록에서, v4·v5·v7·tm_final 점수는 팀 최종 노트북 §6-1에서
확인했습니다. 최종 노트북의 검출 수를 제출 CSV와 대조해 v3·v4·v7·tm_final은 어느 파일의
점수인지까지 맞췄습니다. 다만 0326_v4_over.csv는 다른 팀원 이름이 붙은 v4 제출본과
SHA-256이 같아서, v4 점수가 제 실행본의 점수라고는 적지 않았습니다 — 오버샘플링 설계가
제 노트북에서 왔다는 것까지가 확인되는 범위입니다.
v9는 최종 노트북에 나오지 않습니다. 최종 노트북은 tm_final(검출 2,985건)을 최종 제출로 적고
있고, v9(submission_v9.csv, 검출 3,220건)는 다른 파일입니다. 두 계열을 하나의 순서로 잇지
않았고, 향상폭을 특정 변경 하나에 돌리지도 않았습니다.
제출 CSV를 직접 세어보면 테스트 이미지 843장 · 클래스 73개는 전 구간 같고, 검출 건수만 v3 3,210건 → v9 3,220건으로 움직였습니다. 이 수치는 파일에서 집계한 값이며 점수와는 다른 축입니다.
버전 번호는 팀원별로 따로 붙었습니다. 같은 브랜치의 pre_code_v4_YOLOv8m.ipynb는
증강을 다시 켠 구성(hsv 0.015/0.5/0.4, degrees=100, fliplr·flipud 0.5, epochs=30)이라
제 v4(오버샘플링)와 다른 계열입니다. 하나의 연속된 번호로 읽으면 안 됩니다.
한계와 남은 기록
- 기록으로 복원되지 않는 구간 — v9가 어떤 구성에서 나왔는지, 앙상블·TTA가 v9에 들어갔는지는 저장소에 남아 있지 않습니다. v4 제출본을 누가 실행했는지도 파일만으로는 가릴 수 없습니다. 확인되면 위 표를 채우겠습니다.
- 저장소에 공개된 노트북의 로컬 검증 지표는 별도 실험의 값이라 대표 성과로 쓰지 않았습니다.
- 팀 공동 저장소이며, 이 페이지에서는 제 커밋과 담당 범위로 확인되는 내용만 개인 기여로 서술했습니다. 최종 v9 제출본은 제 노트북 산출물이 아닙니다.
다음에 같은 프로젝트를 한다면
점수를 되짚기 어려웠던 이유는 단순합니다 — 제출 파일과 점수를 잇는 기록이 없었습니다. v4·v5·v7 점수는 나중에 팀 최종 노트북에서 찾았지만, 어느 파일의 점수인지는 검출 수를 세어 간접으로 맞출 수밖에 없었습니다. 제출할 때 한 줄씩만 남겨도 위 표가 통째로 채워집니다.
# experiments.csv — 제출 1건 = 1행
version,date,submitted_file,sha256_8,base_of,changed,score_metric,score,author,note
v3,2026-03-24,submission_v3_0324.csv,2d2a077b,,YOLOv8m 기본 설정,Kaggle mAP@[0.75:0.95],0.99109,,증강 off
v4,2026-03-26,0326_v4_over.csv,8db6cadb,v3,오버샘플링 TARGET_COUNT=62,Kaggle mAP@[0.75:0.95],0.98288,,
v5,2026-03-26,0326_v5_over&augm.csv,f089285e,v4,+ 온라인 증강,Kaggle mAP@[0.75:0.95],0.92666,,
base_of(무엇에서 갈라졌는지)와 changed(한 번에 바꾼 것 하나)만 지켜도 향상폭을 원인에
연결할 수 있습니다. author가 있으면 팀 결과와 개인 기여를 나중에 나눌 수 있고, 점수 칸이
비면 그 구간은 비교에서 빼면 됩니다. 위 세 행은 저장소와 팀 최종 노트북에서 복원한 값으로
채운 것이고, 비어 있는 author 칸이 곧 끝내 복원되지 않은 부분입니다.