본문 바로가기

Project/졸업프로젝트

화폐 인식 AI 모델 개발기: 8개국 87종 화폐를 구분하는 모델을 만들기까지

< 졸업 프로젝트에서 시작된, 진짜 써먹을 수 있는 AI 모델 제작기 >

 

왜 이 프로젝트를 시작했을까?

 

졸업 프로젝트를 진행하며, "외화를 자동으로 인식할 수 있는 AI"가 필요했습니다.
하지만 공개된 데이터셋이나 모델은 대부분 한 나라에만 한정돼 있었습니다. 

그래서 직접, 8개국의 화폐를 구분할 수 있는 AI 모델을 만들기로 했습니다.

 

 

데이터 수집부터 시작

화폐 AI 모델의 시작은 데이터였습니다.
저희가 다룬 국가는 한국인들이 자주 여행가는 나라들인 일본, 대만, 홍콩, 베트남, 호주, 중국, 유럽연합, 미국으로 한정지었습니다. 
먼저 각 나라의 지폐와 동전을 크롤링으로 수집했습니다.

 

 

자동화된 크롤링 파이프라인 구축 」

처음에는 Google 이미지에서 크롤링을 시도했지만, 로봇 차단에 막혔습니다.

Bing 이미지 검색을 사용하니 비교적 자유롭게 크롤링할 수 있었고,
87종의 화폐별로 평균 15~20장씩 총 1,414장을 모았습니다.

 

아래 코드는 빙 크롤링을 위해 사용한 코드입니다.

import os
import time
import urllib.request
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By

# ChromeDriver 경로 설정
driver_path = "./chromedriver.exe"  # chromedriver.exe 경로
service = Service(driver_path)
driver = webdriver.Chrome(service=service)

# 검색어와 폴더 이름 매핑 (나라별로 분류)
search_queries = {
    "japan": {
        # 일본 동전
        "1 yen coin front Japan": "1_yen_coin_japan",
        "5 yen coin front Japan": "5_yen_coin_japan",
        "10 yen coin front Japan": "10_yen_coin_japan",
        "50 yen coin front Japan": "50_yen_coin_japan",
        "100 yen coin front Japan": "100_yen_coin_japan",
        "500 yen coin front Japan": "500_yen_coin_japan",
        # 일본 지폐
        "1000 yen banknote front Japan": "1000_yen_banknote_japan",
        "2000 yen banknote front Japan": "2000_yen_banknote_japan",
        "5000 yen banknote front Japan": "5000_yen_banknote_japan",
        "10000 yen banknote front Japan": "10000_yen_banknote_japan",
    },
    "taiwan": {
        # 대만 동전
        "1 yuan coin front Taiwan": "1_yuan_coin_taiwan",
        "5 yuan coin front Taiwan": "5_yuan_coin_taiwan",
        "10 yuan coin front Taiwan": "10_yuan_coin_taiwan",
        "20 yuan coin front Taiwan": "20_yuan_coin_taiwan",
        "50 yuan coin front Taiwan": "50_yuan_coin_taiwan",
        # 대만 지폐
        "100 yuan banknote front Taiwan": "100_yuan_banknote_taiwan",
        "200 yuan banknote front Taiwan": "200_yuan_banknote_taiwan",
        "500 yuan banknote front Taiwan": "500_yuan_banknote_taiwan",
        "1000 yuan banknote front Taiwan": "1000_yuan_banknote_taiwan",
        "2000 yuan banknote front Taiwan": "2000_yuan_banknote_taiwan",
    },
    "hong_kong": {
        # 홍콩 동전
        "10 cents coin front Hong Kong": "10_cents_coin_hong_kong",
        "20 cents coin front Hong Kong": "20_cents_coin_hong_kong",
        "50 cents coin front Hong Kong": "50_cents_coin_hong_kong",
        "1 dollar coin front Hong Kong": "1_dollar_coin_hong_kong",
        "2 dollars coin front Hong Kong": "2_dollars_coin_hong_kong",
        "5 dollars coin front Hong Kong": "5_dollars_coin_hong_kong",
        "10 dollars coin front Hong Kong": "10_dollars_coin_hong_kong",
        # 홍콩 지폐
        "10 dollars banknote front Hong Kong": "10_dollars_banknote_hong_kong",
        "20 dollars banknote front Hong Kong": "20_dollars_banknote_hong_kong",
        "50 dollars banknote front Hong Kong": "50_dollars_banknote_hong_kong",
        "100 dollars banknote front Hong Kong": "100_dollars_banknote_hong_kong",
        "500 dollars banknote front Hong Kong": "500_dollars_banknote_hong_kong",
        "1000 dollars banknote front Hong Kong": "1000_dollars_banknote_hong_kong",
    },
    "vietnam": {
        # 베트남 동전
        "200 dong coin front Vietnam": "200_dong_coin_vietnam",
        "500 dong coin front Vietnam": "500_dong_coin_vietnam",
        "1000 dong coin front Vietnam": "1000_dong_coin_vietnam",
        "2000 dong coin front Vietnam": "2000_dong_coin_vietnam",
        "5000 dong coin front Vietnam": "5000_dong_coin_vietnam",
        # 베트남 지폐
        "10000 dong banknote front Vietnam": "10000_dong_banknote_vietnam",
        "20000 dong banknote front Vietnam": "20000_dong_banknote_vietnam",
        "50000 dong banknote front Vietnam": "50000_dong_banknote_vietnam",
        "100000 dong banknote front Vietnam": "100000_dong_banknote_vietnam",
        "200000 dong banknote front Vietnam": "200000_dong_banknote_vietnam",
        "500000 dong banknote front Vietnam": "500000_dong_banknote_vietnam",
    },
    "australia": {
        # 호주 동전
        "5 cents coin front Australia": "5_cents_coin_australia",
        "10 cents coin front Australia": "10_cents_coin_australia",
        "20 cents coin front Australia": "20_cents_coin_australia",
        "50 cents coin front Australia": "50_cents_coin_australia",
        "1 dollar coin front Australia": "1_dollar_coin_australia",
        "2 dollars coin front Australia": "2_dollars_coin_australia",
        # 호주 지폐
        "5 dollars banknote front Australia": "5_dollars_banknote_australia",
        "10 dollars banknote front Australia": "10_dollars_banknote_australia",
        "20 dollars banknote front Australia": "20_dollars_banknote_australia",
        "50 dollars banknote front Australia": "50_dollars_banknote_australia",
        "100 dollars banknote front Australia": "100_dollars_banknote_australia",
    }
}

