Buylist Scanner: 사진 한 장으로 매장 카드의 포켓몬 컬렉션 가격을 책정하는 오픈 모델
요약
Buylist Scanner는 포켓몬 카드와 같은 TCG(Trading Card Game) 컬렉션의 가격 책정 과정을 혁신하는 오픈 모델입니다. 사용자가 사진 한 장만 찍으면, 시스템이 카드를 식별하고, 시장 가격을 조회하며, 환율 및 상점 정책까지 적용하여 즉각적인 견적을 제공합니다. 이는 소규모 TCG 상점의 운영 효율성을 극대화할 것으로 기대됩니다.
핵심 포인트
- 사진 기반으로 대량의 카드(20,448종)를 식별하고 가격 책정 가능
- TCGplayer 시장가와 Banxico 공식 환율을 실시간으로 통합 적용
- 상점 크레딧 및 현금 비율 등 복합적인 상점 정책 반영
- 소규모 TCG 상점에 월별 설치/지원 비용 모델 제안
이 글은 Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle에 제출하는 내용입니다.
내가 만든 것 (What I Built)
멕시칼리(Mexicali)의 작은 TCG 상점에 누가 포켓몬 카드가 담긴 신발 상자를 들고 와서 "얼마를 주실 건가요?"라고 묻는 상황을 가정해 봅시다.
오늘날 직원들은 카드를 한 장씩 계산합니다. 어떤 인쇄물인지 확인하고(151 Charmander는 Paldean Fates의 것과 거의 똑같아 보임), TCGplayer 가격을 조회하며, 달러를 페소로 환전하고, 상태에 따른 감가상각을 적용한 후, 상점의 비율을 곱합니다. 수백 장의 카드를 계산하는 데 오랜 시간이 걸리며, 직원 두 명이 같은 상자에 대해 서로 다른 두 가지 견적을 제시할 수도 있습니다.
Buylist Scanner는 이 과정을 몇 장의 사진으로 바꿉니다:
- 직원이 휴대폰으로 카드 한 장 또는 9칸짜리 바인더 페이지 전체를 촬영합니다.
- 상점의 PC가 각 카드를 찾아내고, 이미지를 바로잡은 후, 영어 카탈로그와 비교하여 식별합니다: 총 20,448장의 카드. 대부분은 TCGdex에서 가져오며, TCGdex에 이미지가 없는 785장은 pokemontcg.io에서 가져옵니다.
- 인쇄된 **세트 코드(set code)와 수집가 번호(collector number)**를 읽어 같은 아트를 재출판한 카드를 구분하고, 스페인어나 일본어로 인쇄된 카드는 사람이 직접 확인하도록 플래그를 지정합니다.
- TCGplayer 시장 가격을 조회하고, Banxico의 당일 공식 환율로 환전하며, 상점의 상태 요소를 적용합니다: 현금 85%, 매장 크레딧(store credit) 90%.
- 휴대폰에는 모든 카드의 견적, 총액, 그리고 날짜가 표시된 환율과 함께 고객 요약이 나타납니다. 또한 재고 관리를 위해 CSV 파일로 내보낼 수 있습니다.
지불 주체: 컬렉션을 매입하고 트레이드를 진행하는 소규모 TCG 상점입니다. 계획은 상점당 월별 설치, 카탈로그 업데이트 및 지원 비용을 받는 것입니다.
실제 상점에서 테스트하기 (Testing it with real shops)
카운터에서, 10월 14일~15일
10월 14일 저녁부터 15일 정오까지, 저는 실제로 사용되는 카드들을 휴대폰으로 스캔하며 이 도구를 사용했습니다:
| 실제 사용 기록 (Real use, Oct 14–15) | |
|---|---|
| 휴대폰 사진 | 40 |
| ... |
이틀 후, 메히칼리에서 포켓몬 카드 엑스포가 열렸습니다. 저는 스캐너를 가져가서 그곳의 두 개 다른 매장 테이블에서 시연을 했고, 각 매장의 카드를 스캔하여 현장에서 가격을 책정했습니다. 서버는 제 노트북으로 구동되었고, 제 휴대폰이 와이파이를 통해 연결되었습니다.
이 데모 중 한 곳의 서버 로그:
| 카드 엑스포, 10월 17일 | |
|---|---|
| 휴대폰 사진 | 10장, 4분 이내 |
| ... | |
| 매장들이 본 것: 카드를 얼마나 빠르게 식별하고 페소로 가격을 책정하는지였습니다. 두 매장의 직원들 모두 자체 매장에서 사용하려는 강한 관심을 보였고, 저는 매장이나 다음 행사에 시범 운영을 원하는 두 명의 연락처를 가지고 돌아왔습니다. |
작동하지 않은 것: 완전히 식별할 수 없는 카드들이 있었고, 아직 가격이 책정되지 않은 매우 새로운 카드를 중심으로 일부 카탈로그 공백도 나타났습니다. 이것들이 다음 수정 사항입니다.
아직 모르는 것: 그들이 비용을 지불할지, 그리고 얼마나 지불할지입니다. 엑스포에서는 묻지 않았고, 그것이 시범 운영의 첫 번째 질문입니다.
데모
제가 라벨링한 테스트 사진에 대한 정확도. 모두 4개의 스레드로 제한된 노트북 CPU에서 진행되었습니다:
| 결과 | |
|---|---|
| 가장 높은 순위로 올바른 카드, 실제 카운터 사진 41장 | 100% (이미지 검색만으로: 97.6%) |
| ... | |
| 완벽하지 않으며, 그렇게 말하고 있습니다. 확신하지 못하는 카드에 대해서는 추측하는 대신 3가지 옵션과 함께 **"¿Cuál es?"**를 보여줍니다: 근소한 차이, 일본어로 인쇄된 카드, 카탈로그 이미지가 없는 카드. 직원이 올바른 것을 터치하면 그 수정 사항은 나중에 매칭을 개선하기 위해 저장됩니다. 불확실성을 솔직하게 보여주는 것이 자신감 있는 오가격보다 여기서는 더 중요합니다. |
코드
GitHub logo Martin-EG / pkm-tcg-card-scanner
TCG Buylist Scanner

