막막함 속에서 시작된 첫걸음

딥러닝 기반 이미지 객체 탐지 분야로 첫 프로젝트를 시작하게 됐습니다.

처음엔 막막함 그 자체였습니다. 어디서부터 시작해야 할지, 어떤 모델을 써야 할지, 하나부터 열까지 전부 낯설고 두려웠습니다. 이 글에는 그 막막함을 시작으로 얽힌 매듭을 하나씩 풀어가며 문제를 정리한 과정이 담겨있습니다.

혹시나 저처럼 어떻게 시작해야할지 몰라서 막막한 분들이 있다면 도움이 되는 글이었으면 좋겠습니다. 진지하게 읽어주셔도 좋고, 가볍게 재미로 흘려보셔도 괜찮습니다.

데이터 이전에, 맥락부터

가장 먼저 마주한 건 이 프로젝트가 다루는 “경구 약제”라는 특수한 도메인이었습니다. 단순히 모델을 돌리기보다는 이 도메인을 제대로 이해하고, 데이터가 담고 있는 특성을 먼저 파악하는 것이 우선이라는 생각이 들었습니다.

이미지 처리나 객체 탐지에 대한 전문적인 딥러닝 지식이 부족했고, 어떤 모델이 적합할지조차 명확하지 않은 상태였습니다. 그렇기 때문에 초기 전략을 비슷한 도메인과 유사한 태스크를 어떻게 접근했는지를 먼저 탐색하고 이해하는 데에 집중하는 것으로 잡았습니다.

첫째로, 다양한 객체 탐지 모델을 리서치하고, 각각의 모델이 어떤 구조적 특성과 장단점을 갖는지 비교 분석 했습니다. 특히 이번 프로젝트에 사용할 Google Colab T4 환경에서 실험 가능한 자원과 시간 제약을 고려해 현실적으로 여러 번 실험할 수 있는 가벼운 구조인지, 성능을 위해 어느 정도 리소스를 투자해야 하는 모델인지 등을 검토해봤습니다.

또한, 실제로 경구 약제와 유사한 도메인에서 객체 탐지를 진행한 사례들을 찾아보고자 했습니다. 어떤 기준으로 모델을 선택했는지, 중요하게 본 요소는 무엇이고 어떤 이슈가 있었는지, 정확도를 위해 어떤 전략을 사용했는지를 중심으로 봤습니다.

최고의 레시피를 찾아

모델 리서치하는 과정에서 다양한 논문을 참고했습니다. 가장 먼저 대표적인 one-stage detection 방식인 YOLO 시리즈[10][11]와, two-stage detection 방식의 Faster R-CNN[12] 같은 object detection 모델들을 중심으로 살펴보며 구조적 차이와 성능 특성을 비교했습니다. 그리고 약제와 같은 특수 도메인에서 활용된 fine-grained classification, OCR 기반 인식, prompt tuning, 멀티모달 융합 등 다양한 방식들을 제안한 논문들까지 추가로 참고했습니다.

One-state detection 모델은 이미지를 한 번에 처리해 객체의 위치와 클래스를 동시에 예측하는 방식으로 YOLO 시리즈와 SSD가 있습니다. 빠르고 가벼워서 실시간 처리에 적합하다는 장점이 있습니다.

Two-stage detection 모델은 이미지 내에서 Region Proposal을 뽑고 그 영역에 대해 정밀한 분류와 박스 조정을 수행하는 방식입니다. Faster R-CNN이 그 예이며 정확도는 높지만 연산량이 많고 속도가 느려 실시간 처리에는 적합하지 않습니다.

추가로 본 논문 중 가장 유의 깊게 봤던 건 OCR 기반 접근 [7]이었습니다. 약제에 새겨진 각인을 탐지하고 인식하는 구조가 생각보다 정교하게 발전했다는 걸 알았고 유사한 약제에 대해 각인 정보를 추가로 조합하면 좋은 예측 성능을 낼 수 있을 거라고 생각했습니다.

ViT 기반의 Visual Prompt Tuning 논문 [8]에서는 데이터가 부족한 클래스에 대해 few-shot 방식으로 대처합니다. 클래스가 많고 데이터가 한정적일수록 클래스 불균형이 더 심할거라고 생각해서 불균형 해소 방법을 염두해두고 있었지만 oversampling이나 class-weight 외에 few-shot 방식으로 해결할 수도 있다는 걸 처음 알았습니다.