# 기본 저장 폴더 설정
base_dir = "data"  # 최상위 폴더 이름
if not os.path.exists(base_dir):
    os.makedirs(base_dir)

# 이미지 수집 시작
for country, queries in search_queries.items():
    # 나라별 폴더 생성
    country_dir = os.path.join(base_dir, country)
    if not os.path.exists(country_dir):
        os.makedirs(country_dir)

    for query, folder_name in queries.items():
        # 화폐별 폴더 생성
        currency_dir = os.path.join(country_dir, folder_name)
        if not os.path.exists(currency_dir):
            os.makedirs(currency_dir)

        # Bing 이미지 검색
        driver.get(f"https://www.bing.com/images/search?q={query}")
        time.sleep(5)  # 페이지 로딩 대기

        # 썸네일 찾기
        thumbnails = driver.find_elements(By.CSS_SELECTOR, "img.mimg")
        print(f"Found {len(thumbnails)} thumbnails for query: {query}")

        # 썸네일 이미지 다운로드 (최대 20개 다운로드)
        for i, thumbnail in enumerate(thumbnails[:25]):  # 최대 20개로 확장
            try:
                # 이미지 URL 가져오기
                src = thumbnail.get_attribute("src")
                if src and src.startswith("http"):  # 유효한 이미지 URL 확인
                    filename = f"{currency_dir}/{folder_name}_{i}.jpg"
                    urllib.request.urlretrieve(src, filename)
                    print(f"Downloaded: {filename}")
                else:
                    print(f"Skipping invalid or placeholder image: {src}")
            except Exception as e:
                print(f"Error downloading image {i} for {query}: {e}")

# WebDriver 종료
driver.quit()

 

이때 웹 크롤링 코드는 Python Selenium으로 작성했고, 검색어-클래스 매핑을 자동화했습니다.
20장씩 다운로드를 시도했지만 실제 usable 이미지는 약 15장 수준이었습니다.

 

 

레이블링, 예상보다 큰 산이었다

 

YOLO 모델을 사용하려면 바운딩 박스 라벨링이 필요합니다.
처음에는 Roboflow의 AI 라벨링 기능을 시도했지만 정확도가 떨어졌습니다.

예를 들어 아래 이미지처럼 한 사진에 여러 화폐가 있을 경우, 자동 라벨링이 잘 작동하지 않았습니다.

 

 

결국 손수 모든 이미지를 수작업으로 라벨링했습니다.
총 87개 클래스, 1,414장의 이미지에 바운딩 박스를 그려 넣었습니다.
수십 시간을 투자한 반복 작업이었지만, 데이터 품질을 확보하는 데 중요한 단계였습니다.

 

 