멕시코 메시칼리(Mexicali)의 소규모 TCG 매장을 위한 포켓몬 카드 스캐너입니다. 직원이 휴대폰으로 카드 한 장이나 바인더 페이지를 사진 찍습니다. 가게의 PC가 각 카드를 식별하고, 시장 가격을 조회하며, 가게 자체 규칙을 사용하여 현금 및 매장 크레딧으로 구매 제안액을 계산합니다.
모든 과정은 가게 PC에서 실행됩니다: 오픈 모델, 로컬 카드 인덱스, 그리고 클라우드 비전 API는 사용하지 않습니다. 고객의 사진은 절대 가게를 벗어나지 않습니다. 오직 가격 및 환율 조회만 인터넷을 사용하며, 이 두 정보 모두 캐시(cached)되어 있어 카운터가 오프라인에서도 작동할 수 있습니다.
작동 방식
flowchart TB
subgraph identify["식별 (가게 PC에서)"]
direction LR
photo([사진]) --> crop["자르기<br/>1–9장"]
crop --> search["이미지 검색<br/>DINOv2 + FAISS"]
search --> ocr["OCR 재순위화(re-rank)<br/>코드, 번호, 언어 설정"]
ocr --> card([카드 + 신뢰도])
end
subgraph offer["가격 및 제안액"]
direction LR
price["TCGplayer 시장 가격<br/>TCGdex, pokemontcg.io 백업<br/>매일 캐시]
price --> fx["× 환율<br/>Banxico
…
제작 과정
flowchart LR
P[휴대폰 PWA] -- 사진 --> C[1–9장 자르기<br/>OpenCV]
C --> E[DINOv2 임베딩<br/>전체 카드 + 아트워크]
...
느낌이 아닌 실제 사진으로 모델을 선정했습니다. 저는 카운터에 있는 카드들(슬리브 안에, 바인더 안에, 빛 반사가 심한 상태로, 옆으로 눕힌 상태)을 촬영하고 CLIP ViT-B/32와 DINOv2 ViT-B/14를 같은 사진들에 비교했습니다. CLIP은 카드를 테마(
재출간 카드에는 텍스트가 필요합니다. 일부 카드는 동일한 아트워크로 재출판됩니다 (151 → Prismatic Evolutions, Paldean Fates). 이미지 모델로는 이를 구별할 수 없기 때문에 PaddleOCR이 하단 스트립을 읽습니다. 최신 카드들은 MEW EN 004/165와 같이 인쇄되지만, OCR은 PREEN, SYIEJ203/198 또는 CRLENAPOPE 같은 것을 반환합니다. 파서는 유사한 문자나 병합된 토큰을 처리합니다.
일반 API가 놓칠 수 있는 현지 디테일. 멕시코에서 판매되는 스페인어 카드는 세트 코드 옆에 ES가 아닌 LA로 태그가 지정된 라틴 아메리카 판본입니다. 저는 사진을 살펴보고서야 알게 되었습니다.
가격 정보. 저는 pokemontcg.io부터 시작했습니다. 이 사이트는 최신 세트에 대한 가격 정보가 없었고 계속 502 오류를 반환했습니다: 41장의 카드 중 19장만 가격이 책정되었고, 68개의 상점(s)이었습니다. 그러다가 제가 이미 이미지용으로 사용하던 오픈 카탈로그인 TCGdex가 동일한 카드 ID 아래에 동일한 TCGplayer 가격을 게시한다는 것을 알게 되었습니다. 7개 상점에서 41장 중 39장의 가격이 책정되었으며, pokemontcg.io는 백업으로 유지했습니다. 모든 판매 제안은 가격, 출처, 날짜 및 환율을 저장하므로, 가게는 항상 그 숫자를 설명할 수 있습니다.
전화 앱. React + Vite PWA로 제작되었으며, 멕시코 스페인어로 되어 있고 한 손 사용에 용이하도록 큰 버튼들이 배치되어 있습니다. 만약 상점 PC의 Wi-Fi 연결이 끊기면, 앱은 이를 알려주고 구매 내역을 휴대폰에 유지하며 서버가 돌아올 때까지 대기열 처리된 변경 사항을 전송합니다. 제거된 카드는 Deshacer 기능을 사용하여 몇 초 동안 되돌릴 수 있습니다.
첫 번째 실제 테스트를 통해 배운 것들
카운터는 제가 찍은 테스트 사진보다 더 까다로웠습니다. 아래의 모든 문제는 같은 날 발견되어 수정되었습니다:
- 확신에 찬 오답. 바인더에 든 Radiant Jirachi가 Sneasel ex로 잘못 인식되었습니다. 사용자가 요청하지 않았는데도 말이죠: 0.797 대 0.786. 반짝이는 Radiant 마감이 아트워크 비교를 속였고, OCR은 바인더 포켓이 읽는 스트립에서 숫자를 밀어냈기 때문에 번호를 읽지 못했습니다. 이제 인쇄된 텍스트가 항상 결정하지 못하는 근접한 동점 상황에 대해 질문을 던지고, 첫 번째 시도에서 아무 숫자도 찾지 못한 경우 OCR은 두 번째의 더 높은 스트립을 읽습니다. 15개 대신 21개의 숫자를 읽는 40장의 실제 클롭(crop) 이미지와 함께 Jirachi가 모든 사진 중앙에 위치하게 되었습니다.
- 눈부심으로 가려진 9개 중 4장의 카드. 플라스틱 포켓의 빛이 카드 윤곽을 지웠습니다. 이제 최소 4장의 카드가 그리드를 형성할 경우, 누락된 포켓은 간격에서 추론되며, 카드만 포함하고 있을 때 유지됩니다. 빈 포켓은 거의 에지(edge)를 측정하지 않습니다. 동일한 페이지가 5장부터 9장 중 9장으로 늘어났고, 빈 포켓이 있는 페이지에 가짜 카드가 추가되지 않도록 합니다.
- 잘못 읽힌 숫자. OCR이
112/086대신117/086을 읽었습니다. 세 가지 다른 세트에 카드 #112 of 86이 존재하며, 그중 하나가 요청 없이 추가되었습니다. 이제 숫자는 정확히 하나의 카드를 가리켜야 합니다. - 카탈로그에 아예 없던 카드들. Team Rocket의 Wobbuffet 프로모션 카드와 HGSS 시대의 Suicune 및 Entei 프로모션 카드가 계속 실패했습니다. TCGdex에는 이들이 나열되어 있지만, 이미지는 없습니다. pokemontcg.io는 이러한 많은 누락 부분에 대한 이미지를 가지고 있으므로, 스크립트가 이제 이를 채웁니다: +785장의 카드를 포함하며, Shining Legends, Dragon Majesty 및 Trainer Galleries와 같은 전체 세트를 포함합니다. 이 세 가지 모두 이제 인식됩니다.
오픈 혁신이 중요한 이유는 무엇인가요?
고객의 컬렉션은 매장에 머무릅니다. 사람들은 어릴 때부터 가지고 온 카드를 가져옵니다. DINOv2와 PaddleOCR을 상점 자체 PC에서 실행하므로, 어떤 사진도 제3자 API로 전송되지 않습니다. 오직 가격 및 환율 조회만 인터넷을 사용하며, 둘 다 캐시됩니다.
이 비즈니스는 스캔 비용이 0일 때만 작동합니다. 하루 이틀 테스트를 거치며 사진 40장으로 카드 식별을 84개 수행했고, 실제 컬렉션은 수백 장에 달합니다. 500장의 컬렉션이라면 유료 Vision API 호출이 500번 필요하며, 상점은 일주일에 여러 개의 컬렉션을 다룹니다. 스캔당 비용을 책정하는 카드 인식 서비스는 제품의 가격을 결정하게 됩니다. 상점이 이미 소유하고 있는 CPU 기반의 오픈 웨이트(open weights) 모델 덕분에 한계 비용이 0이 됩니다.
인터넷 연결이 끊겨도 작동합니다. 식별 과정은 완전히 로컬에서 이루어지며, 가격과 환율 정보는 일일 캐시로 대체되고 '오래됨(stale)'으로 표시됩니다.
개방형 데이터가 수정 작업을 가능하게 했습니다. 첫 실제 테스트부터 발생한 모든 문제는 몇 시간 만에 해결할 수 있었는데, 그 이유는 모든 구성 요소가 개방되어 있기 때문입니다. 모델, OCR, 카탈로그 등 모든 것이 그렇습니다. 저는 OCR 출력을 읽고, 라틴 아메리카 LA 태그를 추가하며, 숨겨진 바인더 포켓을 추론하고, 두 번째 오픈 소스에서 누락된 카탈로그 이미지를 채울 수 있었습니다. 폐쇄형(closed) 인식 API를 사용했다면 잘못된 답변만 얻었을 뿐 디버깅할 방법이 없었을 것입니다. Is this even a valid card? Zero-shot image classification in a Lambda container와 What does the card say? Text extraction using Amazon Nova 2 Lite에서 클라우드 방식을 비교해 보세요. 둘 다 멋지지만, 모든 카드가 다른 사람의 서버로 가고, 다른 사람이 정한 가격을 지불해야 합니다.
솔직한 트레이드오프(trade-offs):
· 설정: 첫 실행 시 카탈로그와 모델 가중치(약 2GB)를 다운로드하기 위해 인터넷 연결이 필요하며, 인덱스를 구축하는 데 노트북 기준으로 약 35분이 소요됩니다. 새로운 세트는 약 1분 만에 추가됩니다.
· 이미지가 없는 카드: 현재 두 출처 모두에서 이미지를 찾을 수 없는 영어 카드(MEP 프로모션, My First Battle, 트레이너 키트)가 약 800장 있어, 이 카드들은 수동 검토 대상으로 표시되어 반환됩니다.
· 신규 세트: TCGplayer가 가격을 책정하기 전에 카드를 식별할 수 있으므로, 직원이 가격이 나타날 때까지 직접 입력합니다.
· 스페인어 인쇄본: OCR이 LA 태그를 읽지 못하면 스페인어로 표시되지 않는 경우가 있습니다.
다음 단계: 엑스포에서 만난 두 명의 담당자와 그들의 매장이나 다음 행사장에서 시범 운영을 할 예정입니다. 거기서 저는 여전히 답할 수 없는 질문, 즉 가게가 이것에 얼마를 지불할지 물어볼 것입니다. 그전에, 엑스포에서 식별하지 못한 카드들을 수정해야 합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기