0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MIT App Inventorによるテキスト認識を用いた家計簿機能

Posted at

初めに

沢山の家計簿アプリが存在しますが,自分にマッチしたアプリってなかなか見つからないですよね...
自分で作ってしまえばよくね!ってことで根本的な機能を考えてみました!
非常にシンプルでレシートを認識して,支出ごとにカテゴライズします.

アプリ概要

カメラでレシートを撮影し,Google Cloud Runに送信.Cloud Runではレシート画像からCloud Vision APIにてテキスト認識を行い,Geminiにテキストを渡して支出ごとにカテゴライズします.

デザイナー画面

スクリーンショット 2025-05-05 142522.png

機能 内容
カメラ ボタン,写真撮影
認識 ボタン,テキスト認識を行う
保存 ボタン,結果をTinyDBに保存する
表示 ボタン,TinyDBに保存されているデータを表示する
image1 撮影した画像を表示する
Label1 ラベル,認識結果もしくはTinyDBに保存したデータの表示
Camera1 写真撮影
Web1 CloudRunへのアクセス
KIO4_Base641 画像をbase64にエンコードする
TinyDB1 CloudRunからの結果の保存場所

blocks画面

変数

CloudRunURL
CloudRunにアクセスするための1URL
blocks.png

ボタン

カメラ
blocks (2).png

認識
画像をbase64に変換しCloudCunへ送信.レスポンスはLabel1に表示.
blocks (3).png

保存
TinyDBにCloudRunの結果を保存.
blocks (6).png

表示
TinyDBに保存した今までのCloudRunの出力をLabel1に表示.
blocks (7).png

when Camera1. After picture

レシート撮影後に画像を表示する.
blocks (5).png

when KIO4_Base64. After Image Base64

画像をBase64にエンコードして,Cloudrunに送信.
blocks (1).png

when Web1. GotText

Cloud Runからのレスポンス.
ResponseContentから必要な情報のみを表示.
blocks (4).png

Google Cloud Runのコード

Cloud Vision APIを使用できるようにし,Cloud Runにコードを記述します.
生成AIに書いてもらいました~~!AIってほんとに優秀ですね.

main.py
import base64
import json
import os
from googleapiclient import discovery
from flask import Flask, request, jsonify
import functions_framework
import requests

app = Flask(__name__)

# Google Cloud Vision API のエンドポイント
VISION_API_ENDPOINT = "https://vision.googleapis.com/v1/images:annotate"

# Gemini API のエンドポイントとAPIキー
GEMINI_API_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent"
GEMINI_API_KEY = "XXXXXXXXXXXXXXXXXXXXXX" #ご自身のAPIKeyに書き換える

@app.route('/extract_text', methods=['POST']) # POSTメソッドに変更
def extract_text():
    """Base64エンコードされた画像を取得し、テキストを抽出し、Gemini APIでカテゴリ分けし、JSONで返す"""
    try:
        # リクエストからBase64エンコードされた画像データを取得
        data = request.get_json()
        base64_image = data.get('image')

        if not base64_image:
            return jsonify({"error": "imageデータがありません"}), 400

        # Google Cloud Vision API のクライアントを作成
        service = discovery.build(
            'vision', 'v1', credentials=None,
            cache_discovery=False,   # キャッシュを無効にする
            static_discovery=False   # 静的検出を無効にする
        )

        # リクエストの本文(JSON)を作成
        request_body_vision = {
            "requests": [
                {
                    "image": {
                        "content": base64_image
                    },
                    "features": [
                        {
                            "type": "TEXT_DETECTION"
                        }
                    ]
                }
            ]
        }

        # Google Cloud Vision API にリクエストを送信
        response_vision = service.images().annotate(body=request_body_vision).execute()

        # Vision API のレスポンスを処理
        extracted_text = ""
        if response_vision.get('responses'):
            text_annotations = response_vision["responses"][0].get("textAnnotations", [])
            # 全てのテキストを結合
            extracted_text = "\n".join([annotation.get('description', '') for annotation in text_annotations])
        else:
            return jsonify({"error": f"Vision APIリクエストエラー: {response_vision.get('error')}"}), 500

        # Gemini API へのリクエストを作成
        headers_gemini = {
            'Content-Type': 'application/json'
        }
        payload_gemini = {
            "contents": [{
                "parts": [
                    {"text": f"""これから家計簿を作成します。
レシートテキストを渡すのでそれをカテゴライズし、カテゴリーごとの金額を出力してください。カテゴライズできない購入品に関しては「その他」に分類してください。
また、購入日と店舗名、決済方法も出力してください。

### レシートテキスト
{extracted_text}
"""}
                ]
            }],
            "generationConfig": {
                "response_mime_type": "application/json",
                "response_schema": {
                    "type": "ARRAY",
                    "items": {
                        "type": "OBJECT",
                        "required":["購入日", "店舗名", "決済方法"],
                        "properties": {
                            "購入日": {
                                "type":"STRING",
                                "description":"購入日を出力"
                            },
                            "店舗名": {
                                "type":"STRING",
                                "description":"店舗名を出力"
                            },
                            "決済方法": {
                                "type":"STRING",
                                "description":"決済方法を出力"
                            },
                            "食費": {
                                "type":"INTEGER",
                                "description":"食費の合計を数値で出力"
                            },
                            "交通費": {
                                "type":"INTEGER",
                                "description":"交通費の合計を数値で出力"
                            },
                            "日用品": {
                                "type":"INTEGER",
                                "description":"日用品の合計を数値で出力"
                            },
                            "衣服": {
                                "type":"INTEGER",
                                "description":"衣服の合計を数値で出力"
                            },
                            "コンビニ": {
                                "type":"INTEGER",
                                "description":"コンビニの合計を数値で出力"
                            },
                            "その他": {
                                "type":"INTEGER",
                                "description":"分類できない支出の合計を数値で出力"
                            }
                        }
                    }
                }
            }
        }

        # Gemini API にリクエストを送信
        response_gemini = requests.post(
            f"{GEMINI_API_ENDPOINT}?key={GEMINI_API_KEY}",
            headers=headers_gemini,
            json=payload_gemini
        )
        response_gemini.raise_for_status() # エラーレスポンスの場合は例外を発生させる
        gemini_results = response_gemini.json()

        # Gemini API のレスポンスを処理
        if gemini_results.get('candidates') and gemini_results['candidates'][0].get('content') and gemini_results['candidates'][0]['content'].get('parts'):
            gemini_response_text = gemini_results['candidates'][0]['content']['parts'][0].get('text')
            try:
                categorized_data = json.loads(gemini_response_text)
                return jsonify({"extracted_text": extracted_text, "categorized_data": categorized_data})
            except json.JSONDecodeError:
                return jsonify({"error": f"Gemini APIのレスポンスをJSONとしてデコードできませんでした: {gemini_response_text}"}), 500
        else:
            return jsonify({"error": f"Gemini APIからの有効なレスポンスが見つかりませんでした: {gemini_results}"}), 500

    except Exception as e:
        return jsonify({"error": str(e)}), 500

@functions_framework.http
def main(request):
    """HTTPリクエストを処理する"""
    return extract_text()

requirements.txt
functions-framework==3.*
requests
google-api-python-client

実行結果

ちゃんと機能していますね!

終わりに

カテゴライズできたので,表にまとめられるようにしたいですね!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?