バッチ処理で Gemini に画像内の文字認識をさせてみた（2025.3）

# 1.はじめに
　この記事では、Google Gemini のAPIを使用して、バッチ処理で画像から文字を読み取るAI-OCRを作りました。もし、仮にAI-OCRを作成される機会がありましたら、ご参考まで。
 
# 2.背景
## 2.1.動機
　教育訓練講座（SAMURAI ENGINEER　AIデータサイエンスコース）を半年間受講し、機械学習の基礎を学びました。
　得た知識を使って、是非実現したいことがありました。それがAI-OCRの作成です。
　私は現在、とある企業の人事・給与事務の統括をしており、部下の人員削減の一方、業務量の増大に日々頭を抱えています。その私が今、一番恐れているのが、４月に新入社員が入ってくることによる（一過性ですが）業務量の増大です。
 
　人を雇うと、

* 人事システムに人事基礎データ（氏名、住所、生年月日など）登録
* マイナンバーを収集し、人事システムへ登録
* 社員証の発行（外注ではなく、私の部署でカード発行機で発行している）
* 雇用保険加入手続き
* 厚生年金加入手続き
* 健康保険加入手続き
といった事務を２週間くらいでやり遂げなければならず、結構負担です。

　ところが諸事情により、未だにマイナンバーなどの収集は「紙」です。
　そこで前作[AI-OCRを自作しました(2025.2)]（https://qiita.com/jupiter-san/items/d7b3e2a70c8624c43c45 ）にて、自前でAI-OCRを作成することにチャレンジしました。
　結果として、識字性能に沢山の課題を残しました。:pensive:
 
　**問題点**
   ①　数字の読み取り精度がイマイチ
   ②　漢字の読み取りはできない（学習データの入手が難しいため、鍛えることができない）
   
　がんばって課題をひとつひとつ解決する方法を考えていく傍ら、もうすぐ実運用を開始しなければならない4月1日がやってきます！　ああ、どうしよう。。。

　そんなとき、最近の生成AIは「マルチモーダル」であることを思い出しました。
　ご存知のとおり、「マルチモーダル」モデルは、文字・画像・音声　を入力して答えを返してくれるスグレモノです！
　画像を入力して、書かれている内容を返してくれればいいやん！と気づきました。
　画像をAPIでQUERYし、応答された「社員番号」をテキストに吐き出すバッチ処理だ。
　**なんか面白そう！やってみたい！**
 
　尚、「マイナンバー」などの個人情報をパブリックネットワークに出すのは、個人情報保護法に抵触する恐れがあるので、今回はやりません。

## 2.2.開発環境
　処理自体は、ローカルのリソースを食うものではないので、普通の事務処理用PCでできます。
　私は、VSCode上の jupyter notebook 上で動かしています。
 