Multimodal Pill ID 리뷰 [9]에서는 단순히 이미지를 넣는 방식이 아니라 색상·형태·각인 정보를 각각 따로 처리하고 최종적으로 종합 판단하는 방식이 소개됐습니다. 이미 데이터셋을 다 본 지금 관점에서 보자면, JSON 파일에 이러한 정보들이 세분화되어 담겨있었기 때문에 좋은 실험 방법이었다고 생각됩니다.

그 외에도 Pill-ID, Fine-grained Attenion 기반 모델 [5] 등을 참고했지만 처음 접하는 내용들이 너무나도 많았기 때문에(Transformer 등) ‘경구 약제 객체 탐지 task를 실험할 수 있는 방법이 정말 무궁무진하구나’라고만 생각하고 가볍게 봤습니다. 몇 없는 지식으로 여러 정보를 머릿속에 담자니 더 보다간 과부화만 올 것 같았습니다.

이 외로 비슷한 테스크의 자료들을 참고했습니다. 여러 참고 자료들이 있었고 YOLO 기반 객체 탐지에 OCR과 분류 모델을 결합한 파이프라인 [1], AI Hub에서 제공하는 ResNet 기반 Baseline [2]등 저희 테스크에 맞는 자료들도 존재했습니다. 하지만 세부적으로 어떤 실험을 거쳤고 왜 최종 모델로 선정했는지 등은 알 수 없어 참고용으로만 확인했습니다. [3][4]

최종적으로 저희 팀은 YOLOv8n를 실험의 baseline으로 선택했습니다. 처음에는 구조가 가볍고 실험 반복하기 적합하다는 점에서 YOLOv8n를 기준으로 삼았고 이후에는 성능 향상과 최신 구조 반영을 위해 YOLOv11을 실험했습니다. 또한 비교군으로 Faster R-CNN, YOLO + OCR 조합, ResNet + YOLOv8 연계 구조도 함께 테스트했습니다.

본격적으로

저희 팀의 프로젝트 목표는 두 가지였습니다. 첫째, 이 테스크 전용 Kaggle 리더보드에서 가능한 한 높은 점수를 기록하는 것. 둘째, 실제 경구 약제를 정확하게 탐지하고 분류할 수 있는 모델을 만드는 것이었습니다.

이 두 가지를 위해 실험 전략을 짰습니다. 먼저, 다양한 객체 탐지 모델을 폭넓게 실험해본 뒤, 가장 성능이 우수한 모델을 선정합니다. 그리고 해당 모델의 구조를 분석해 성능을 더 끌어올릴 수 있는 모든 요소(하이퍼파라미터, 증강, 학습 기법, 후처리 방식 등)을 적극적으로 조정하고 개선했습니다.

시행착오를 겪으며

