Google Cloud Vision APIでレシート画像を解析し、データ整理してみた

## はじめに
こんにちはGxP株式会社のかわむーです！
この記事は[グロースエクスパートナーズAdvent Calendar 2024](https://qiita.com/advent-calendar/2024/gxp)の12日目の記事です。

社内の勉強会でネタ切れになりネタ探しの際に出会ったGoogle Cloud Vison APIを使用した画像処理を紹介します。

## 今回使用したセットアップ
**実施環境**
+ Google Colab

**使用言語**
+ Python

**用意するAPI**
+ Google Cloud Vison API
+ Google Sheets API
+ OpenAI API
## 各APIをサラッと解説
#### Google Cloud Vison API
Google Cloud Vision APIは、画像内の情報を解析して、テキスト、オブジェクト、ラベル、顔、ロゴなどを認識する画像処理サービス。
主にOCR（光学文字認識）、画像分類、顔検出などに使用される。

https://cloud.google.com/vision/docs?hl=ja

#### Google Sheets API
Google Sheets APIは、Googleスプレッドシートをプログラムから操作するためのAPIです。
スプレッドシートのデータの読み取り、書き込み、更新、フォーマット変更などが可能。

https://developers.google.com/sheets/api/guides/concepts?hl=ja

#### OpenAI API
OpenAI APIは、ChatGPTやGPT-4などのモデルを利用して、テキスト生成や解析、翻訳、会話などの自然言語処理（NLP）タスクを実行するサービスです。
Chatベースの対話型モデルや、特定のタスクに特化したモデルを利用可能。

https://platform.openai.com/docs/api-reference/introduction

## 今回の内容
レシート画像からテキストを抽出し、特定の項目（**購入日**、**商品名**、**価格**）を解析してGoogleスプレッドシートにまとめる処理を作成していきます
本記事では、Google Cloud Vision API、OpenAI API、Google Sheets APIを使用した画像処理フローを実現する方法を紹介します
#### 処理フロー
1\.レシート画像のOCR（Google Vision APIを使用）
2\.抽出されたテキストの解析（OpenAI APIを使用）
3\.Googleスプレッドシートにデータを追加（Google Sheets APIを使用）

#### 今回使用するレシート
女子力を感じる購入履歴ですね～ウマカッタ
![レシート.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3941952/3b30cd16-884b-4c48-3cce-69f183fcc8f4.jpeg)

## 実装内容
必要なライブラリをインストールしていきます。
OpenAIのversionは0.27.8を使用します
```bash:bash
!pip install google-cloud-vision google-auth google-api-python-client nest-asyncio
!pip install openai==0.27.8
```
ライブラリをインストールしたら必要は環境設定をしていきます。Cloud Vison APIとSheets APIは同じサービスアカウントで管理しているため認証キーは１つだけになっています。
APIを色々使用しているので非同期処理にすることで待機時間の緩和と今後複数画像処理する際に並列処理ができるようにしておきます。
``` Python:Python
import os
import asyncio
from google.cloud import vision
from google.oauth2.service_account import Credentials
from googleapiclient.discovery import build
import openai
import nest_asyncio
import re

# nest_asyncioを有効化（Google Colab環境での非同期処理対応）
nest_asyncio.apply()

# 環境変数の設定
GOOGLE_SERVICEACCOUNT_CREDENTIALS = "path/credentials.json"  # Vison API&sheet API認証キー
SPREADSHEET_ID = "sheet_id"  # GoogleスプレッドシートのID
SHEET_NAME = "sheets_name"  # スプレッドシートのシート名
OPENAI_API_KEY = "OPENAI_API_KEY"  # OpenAI APIキー
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = GOOGLE_SERVICEACCOUNT_CREDENTIALS
openai.api_key = OPENAI_API_KEY
```
この関数では、Google Vision APIを使用してレシート画像からテキストを抽出します。画像データをAPIに送信し、返されたテキストを解析して結果を返します。
```Python:Python
# APIクライアントの初期化
vision_client = vision.ImageAnnotatorClient()
credentials = Credentials.from_service_account_file(
    GOOGLE_SERVICEACCOUNT_CREDENTIALS,
    scopes=["https://www.googleapis.com/auth/spreadsheets"]
)
sheets_service = build("sheets", "v4", credentials=credentials)

# OCR処理（Google Vision API）
def extract_text_from_receipt(image_path):
    """レシート画像からテキストを抽出"""
    with open(image_path, "rb") as image_file:
        content = image_file.read()
    image = vision.Image(content=content)
    response = vision_client.text_detection(image=image)
    if response.error.message:
        raise Exception(f"Vision API error: {response.error.message}")
    texts = response.text_annotations
    if not texts:
        return None
    return texts[0].description
```
この非同期関数は、Cloud Vison APIで抽出したテキストをOpenAI APIに渡し、プロンプトて指定した解析結果を取得します。今回は購入日、商品名、価格を抽出するプロンプトを与えます。
プロンプトの結果によって後続の処理が変化するのでお好みのプロンプトにしてみるといいですね。
```Python:Python
async def extract_data_with_openai(full_text):
    """OCRで取得したテキストから商品情報と購入日を抽出"""
    prompt = f"""
    以下のレシートのテキストから、商品名、価格、購入日を抽出してください。
    出力形式は以下のようにしてください：
    購入日: <購入日>,商品名: <商品名>,価格: <価格>
    レシートの内容：
    {full_text}
    """
    response = await openai.ChatCompletion.acreate(
        model="gpt-4",
        messages=[
            {"role": "system", "content": "あなたはテキスト解析の専門家です。"},
            {"role": "user", "content": prompt}
        ]
    )
    return response.choices[0]["message"]["content"]
```
OpenAI APIから得られた結果を基に、Sheets APIへ渡せる内容に整理していきます。
```Python:Python
def append_to_google_sheet(parsed_data):
    """Googleスプレッドシートに解析データを購入日、商品名、価格の順に追加"""
    rows = []

    # 行ごとに処理
    for line in parsed_data.split("\n"):
        line = line.strip()  # 前後の空白を削除
        if not line:  # 空行をスキップ
            continue

        # 正規表現で購入日、商品名、価格を抽出
        match = re.match(r"購入日: (.+?),\s*商品名: (.+?),\s*価格: ¥([\d,]+)", line)
        if match:
            purchase_date = match.group(1).strip()
            product = match.group(2).strip()
            price = match.group(3).strip()  # カンマ付き価格をそのまま保持
            rows.append([purchase_date, product, f"¥{price}"])
        else:
            print(f"行がパターンに一致しませんでした: {line}")

    # デバッグ用: 全データの確認
    print("送信するデータ:")
    print(rows)

    # Google Sheets APIに渡すデータを構成
    body = {"values": rows}

    range_name = f"'{SHEET_NAME}'!A1"  # データの挿入範囲
    response = sheets_service.spreadsheets().values().append(
        spreadsheetId=SPREADSHEET_ID,
        range=range_name,
        valueInputOption="USER_ENTERED",  # Google Sheetsの自動フォーマット機能を使用
        insertDataOption="INSERT_ROWS",
        body=body
    ).execute()

    # APIレスポンスを確認
    print("データをGoogleスプレッドシートに追加しました。")
```
このコードでは、レシート画像から情報を抽出し、Googleスプレッドシートに記録する非同期処理を実行します。
Cloud Vision APIを使って画像内のテキストをOCRで抽出し、OpenAI APIでテキストを解析して購入日、商品名、価格を特定します。その結果をSheets APIを用いてスプレッドシートに追加します。
非同期処理により各API呼び出しを効率化し、データ抽出と記録をシームレスに行っています。
```Python:Python
# メイン処理（非同期対応）
async def main_async():
    image_path = "/content/drive/MyDrive/レシート画像処理/レシート.jpg"  # レシート画像のパス
    print("レシートのOCRを実行中...")
    full_text = extract_text_from_receipt(image_path)
    if not full_text:
        print("レシートからテキストを抽出できませんでした。")
        return
    
    print("OpenAIを使用してデータを抽出中...")
    extracted_data = await extract_data_with_openai(full_text)  # 非同期関数を呼び出し
    print("抽出されたデータ:")
    print(extracted_data)
    
    print("Googleスプレッドシートにデータを追加中...")
    append_to_google_sheet(extracted_data)

# Google Colab環境でのエントリポイント
loop = asyncio.get_event_loop()
loop.run_until_complete(main_async())
```
#### 出力結果
>レシートの画像解析を実行中...
OpenAIを使用してデータを抽出中...
抽出されたデータ:
購入日: 2024-11-04,商品名: パンケーキ,価格: ¥1,650
購入日: 2024-11-04,商品名: ピーチソーダ,価格: ¥700
Googleスプレッドシートにデータを追加中...
送信するデータ:
[['2024-11-04', 'パンケーキ', '¥1,650'], ['2024-11-04', 'ピーチソーダ', '¥700']]
データをGoogleスプレッドシートに追加しました。

#### スプレッドシート結果
![sheet_result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3941952/a29a0ab0-28e9-a5f3-ac91-4c00b1769469.png)
※1行目のタイトルはあらかじめ手動で入れてあるものです

#### 別のレシート画像でもやってみた
![別レシート.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3941952/6b17864e-0f89-a639-1410-47938cc95896.jpeg)
#### 出力結果
>レシートの画像解析を実行中...
OpenAIを使用してデータを抽出中...
抽出されたデータ:
購入日: 2024年11月5日,商品名: タラコとツナ,価格: ¥1,450
Googleスプレッドシートにデータを追加中...
送信するデータ (rows):
[['2024年11月5日', 'タラコとツナ', '¥1,450']]
データをGoogleスプレッドシートに追加しました。

#### スプレッドシート結果
![スクリーンショット 2024-12-11 115659.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3941952/6141a745-2cfd-6a8f-9d26-792025bf5ae5.png)


## 筆者の独り言
+ プロンプトの出力結果をJson形式にすれば汎用性もあるし正規表現しなくて済んだな
+ とにかく動くこと第一で作成したのでかなり雑になっている
+ 各ドキュメントを読み込むのがかなり大変だったな．．．
+ なかなかリファクタリングのしがいがあるコードですがどうかお手柔らかに:grinning:


## 最後に
まずは、ここまで読んでいただきありがとうございます！
今回の記事はGoogle Cloud Vision APIで何ができるかな？というところから始まり、あれもできるんじゃない？これもできるんじゃない？と自由気ままに組み合わせた結果を紹介しました。
今の世の中色々便利な技術がインターネットにゴロゴロ転がっているので、まずはやってみよう精神で小さなアイデアを実現してみると楽しいですよ！