# 3.準備
## 3.1. GooGle APIキーの取得
　まず、[Google AI Studio](https://aistudio.google.com/app/prompts/new_chat)にアクセスします。
このように、利用規約の同意を求められますので、同意します。
![Screenshot from 2025-03-08 21-01-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3937616/89323045-0adc-4b00-9634-f1b340f7ea87.png)
　左上の 「Get API key」を押下します。
![Screenshot from 2025-03-08 21-05-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3937616/d698f117-b180-44f7-8500-2c63e09c677f.png)

「キー APIキーを作成」を押下するとAPIキーを取得できます。
![Screenshot from 2025-03-08 21-05-32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3937616/55f21336-6f16-4d85-9537-b83c2b86db6f.png)


　今回は、Gemini2.0Flashを使用します。
　無料枠があります。（注意点としては、「サービスの改善に使用」が「はい」となっていることです。これは、投入したデータを学習に使用することを言っていますので、配慮が必要なデータは投入しないほうが無難です。）
　尚、有料契約の場合は学習に使用しないことになっています。価格としては安価（100万トークンあたり$0.10）なので、実際に使う場合には有料契約もいいかもと考えています。

https://ai.google.dev/gemini-api/docs/pricing?hl=ja
![Screenshot from 2025-03-08 21-25-09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3937616/566abd3d-0412-4d1c-a07f-2eb1983f8c78.png)

## 3.2. 読ませる画像の準備
　今回は次のような帳票を読ませます。
![20250313151106-0001.tif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3937616/924a75d8-1f32-4227-8ab6-076a29d9ffe5.tiff)


 
## 3.3. 試行
　次のようなコードを用意しました。
　（参考にしたサイト　https://qiita.com/junpei_style/items/6736ab2d9b4098993e90)
``` python
import os
import google.generativeai as genai
from google.generativeai import types
import PIL.Image

apikey ="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

file = os.path.join(path,'test_img_in/20250313151106-0001.tif')
image = PIL.Image.open(file)

# プロンプト
contents=["画像に写っている郵便番号と住民票住所", image]
# モデルは gemini-2.0-flash を使用
genai.configure(api_key=apikey)
model = genai.GenerativeModel(model_name='gemini-2.0-flash')
response = model.generate_content(contents)
print(response.text)

```
![Screenshot from 2025-03-16 18-45-47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3937616/e7385be3-8db5-4f30-99b2-5160efe5c56b.png)

おお！なんとすばらしい！
ちゃんと読み取れています。
　
# 4.実践
　実運用するにあたり、次のコードを用意しました。
　test_img_inフォルダに置いたすべての画像をひとつひとつ読み、画像から郵便番号と住民票住所を読み取って、キーとして使うファイル名と一緒に output.txtファイルに書き出す処理です。
``` Python
import os
import google.generativeai as genai
from google.generativeai import types
import PIL.Image

path = os.getcwd()

GOOGLE_API_KEY ="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
out_list = []
#　画像ファイルがあるフォルダ
img_dir = os.path.join(path,"test_img_in")

for current_dir,dirs,files in os.walk(img_dir):
    for file in files:
        file1 = os.path.join(img_dir,file)
        image = PIL.Image.open(file1)
        contents=["画像に写っている郵便番号と住民票住所を読み取って、[郵便番号,住民票住所]という形式で返してほしい", image]

        genai.configure(api_key=GOOGLE_API_KEY)
        model = genai.GenerativeModel(model_name='gemini-2.0-flash-lite')
        response = model.generate_content(contents)
        res = response.text
        out_list.append([file,res])

o_file = os.path.join(path,"output.txt")
with open(o_file, 'w') as o:
    for ele in out_list:
        st1 = ele[0] + "," + ele[1]
        print(st1, end="\n", file=o)

```
# 5.結果
**output_txt**
20250313151131-0001.tif,[702-8013, 岡山県岡山市南区飽浦]
20250313151119-0001.tif,[946-5231, 福岡市中央区鳥飼2丁目1-11]
20250313151106-0001.tif,[865-0063, 熊本県玉名市中尾 123]
完璧です。:open_mouth:

:::note info
　画像枚数が多いと、次のエラーを吐いて止まってしまいます。（私の場合、40枚くらいです。）
 ![Screenshot from 2025-03-16 18-57-28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3937616/bb317089-b0d3-4bbe-9639-cbb6a34521e7.png)
　（情報源：https://qiita.com/7shi/items/54e45181052c914a4d45)
 要は、無料お試しはここまでで、本格運用するなら有料契約を検討してね！ということみたいです。
:::

# 6.参考にした情報
　本当に世の中にはすごい方がおられまして、その方々が公開していただいている情報は宝の山です。
　今回もそれを参考にさせていただいております。
　最大の謝辞を述べさせていただきます。
 
**[Geminiのドキュメント]** 
https://ai.google.dev/gemini-api/docs/pricing?hl=ja

**[Gemini-APIのかきかた]** 
https://qiita.com/junpei_style/items/6736ab2d9b4098993e90

**[429エラーについて]**
https://qiita.com/7shi/items/54e45181052c914a4d45