모델 학습과 배포

YOLOv8은 최신 객체 탐지 모델로, 적은 데이터에서도 높은 효율을 보여줍니다.
저희는 이를 기반으로 87개 클래스(화폐 단위 및 국가)에 대한 화폐 인식 모델을 직접 학습시켰습니다.

 

모델 정보

  • 초기 모델: yolov8n.pt, 300 epoch, mAP 0.53

초기 실험:
yolov8n.pt로 300 epoch을 학습한 결과 mAP 0.53을 얻었습니다.
빠른 속도는 만족스러웠지만, 복잡한 배경이나 저조도 이미지에서 성능이 아쉬웠습니다.

 

 

  • 최종 모델: yolov8s.pt, 212 epoch, precision 0.7213

yolov8s.pt로 변경 후 212 epoch에서 precision 0.7213을 달성하여 best model로 확정했습니다.
이미지 증강을 병행하며, 특히 회전·왜곡·노이즈에 강한 학습이 되도록 설정했습니다.

 

 

  • 학습 시간: 총 16시간

 

  • 데이터셋 구성:
    • 총 이미지 수: 1,414장
    • 클래스 수: 87개
    • Split 비율: train:val:test = 70:20:10
    • Augmentation: 밝기 변화, Gaussian blur, 수평 반전, rotation 등 적용

 

  • 검증 전략:
    클래스별 precision 분포를 확인하고, 성능이 낮은 클래스에 대해 추가 수집 및 증강을 반복했습니다.

 

학습된 모델은 FastAPI 백엔드에 탑재했습니다.
프론트엔드에서 이미지를 업로드하면, YOLO 모델이 화폐의 국가종류를 인식하고,실시간 환율 API를 통해 원화 환산 금액까지 제공합니다.

 

 

 

 

 

모델 배포 및 연동 아키텍처

모델은 FastAPI 기반의 로컬 서버에 배포되었고, React 프론트엔드와 완전히 연결돼 있습니다.

 

 


최종 파이프라인 요약 

 

이 프로젝트는 사용자가 외화를 촬영하면, 화폐 종류와 국가를 자동으로 인식하고,
실시간 환율을 반영하여 원화로 환산된 금액을 보여주는 시스템입니다.
전체 흐름은 다음과 같습니다.

 

동작 흐름

 

  • 사용자는 앱에서 외화를 촬영하거나 업로드합니다.
    → 해당 이미지는 FormData 형식으로 FastAPI 서버로 전달됩니다.
  • FastAPI 서버는 YOLOv8 모델을 통해 객체 탐지를 수행합니다.
    → 결과는 [{class, confidence, bbox}] 형식의 JSON으로 반환됩니다.
  • 프론트엔드는 class 값을 기준으로 화폐 정보를 파싱합니다.
    → 국가명, 금액, 단위를 구분하여 보여줄 준비를 합니다.
  • 환율 정보는 외부 API를 통해 가져옵니다.
    → URL: https://api.exchangerate-api.com/v4/latest/KRW
    → 예: USD → KRW, JPY → KRW, EUR → KRW 등으로 자동 변환됩니다.
  • 최종적으로, 사용자는 감지된 외화의 종류와 환산 금액을 화면에서 확인할 수 있습니다.

 

시스템 구성 요약

  • 입력: 사용자 외화 이미지 (지폐 또는 동전)
  • AI 모델: YOLOv8 (객체 탐지, 클래스별 confidence 반환)
  • 백엔드: FastAPI (이미지 수신, 추론 결과 반환, 환율 적용)
  • 프론트엔드: React (이미지 업로드, 결과 출력)

 

 

실시간 환율 적용

 

성능 최적화 포인트

항목 설명
모델 추론 속도 평균 0.06초 / 이미지
Confidence Threshold 0.25로 설정하여 불필요한 탐지를 최소화함
후처리 softmax 확률 기반으로 결과를 정렬하여 가장 유의미한 클래스 우선 표시

실행 방법 요약

  • 데이터 수집: Bing 이미지 크롤링 자동화
  • 데이터 라벨링: Roboflow 수동 라벨링 (Bounding box, 87클래스)
  • 모델 학습: YOLOv8 + augmentation + validation 분리
  • 배포 연동: FastAPI + React + 실시간 환율 API

배운 점

  • 데이터를 모으는 과정부터 전처리까지 직접 해보며 실무적인 감각을 익혔습니다.
  • 자동화의 한계와 수작업의 중요성을 동시에 경험했습니다.
  • 단순히 정확도를 높이는 것보다 사용자의 불편을 줄이는 AI가 중요하다는 걸 느꼈습니다