하지만 실험은 항상 뜻대로 흘러가지 않았습니다. 프로젝트는 2주 간 진행됐고, 많은 시행착오가 발생했습니다.

  1. 데이터 정제에 소요된 과도한 시간
    초기 데이터셋에는 이미지 누락, 바운딩 박스 오류, 라벨 누락 등 여러 문제가 있었고, 수작업으로 보완하며 JSON 파일을 새로 생성하는 데만 약 2일이 소요됐습니다. 특히 약제의 앞면과 뒷면이 동일한 클래스로 처리되어 있어 시각적으로는 완전히 다른 두 이미지를 같은 클래스로 다뤄야 한다는 문제는 끝내 해결하지 못했습니다. 프로젝트가 끝날 무렵 AI Hub에서 누락된 이미지를 포함한 데이터셋을 다시 받을 수 있다는 걸 알아 이 또한 진행했지만 라벨 오류를 정제하는 데 여전히 많은 시간을 쏟아야 했습니다.

    바운딩 박스 오류 예시 ↑

    클래스 오분류 예시 ↑

  2. 다양한 실험에도 불변하는 성능
    데이터셋을 정제한 후 여러 모델(YOLOv8/v11, Faster R-CNN)을 실험한 결과, Kaggle 리더보드에는 거의 동일한 점수(0.99532)가 반복됐습니다. 이를 해결하기 위해 다양한 방식의 augmentation을 시도했습니다. 모듈을 통한 강한 증강/약한 증강, 알약 누끼를 따서 검은 배경 위에 유사 알약을 배치한 synthetic 이미지 생성, 오분류 사례 중심의 배경 포토샵 자동화, crop & collage 방식 등. 학습 구조도 크기(s/m/l)와 loss function(focal loss), 하이퍼파라미터까지 다양하게 조정했지만 점수는 변하지 않았습니다.

    synthetic 이미지 생성 예시 ↑

    강한 증강 예시 ↑
    .
    원인을 찾기 위해 여러 방법을 시도해보다가 훈련 이미지와 테스트 이미지 간의 유사도 분석을 해봤습니다. SSIM을 사용해 두 이미지 간의 시각적 유사도를 비교했고 그 결과 전체 테스트셋의 80% 이상이 SSIM 1.0으로 훈련 이미지와 일치한다는 사실을 발견했습니다. 결국 나중에서야 데이터 누수 문제가 근본 원인이라는 것을 알게 됐습니다. 또한 테스트셋 내 일부 이미지에는 라벨 오류까지 포함돼 있었기 때문에, 점수 자체가 실제 모델의 일반화 성능을 반영하지 못한다는 문제도 있었습니다.

  3. 증강이 성능을 떨어뜨렸던 이유
    augmentation 강도를 높일수록 오히려 성능이 저하되는 현상도 겪었습니다. 처음에는 일반화 실패라고 생각했지만, 실제로는 데이터 누수와 테스트셋 특성이 원인이었습니다. 훈련 데이터와 테스트 데이터가 대부분 동일한 환경(조명, 배경, 각도 등)에서 촬영된 유사한 이미지들이었기 때문에 훈련 이미지를 과하게 변형하면 오히려 테스트셋과 괴리감이 생겨 성능이 떨어졌습니다. 결국 이 프로젝트에서는 일반화보다 ‘훈련셋과 최대한 유사한 이미지’를 그대로 보여주는 쪽이 더 높은 점수를 내는 구조였고, 증강이 실제로 도움이 되지 않았던 셈입니다.

  4. 사소한 실수가 성능 저하로 이어짐
    모델 학습 시 원본 이미지의 입력 해상도를 설정할 때 (height, width) 대신 (width, height)로 잘못 기입하여 성능이 저하되는 실수도 있었습니다.

  5. 클래스 불균형 문제의 한계
    전체 클래스 수는 많은 반면 클래스별 이미지 수가 매우 적어 불균형 문제가 심각했습니다. 한 이미지에 여러 알약이 담겨 있는 데이터셋 특성상 클래스 균형을 고려한 학습이 구조적으로 어려웠고, train/val 분리도 제대로 수행하지 못했습니다. 임의로 증강한 데이터를 validation set으로 사용했습니다.
    클래스 불균형에 대해서는, 오버샘플링이나 focal loss를 적용하는 것, 증강 외에도 rare class에 대해 few-shot 학습 구조나 prototype 기반 분류기를 도입해보거나, class-aware sampling 전략으로 학습 배치를 조정하는 등 여러 시도를 해보면 좋았겠다는 아쉬움이 남는 것 같습니다.

  6. 실험 코드 관리의 어려움
    첫 프로젝트였기에 실험을 반복할수록 코드가 복잡해지고 비효율적으로 정리되지 않는 문제가 발생했습니다. 실험별 버전을 체계적으로 기록하지 못했고, git을 적극적으로 활용하지 않아 비슷한 코드를 계속 반복해서 작성해야 했던 비효율성이 컸습니다. 다음 프로젝트에서는 실험 단위별 버전 관리와 코드 구조화에 더 신경써보려고 합니다.

성능 평가 결과

모델명mAP@50mAP@50-95PrecisionRecallFPS (추론 속도)파라미터 수
YOLOv8n0.9930.9830.9940.99998 FPS3.2M
YOLOv11l0.9930.9800.9930.998약 45 FPS약 25M (추정)
Faster R-CNN0.9870.8070.7501.000약 12 FPS42M

