< 졸업 프로젝트에서 시작된, 진짜 써먹을 수 있는 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 (이미지 업로드, 결과 출력)
실시간 환율 적용
- API: https://api.exchangerate-api.com/v4/latest/KRW
- 환율은 동기적 요청으로 처리되며, 각 화폐마다 실시간으로 변환 결과가 표시됩니다.
- 예: USD → KRW, JPY → KRW 등
성능 최적화 포인트
항목 | 설명 |
모델 추론 속도 | 평균 0.06초 / 이미지 |
Confidence Threshold | 0.25로 설정하여 불필요한 탐지를 최소화함 |
후처리 | softmax 확률 기반으로 결과를 정렬하여 가장 유의미한 클래스 우선 표시 |
실행 방법 요약
- 데이터 수집: Bing 이미지 크롤링 자동화
- 데이터 라벨링: Roboflow 수동 라벨링 (Bounding box, 87클래스)
- 모델 학습: YOLOv8 + augmentation + validation 분리
- 배포 연동: FastAPI + React + 실시간 환율 API
배운 점
- 데이터를 모으는 과정부터 전처리까지 직접 해보며 실무적인 감각을 익혔습니다.
- 자동화의 한계와 수작업의 중요성을 동시에 경험했습니다.
- 단순히 정확도를 높이는 것보다 사용자의 불편을 줄이는 AI가 중요하다는 걸 느꼈습니다
'Project > 졸업프로젝트' 카테고리의 다른 글
잠자고 있는 외화를 깨우다, P2P 환전 플랫폼 '커렉스' (0) | 2024.11.26 |
---|