![[AI 가계부] AI와 상담하며 가족 전용 가계부 시스템을 만들어 보았다 ③ 〜영수증 해석부터 가계부 데이터화까지〜 대표 이미지](https://static.zenn.studio/images/drawing/discussion.png)
[AI 가계부] AI와 상담하며 가족 전용 가계부 시스템을 만들어 보았다 ③ 〜영수증 해석부터 가계부 데이터화까지〜
요약
영수증 이미지에서 가계부 데이터를 추출하기 위한 AI 시스템 구축 과정을 다룹니다. 로컬 LLM과 OCR의 한계를 확인한 후, Gemini API를 활용한 멀티모달 접근 방식으로 전환하여 구현하는 과정을 설명합니다.
핵심 포인트
- 로컬 LLM(Gemma-2-2B-it 등)을 통한 상품 카테고리 분류 가능성 확인
- 단순 OCR 방식의 레이아웃 인식 및 정밀도 한계 파악
- 멀티모달 LLM을 활용한 이미지 직접 해석 방식의 필요성 도출
- Gemini API를 이용한 클라우드 기반 멀티모달 구현으로 전환
서론
지난번에는 LINE, Gemini API, Cloud Functions 등 시스템 전체를 어떤 서비스로 구축했는지 소개했습니다.
이번에는 그중에서도 시스템의 중심이 되는 「영수증 이미지로부터 가계부 데이터를 생성하는 AI 해석 부분」에 대해, 실제로 어떤 흐름으로 검증 및 구현해 나갔는지 소개합니다.
처음부터 지금과 같은 구성을 생각했던 것은 아닙니다.
로컬 LLM(Large Language Model)이나 OCR(Optical Character Recognition) 검증, Gemini API 비교, 프롬프트(Prompt) 조정 등 시행착오를 거듭하며 현재의 형태가 되었습니다.
1. 영수증 해석이 가능한지 확인하기
가장 먼저に取り組んだ 것은 가계부 시스템에서 가장 중요한 부분인,
「영수증 이미지로부터 가계부에 사용할 수 있는 정보를 취득할 수 있는가」
를 확인하는 것이었습니다.
이 시점에서는 노트북에 영수증 이미지를 몇 장 준비하여 로컬 환경에서 검증을 진행했습니다.
다만, 갑자기 이미지 처리부터 시작하는 것이 아니라, 우선은 「AI에게 상품 분류를 맡길 수 있는가」를 확인하는 것부터 시작했습니다.
LLM에 의한 분류 능력 확인
처음 시도한 것은 로컬 환경에서 동작하는 LLM에 의한 분류 처리입니다.
이용한 것은 LM Studio였으며, 모델로는
- Gemma-2-2B-it
- MiniCPM-V-2.6
등을 테스트했습니다.
처음에는 영수증 내용을 문자 데이터로 제공하여 카테고리 분류가 가능한지 확인했습니다.
예를 들어,
・아사히 슈퍼드라이 6캔 팩
・국산 오야마 닭 다리살 300g
・산토리 킨미카이 350ml
...
와 같은 상품 목록을 입력하고,
- 식품
- 술
와 같이 분류를 시키는 처리입니다.
그 결과, 문자 정보로 정리된 데이터라면 LLM에 의한 분류 자체는 충분히 가능하다는 것을 확인할 수 있었습니다.
즉,
영수증 정보
↓
문자 데이터
↓
LLM에 의한 분류
라는 흐름이라면 실현 가능할 것이라고 판단했습니다.
OCR에 의한 문자 읽기
다음으로 「영수증 이미지를 문자 데이터로 변환하기」 위해, 이미지에서 문자 정보를 추출하는 OCR을 검증했습니다.
하지만 실제로 영수증 이미지를 처리해 보니, 단순한 문자 인식만으로는 충분한 정밀도가 나오지 않았습니다.
영수증은,
- 점포마다 레이아웃이 다름
- 상품명이나 금액의 위치 관계가 복잡함
- 작은 글씨나 인쇄의 흐릿함이 있음
때문에 문자를 읽어내는 것만으로도 상상 이상으로 어려웠습니다.
이미지 전처리 등도 시도해 보았지만, 이번에는 OCR 부분을 직접 구현하기보다는 다른 접근 방식을 검토하는 것이 좋다고 판단했습니다.
이미지 입력이 가능한 로컬 AI 시도
직접 OCR을 구현하는 것이 힘들다면, 「처음부터 AI 스스로 영수증 이미지를 이해하게 하자」라고 생각했습니다.
그래서 이미지 입력에 대응하는 멀티모달(Multimodal) LLM인 MiniCPM-V-2.6을 시도했습니다.
하지만 로컬 환경에서 동작시킬 경우,
- 사용할 수 있는 모델 크기에 제한이 있음
- 노트북의 연산 성능으로는 처리 속도나 정밀도에 한계가 있음
이라는 문제가 있어, 이번 용도로는 충분한 성능을 얻을 수 없었습니다.
여기서 빠르게 로컬 환경에서 AI 모델을 돌리는 방침에서, 클라우드 상의 멀티모달 AI를 이용하는 방향으로 전환했습니다.
2. Gemini API로 영수증 해석 구현하기
Gemini API 단독 검증
먼저 Python에서 Gemini API를 호출하여 영수증 이미지를 해석할 수 있는지 확인했습니다.
Gemini API 이용 자체는 어렵지 않았습니다.
API 키를 취득하고 이미지와 프롬프트를 전달하는 것만으로 해석 결과를 얻을 수 있습니다.
from google import genai
from google.genai import types
from PIL import Image
...
실제로 출력된 결과는 다음과 같았습니다.
{
"purchase_date": "2026-XX-XX",
"store_name": "〇〇 슈퍼",
...
상품명에는 다소의 오타나 생략이 보였지만, 「무엇에 대한 상품인지」는 충분히 판단할 수 있는 수준입니다. 구매일이나 가격에 대해서도 올바르게 읽어내고 있음을 확인할 수 있었습니다.
물론 업무 시스템으로서 이용한다면 더욱 높은 정밀도가 요구될 것이라고 생각합니다. 하지만 이번에는 개인 이용이 목적입니다. 세세한 상품명을 나중에 다시 확인하는 일은 거의 없으므로, 약간의 표기 차이보다는 그것이 무엇인지 인식할 수 있는 것이 더 중요하다고 생각했습니다.
이로써 「영수증 이미지로부터 가계부용 데이터를 취득할 수 있다」는 것을 확인했기에, 이 방향으로 개발을 진행하기로 했습니다.
모델을 비교하기
Gemini API가 동작하는 것을 확인한 후에는, 어떤 모델을 채택할지 비교하여 선정하고 있습니다.
주로 비교한 것은,
- gemini-2.5-flash
- gemini-2.5-flash-lite
- gemini-3.1-flash-lite
입니다.
이 외에도 Flash나 Pro 모델을 시도하면서,
- 상품명·가격의 판독 정밀도
- 카테고리 분류의 정확성
- 응답 속도
- 토큰 (Token) 사용량
- 이용 비용
을 비교했습니다. 고성능 모델이 더 좋은 결과를 보여줄 것이라 기대했지만, 실제로는 그렇게 단순하지 않았습니다.
용도에 따라 장단점이 있는 것인지, 이번 영수증 해석에서는 Flash-lite 계열이 더 안정적인 결과를 반환해 주는 케이스도 있었습니다.
최종적으로는 정밀도·속도·비용의 균형을 고려하여 gemini-3.1-flash-lite를 채택했습니다.
AI는 "가장 성능이 높은 모델을 선택하면 된다"는 것이 아니라, 실제 용도에 맞춰 비교하는 것이 중요하다는 것을 실감했습니다.
프롬프트 (Prompt)를 조정하기
모델을 결정했으므로, 출력 결과를 가계부 시스템에서 다루기 쉽게 만들기 위해 프롬프트를 조정하고 있습니다.
출력 결과를 스프레드시트 (Spreadsheet)에 등록하는 것을 고려하여, 우선 다음과 같이 설정했습니다.
- JSON 형식으로 출력할 것
- 카테고리는 사전에 정의한 후보 중에서 선택할 것
카테고리를 고정함으로써, AI가 독자적인 카테고리를 생성하거나 「식비」, 「식품」, 「식료품」과 같은 표기 불일치가 발생하는 것을 방지했습니다.
다음과 같이 가계부에서 이용할 카테고리를 미리 정의해 두었습니다.
【카테고리 정의 (전 13종 · 완전 일치)】
식료품, 술, 일용품, 잡화, 가전, 외식비, …
・・・
...
특정 내용(여기서는 Uber Eats)의 카테고리를 사전에 정의해 두는 것도 중요하다고 생각합니다.
또한, 단순히 상품을 읽어내는 것만으로는 가계부로서 보았을 때 다루기 어려운 케이스가 있다는 것도 알게 되었습니다.
예를 들어 슈퍼마켓이나 편의점의 경우에는,
- 맥주
- 우유
- 티슈
와 같이 상품 단위로 기록하지 않으면, 이번 목적이기도 한 「식비와 술값을 나누어 관리하는 것」을 할 수 없게 됩니다.
반면, 이자카야나 카페의 경우에는 주문한 요리를 한 품목씩 기록하면 가계부로서 알아보기 어려워집니다.
따라서,
〇〇 이자카야 → 외식비
〇〇 카페 → 외식비
와 같이 점포 단위로 묶음으로써 「가계부로서 사용하기 쉬운 데이터가 되는 것」을 의식하여 프롬프트를 작성했습니다.
▼ 발행처가 【A) 시설 내 소비형】인 경우:
- 개별 메뉴나 실료, 서비스료를 분해해서는 안 됩니다. 【영수증 1매당 1행】으로 묶어, 최종 합계 금액(세금 포함)으로 출력해 주세요.
・・・
...
나아가 AI 스스로 confidence (확신도)도 출력하게 하고 있습니다.
이는 사람이 확인할 때 「어느 항목을 중점적으로 봐야 하는지」 판단하기 쉽게 하기 위함입니다.
【「confidence (확신도)」의 출력 기준】
기본적으로는 "high"를 기본값으로 하며, 다음 케이스에 해당하는 상품만 "low"로 설정하십시오.
1. 문맥으로부터 장르를 추측하여 카테고리를 할당한 경우.
...
하지만 실제로 사용해 보니 confidence가 high여도 틀리는 케이스는 흔히 있었습니다.
AI는 확신이 없는 경우에도 단정적으로 답변할 때가 있기 때문에, 어디까지나 확인 대상을 찾는 보조 정보로서 이용하고 있습니다.
최종적으로 프롬프트는 길어졌지만, 그만큼 후속 프로그램 측에서는 복잡한 예외 처리를 줄일 수 있었다고 생각합니다.
안정적으로 작동시키기 위한 고안
어느 정도 형태가 갖춰진 시점에서, 여러 장의 영수증을 모아서 처리하는 테스트도 진행하고 있습니다.
그러자 연속해서 Gemini API로 이미지를 전송하면 에러가 발생하는 경우가 있었습니다.
원인은 무료 범위의 시간당 입력 토큰 수 제한입니다.
그래서 일정 시간 대기한 후 다음 이미지를 전송하는 처리를 추가하여, 안정적으로 연속 처리를 할 수 있도록 하고 있습니다.
얼마나 지나야 제한에 걸리는지는 실제로 작동시켜 보지 않으면 알아차리기 어려운 부분이며, 클라우드 AI를 이용할 때 고려해야 할 점 중 하나라고 느꼈습니다.
이 시점의 시스템 구성
여기까지 완성된 것은 「영수증 이미지를 해석하여 구조화된 데이터를 생성하는」 부분입니다.
아직 LINE이나 Spreadsheet와의 연동은 이루어지지 않았지만, 시스템의 핵심인 AI 해석 부분은 동작하게 되었습니다.
다음으로, 이 JSON 데이터를 저장·관리하기 위해 Google Spreadsheet 측의 메커니즘을 만들고 있습니다.
3. Spreadsheet에 저장하는 메커니즘 만들기
Gemini API를 통해 영수증 정보를 JSON 형식으로 가져올 수 있게 되었으므로, 다음으로는 그 데이터를 가계부로서 저장할 수 있도록 했습니다.
Python에서 Spreadsheet로 쓰는 부분에는 gspread를 이용하고 있습니다.
temp_worksheet.append_rows(
values=new_items_rows,
value_input_option="USER_ENTERED"
...
이것만으로도 JSON에서 생성한 데이터를 Spreadsheet에 추가할 수 있습니다.
다만, 실제로 운용하는 것을 고려하면 단순히 JSON 내용을 추가하는 것만으로는 불충분했습니다.
원본 이미지를 특정할 수 있도록 하기
가계부 데이터를 확인할 때,
"이것은 어떤 영수증으로부터 만들어진 것인가"
를 알 수 없다면, 원래의 영수증 이미지를 확인할 수 없습니다.
그래서 해석 결과에는 원본 이미지의 파일명도 저장하도록 했습니다.
로컬 검증 시에는 단순한 파일명이었지만, LINE 연동 후에는 여러 사용자나 같은 이름의 이미지가 혼재할 가능성이 있기 때문에,
LINE_영수증에서 읽어온 날짜_MessageID_사용자명.jpg
와 같이 식별 정보를 포함한 파일명을 생성하고 있습니다.
user_id = event.source.user_id
profile = messaging_api.get_profile(user_id=user_id)
user_name = profile.display_name
# 날짜와 파일명 생성
receipt_date = items[0].get("date", "2026-07-10") if items else "2026-07-10"
final_file_name = f"LINE_{receipt_date}_{message_id}_{user_name}.jpg"
이를 통해 Spreadsheet 상의 데이터로부터 원본 이미지를 추적할 수 있도록 했습니다.
중복 등록에 대한 대응
같은 이미지를 여러 번 처리했을 경우의 중복 등록에 대한 대응도 포함했습니다.
예를 들어,
- API 에러 후 재실행
- 처리 도중에 정지했을 경우
- 같은 폴더의 이미지를 재처리했을 경우
등의 상황에서는 같은 영수증이 여러 번 등록되어 버립니다.
그래서 저장 전에 파일명을 키(Key)로 하여 기존 데이터를 확인하고, 같은 파일명의 데이터가 존재하는 경우에는 한 번 삭제한 뒤 새로운 해석 결과를 쓰는 방식으로 했습니다.
# 스프레드시트 상의 기존 데이터 취득
all_existing_rows = worksheet.get_all_values()
target_filenames = [os.path.basename(p) for p in image_paths]
...
다만, LINE에서 등록하는 경우에는 이미지 전송마다 고유한 MessageID가 부여되므로, 거의 PC에서 처리했을 때를 위한 전용 기능이 되었습니다.
Spreadsheet의 행 수 문제
또 하나, 구현하고 나서 처음으로 깨달은 문제가 있었습니다.
Spreadsheet는 시트를 열었을 때의 초기 상태에서는 최대 1,000행 정도로 설정되어 있습니다.
대량의 데이터를 추가하다 보면 행 수 부족으로 쓰기에 실패하는 경우가 있었습니다.
그 때문에 데이터 추가 전에 현재의 행 수를 확인하고, 필요하다면 자동으로 행을 추가하는 처리가 필요해졌습니다.
# 3. 행 수 자동 확장 & 안전한 끝부분 추가
if new_items_rows:
# 현재의 최종 행 수(데이터가 들어있는 행)와, 시트 자체의 최대 행 수를 취득
...
영수증 1,000장이면 금방 부족해지지는 않겠지만, 상품 단위로 기록하면 1,000행은 순식간이었습니다.
Spreadsheet의 최대 셀 수는 1,000만 셀이라고 하며, 행 수는 사용 중인 열 수에 따라 결정된다고 합니다. 메인으로 사용하는 명세 시트가 6개 열이므로 약 166만 행 분량의 기록은 가능하다고 하지만, 수만 행을 넘어가면 동작이 무거워지거나, 쌓여있던 영수증을 등록한 것만으로 1만 행 가까이 소비된 것을 생각하면 예상보다 빠른 단계에서 한계를 맞이할 우려가 있을지도 모른다고 생각했습니다.
집계 시트 작성
시스템 구성과는 조금 거리가 있지만, 이 시점에서도 최소한의 가계부로 이용할 수 있도록 집계용 시트를 작성하고 있습니다.
집계 내용은 다음과 같습니다.
- 월별 지출 합계
- 카테고리별 지출
- 연간 추이
등입니다.
예를 들어, 다음과 같은 Spreadsheet 함수를 이용하여 명세 데이터로부터 카테고리별·기간별 집계를 수행하고 있습니다.
=SUMIFS('明細入力'!$C:$C, '明細入力'!$A:$A, ">="&DATE($A2,1,1), '明細入力'!$A:$A, "<="&DATE($A2,12,31), '明細入力'!$D:$D, B$1)
이 단계에서는 Spreadsheet 상에서 확인하는 심플한 집계였지만,
나중에 Looker Studio와 연동할 때의 원본 데이터로도 이용하고 있습니다.
이 시점의 시스템 구성
여기까지를 통해, 영수증 이미지를 해석하고 저장·집계하는, 가계부 시스템의 핵심이 되는 부분이 동작하는 상태가 되었습니다.
요약
이번에는 AI 가계부 시스템 중에서도 시스템의 핵심인 「영수증 이미지로부터 가계부 데이터를 생성하는 부분」에 대해 소개했습니다.
Gemini API를 통한 영수증 해석, JSON 형식으로의 변환, Spreadsheet로의 저장, 집계까지 구축함으로써, PC 상에서 실행하는 형태이긴 하지만 최소한으로 이용 가능한 가계부 시스템의 기반이 완성되었습니다.
다음 회차에서는 이 메커니즘을 보다 일상적으로 사용할 수 있도록 하기 위해, LINE Bot, Cloud Functions, LIFF 등을 이용한 사용자 인터페이스 (User Interface) 부분의 구현에 대해 소개하겠습니다.
※ 이 기사 또한 ChatGPT가 초안을 작성했습니다.
이하는 일본어 기사를 수작업으로 영어로 옮긴 후, ChatGPT가 수정한 영문입니다.
English Summary
This article introduces the implementation of the AI receipt analysis feature in my AI-powered household budget system.
I first evaluated local LLMs and OCR, then chose the Gemini API after comparing different approaches.
The article also explains prompt design, model selection, and how the extracted data is stored in Google Spreadsheet for practical use.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기