프로젝트 개요
● 4인 팀을 구성해 약 2주간 진행한 팀 프로젝트다. 주제는 알약 이미지 객체 탐지 — 스마트폰으로 알약을 촬영하면 YOLO 모델이 약을 인식하고, 식약처 API를 통해 효능·복용법·주의사항을 보여주는 앱을 만드는 것이었다.
● 단순히 모델 학습으로 끝내는 게 아니라, 실제로 사용자가 쓸 수 있는 앱까지 완성하는 것을 목표로 잡았다. 결과적으로 mAP@0.5 0.9914, F1 Score 0.9948의 모델을 학습시키고, FastAPI 서버와 Flutter 앱을 연결해 실제로 동작하는 서비스를 완성했다.
내 역할 : PM도 하고 개발도 하고
● 프로젝트에서 PM을 맡았다. 처음엔 일정 관리와 업무 분배에 집중했지만, 실제론 매일 직접 코딩도 했다. EDA팀과 모델팀으로 분업 구조를 짜고, 하루에 세 번 브리핑하면서 팀원들에게 업무를 나눠줬다. 동시에 RetinaNET 구현, YOLO 실험, FastAPI 서버 개발, Flutter 앱 화면 구현까지 직접 수행했다.
● PM으로서 가장 어려웠던 건 코드보다 사람 관리였다. 진행 속도가 다른 팀원에게 즉석에서 새 업무를 찾아주는 일, 한 분이 중간에 개인 프로젝트로 이탈하셨을 때 공백을 메우는 일, 코드를 업데이트했는데도 pull하지 않고 실험을 돌리는 분께 매번 안내하는 일까지 ... 사람 사이의 문제라 코딩보다 훨씬 소모적이었다.
기술 선택: 5개 모델 만들어보고 결정(결국 YOLO다)
● 프로젝트 초반, 팀원이 한 명씩 모델을 맡아 실험하는 방식으로 비교했다. YOLO, SSD, Faster R-CNN, DETR, RetinaNET — 5가지를 동일한 조건에서 직접 구현하고 성능을 비교했다.
● 결론은 YOLO가 압도적이었다. DETR은 수렴하려면 수백 epoch이 필요했고, RetinaNET과 Faster R-CNN은 성능 자체가 낮았다. 알약처럼 작은 객체를 탐지하면서 실시간 앱에서 써야 하니 추론 속도도 중요했는데, YOLOv11s가 성능과 속도 모두 가장 좋았다.
● 흥미로운 발견이 하나 있었다. 더 큰 모델이 낫겠지 싶어서 YOLOv11m도 실험해봤는데, 오히려 11s가 더 높은 지표를 냈다. 데이터셋 규모에 비해 모델이 크면 오히려 작은 객체의 디테일을 잘 못 잡는다는 것을 직접 확인한 경험이었다.
데이터 통합 과정에서 만난 연속 오류들
● 프로젝트에서 가장 힘들었던 부분은 추가 데이터 통합이었다. AI Hub에서 경구약제 이미지 추가 데이터를 받아 기존 데이터셋에 합치는 작업이었는데, 예상치 못한 오류가 연달아 터졌다.
JSON 파일명 충돌
● 조합 이미지 1개에 약별 JSON이 4개씩 딸려 있는 구조였는데, 파일명이 같아서 저장할 때마다 덮어씌워졌다. 결과적으로 이미지마다 bbox가 1개만 잡혔는데, 이 문제가 있다는 것 자체를 파악하는 데 시간이 한참 걸렸다. 서브디렉터리 구조를 유지하면서 저장하도록 로직을 고쳐서 해결했다.
category_id 매핑 오류 — 0.12에서 0.987로
● 이게 가장 치명적이었다. 제출 파일을 만들 때 원래 category_id 대신 item_seq(식약처 9자리 코드)를 그대로 써버렸는데, 채점 결과가 0.12가 나왔다. 처음엔 모델 문제인 줄 알고 한동안 다른 데서 원인을 찾았다. 매핑 테이블을 뜯어보다 원인을 발견했고, 원본 category_id 기준으로 매핑을 수정한 뒤 0.987까지 올랐다. 숫자 하나 잘못 쓴 게 점수 0.87의 차이를 만든 경험이었다.
추가 클래스 KeyError
● 추가 데이터에만 있는 클래스가 원본 어노테이션에 없다 보니, 매핑 생성 시 KeyError가 계속 발생했다. 생각해보면 당연한 일인데 미처 예상 못했다. 매핑에 없는 클래스는 skip하는 방식으로 처리했다.
앱 개발: FastAPI + Flutter 연결
● 모델이 완성된 뒤 실제 앱으로 만드는 작업에 들어갔다. FastAPI와 Flutter 모두 이번에 처음 써봤는데, 2주라는 시간 안에 서버부터 앱까지 완성해야 했기 때문에 빠르게 익히면서 진행했다.
● FastAPI로 /predict 엔드포인트를 만들어서, 이미지를 받으면 YOLO 추론 → bbox 크롭 → 식약처 API 조회까지 한 번에 처리하도록 했다. 여러 알약을 동시에 인식할 수 있도록 API 호출은 ThreadPoolExecutor로 병렬 처리했다.
● 약 정보를 가져오는 과정에도 예상 못한 일이 있었다. 처음엔 e약은요 API만 쓰려 했는데, 이게 일반의약품 위주라서 우리 데이터셋에 있는 전문의약품 정보가 거의 없었다. 그래서 식약처 제품허가정보 API를 1차로 조회하고, 정보가 없으면 e약은요 API로 보완하는 폴백 구조로 바꿨다.
● 팀원이 Figma로 디자인을 만들어와서 그걸 구현하면서 감을 잡아갔다. 홈, 결과, 약 목록, 복약 알림 4개 탭 구조로 완성했다.



회고
● 대학 시절 이후로 팀 프로젝트는 정말 오랜만이었다. 그래서인지 앱이 실제로 돌아가는 걸 확인하는 순간, 그만큼 후련하고 뿌듯할 수가 없었다. 3주가 어떻게 지나갔는지도 모르겠다.
● PM도 처음이었는데, 막상 해보니 단순히 일정 관리가 아니었다. 막혀 있는 곳을 찾아내고, 중간에서 연결하고, 해결해주는 역할이었다. 매일 아침저녁으로 이 사람한테는 어떤 업무를 줘야 할지 생각하면서 다녔고, 책임감 때문인지 쉬는 날에도 불안해서 컴퓨터를 켜 오류를 고치곤 했다. 그만큼 고민하고 부딪혔던 만큼 뿌듯함도 컸다. 개발에서 느끼는 기쁨이 이런 거구나 싶었다.
● 기술적으로는 두 가지가 기억에 남는다. 하나는 데이터셋 크기에 맞는 모델 선택의 중요성, 다른 하나는 데이터 파이프라인에서 매핑 하나를 잘못 짰을 때 얼마나 큰 차이가 생기는지다. category_id 이슈는 앞으로 데이터 파이프라인을 짤 때 제일 먼저 검증하게 될 부분으로 남았다.
● 3주만에 모델 선정, 학습 앱 배포까지 해본 프로젝트였는데, 많은걸 느끼고 배웠다. 다음에 PM을 맡게 된다면 이번보다는 훨씬 여유 있게 할 수 있을 것 같다. 앞으로 어떤 프로젝트든 이런 뿌듯함을 느낄 수 있다면 얼마든지 재밌게 임할 수 있을 것 같다.