YOLOv8n 모델은 mAP@50 기준 0.993, 추론 속도 98FPS라는 우수한 성능을 보였습니다.

Faster R-CNN은 높은 정확도를 가질 것이라는 기대와는 다르게 mAP@50-95가 0.807로 다소 낮은 성능을 보였습니다. NMS 튜닝 부족, rare class에 대한 적응 실패, 연산량이 많아 충분히 학습을 돌리지 못한 점 등이 복합적으로 작용한 결과라고 판단했습니다. 또한 mAP@50 대비 mAP@50-95가 급격히 낮아지는 건 과적합의 명확한 신호였습니다.

실제로 다양한 실험에서도 과적합 문제가 계속 나타났습니다. focal loss, EMA, label smoothing 등의 학습 기법을 적용해봤고, 다양한 증강 기법도 시도했지만 성능에는 거의 영향을 주지 못했습니다.

OCR 엔진한글 인식률영문 인식률평균 속도 (초)특징
EasyOCR98.3%97.9%1.2GPU 지원, 한글/영문 인식 강점
Tesseract94.7%92.1%2.7CPU 전용, 속도 느림

OCR도 실험의 주요 요소였습니다. EasyOCR과 Tesserack 두 가지 엔진을 활용해 한글과 영문 인식 성능을 비교해봤고, EasyOCR이 평균 인식률 98% 이상으로 우수한 결과를 보였습니다. 그러나 실제 약제명과의 일치율은 기대에 미치지 못했습니다. 텍스트 인식 자체는 잘 되지만 회전된 이미지, 배경 노이즈, 저조도 환경 등에서 인식률이 급격히 떨어졌고, 약제명은 단순 문자열이 아니라 정제된 형태소 기반 이름이라 후처리 없이 활용하기엔 어려움이 컸습니다.

잘 예측한 예 (BSP)

예측하지 못한 예 (No Text)

잘못 예측한 예 (Noltec |.|)

YOLO와 ResNet을 결합한 two-stage 실험도 진행했습니다. YOLO가 객체를 탐지하고, cropped 이미지를 ResNet18이 분류하는 구조였는데, YOLOv8n 대비 약 0.0065의 성능 상승이 있었고, cascade 방식에 비해 연산량이 적고 효율적이었습니다. 하지만 성능 변화가 미미했고, 이후 소수 클래스에 대한 보완을 임베딩 기반이나 트랜스포머 계열 분류기로 확장해보았다면 더 좋은 실험 방법이 됐을 것 같습니다.

하지만 주어진 테스트셋의 심각한 누수 문제로 인해 모든 수치 성능은 일반화 적용에 신뢰하기 어려운 면이 있었습니다. 그래서 라벨이 없는 외부 이미지 12장을 대상으로 정성 평가를 진행했습니다. 그 결과, 강한 증강으로 학습한 대형 모델인 YOLOv11이 더 많은 객체를 정확히 탐지했고, 약한 증강만 한 작은 모델보다 일반화 성능이 좋다는 걸 있었습니다.

YOLOv11의 예측 잘된 예 (출처: 약학정보원)

마치며

첫 프로젝트를 진행하면서는 ‘아 이번 프로젝트 망했네. 어떻게 수습할까?’라는 생각 뿐이었습니다. 하나도 내 뜻대로 되는 게 없었고, 순조롭게 흘러가지 않았고, 결과도 엉망인 것처럼 느껴졌습니다. 하지만 되돌아보니 그만큼 많이 배우고 성장한 시간이었습니다.

무엇보다 가장 어려웠던 점은 실험을 반복하는 과정에서 Git이나 폴더 구조를 체계적으로 정리하지 못했던 점입니다. 실험을 거듭할수록 코드는 점점 복잡해지는데, 나중에 어떤 코드가 어떤 실험을 위한 것인지 혼란스러워졌습니다. 구조화된 실험 관리의 중요성을 절실히 느꼈습니다.

그리고 두 번째로 어려웠던 건, 어떤 시도가 성능에 어떤 영향을 주는지에 대한 판단이 부족했다는 점입니다. 다양한 요소를 건드려보긴 했지만, 무엇이 진짜 중요한지 감을 잡지 못하고 시간만 빠르게 흘러갔던 순간들이 많았습니다. 특히 점수가 높게 나올 때도 이유를 명확히 파악하지 못한 채 마냥 좋아했던 건 아쉬움으로 남습니다.

