Minseong Code Lab
← Projects
Bootcamp Team ProjectComputer Vision

경구약제 이미지 객체 검출

경구약제 이미지에서 약제를 검출하는 부트캠프 팀 프로젝트. 데이터 전처리와 YOLOv8m 학습·실험을 담당했습니다.

게시 2026-03-26업데이트 2026-10-22
PythonPyTorchUltralyticsJupyter

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 학습·실험 비교, 베스트 모델 산출
Contents · 목차
  1. 문제 정의
  2. 담당 범위
  3. 데이터 전처리
  4. 학습 설정
  5. 결과
  6. v3에서 v9까지 — 기록으로 복원한 구간
  7. 한계와 남은 기록
  8. 다음에 같은 프로젝트를 한다면

문제 정의

경구약제 이미지에서 개별 약제의 위치와 종류를 찾아내는 객체 검출 과제입니다. 저장소의 전처리 노트북 출력 기준으로 클래스 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]근거
v3YOLOv8m 학습 설정 — 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_finalv7 파이프라인 + 팀원 설정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.99414submission_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 칸이 곧 끝내 복원되지 않은 부분입니다.