세 번째로, 실엄의 정량적 결과를 체계적으로 기록하지 못했던 점입니다. 실험마다 어떤 학습기법을 사용했고 어떤 후처리를 적용했는지에 대한 로그가 누락되거나 불완전한 경우가 많았습니다. 나중에 어떤 방식이 효과적이었는지 되짚어보기 쉽지 않았습니다.

마지막으로, 프로젝트 초반에 설정한 리더보드 점수 높이는 것과 탐지 정확도 극대화하는 두 가지 목표에만 집착하다 보니 모델의 일반화 성능을 고려한 폭넓은 실험은 오히려 놓치게 됐습니다. 다양한 증강 기법이나 모델 조합은 실험했지만, 결과적으로는 리더보드 점수에 영향을 주지 않는 실험은 생략하게 됐고 이게 오히려 실험의 깊이를 얕게 만든 원인이기도 했습니다.

다음 프로젝트에서는 전처리, 학습, 후처리 각 단계마다 중간 점검과 검증 단계를 명확히 두고 Git을 통해 실험 버전을 정리하며 각 실험의 전략과 결과, 적용한 학습기법과 후처리 내용 등을 자세히 기록하고 사전 조사 내용을 실험 설계에 적극적으로 반영하면 좋을 것 같다는 생각을 했습니다.

이번 프로젝트는 성능 좋은 모델을 만드는 경험 뿐만 아니라 하나의 프로젝트를 처음부터 끝까지 어떻게 이끌고 기록하며 마무리할 수 있을지 연습하는 시간이었다고 생각합니다. 매듭 묶인 실을 처음 풀어보니 이렇게도 건들여보고 저렇게도 건들여봐서 실이 엉망진창이 되고 엉성하게 풀렸다는 느낌을 지울 수 없는 다사다난한 첫 프로젝트였지만 ‘뭐라도 하면 풀리긴 풀리는구나..’ 싶었습니다. 이 글을 읽는 분들께 조금이나마 도움이 되었기를 바랍니다. 읽어주셔서 감사합니다.

최종 코드는 아래 GitHub에서 확인하실 수 있습니다.
https://github.com/Team-Epoch-4/Project

참고자료

[1][“뭔약이유?” 경구약 분류 시스템](https://velog.io/@rkdghwjd1999/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%94%A5%EB%9F%AC%EB%8B%9D-%EA%B2%BD%EA%B5%AC-%EC%95%BD%EC%A0%9C-%EB%B6%84%EB%A5%98-%EC%8B%9C%EC%8A%A4%ED%85%9C)

[2][AI Hub – 의약품 이미지 데이터셋 (경구약제)](https://www.aihub.or.kr/aihubdata/data/view.do?dataSetSn=576)

[3][경구약제 분류 및 효능 안내 시스템 (한국로봇학회 논문, 2023)](https://www.rne.or.kr/gnuboard5/bbs/download.php?bo_table=rs_year&wr_id=1923&no=0)

[4][YOLO v8을 이용한 경구약제 분류 및 효능, 부작용 안내 프로그램 개발](https://www.sci-gifted-festival.kr/bbs/board.php?bo_table=pro2&wr_id=105)

[5][Pill-ID: Towards Transparent and Transferable Pill Identification from Images](https://arxiv.org/abs/2103.00295)

[6][Fine-grained Visual Classification via Multi-Feature Embedding and Attention](https://arxiv.org/abs/2011.12546)

[7][OCR + Detection 기반 약제 인식 구조 관련 리뷰: Scene Text Detection and Recognition: The Deep Learning Era](https://arxiv.org/abs/2001.05086)

[8][Visual Prompt Tuning for Pill Recognition in Few-shot Settings](https://arxiv.org/abs/2203.12119)

[9][Multimodal Approaches for Pill Identification using Shape, Color, Imprint and OCR](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5962805/)

[10][YOLOv8 - Ultralytics 공식 문서](https://docs.ultralytics.com/ko/)

[11][YOLOv11]

[12][Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks](https://arxiv.org/abs/1506.01497)

0개의 댓글