8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWSで手書き日本語OCR

Last updated at Posted at 2025-08-13

はじめに

AWSには昔からTextractというOCRサービスはあるものの、2025年8月現在でも日本語対応はされていません。日本のAWSエンジニアたちは、日本語OCRの必要がある際には、AzureやGCPなどの他のクラウドサービスを利用する等の回避策を行ってきました。中にはRekognitionのテキスト検出やTessaractを利用する強者もいらっしゃるようですが、精度や難易度の点で手を出しにくいものでした。
ところが、2025年の6月末にBedrockでもClaudeのPDFサポートが利用できるようになりました。これがなかなかの優れもので、本来の使い方とは若干異なるものの、日本語OCRにも十分使えますのでご紹介いたします。

利用ソフトウェア

以下の環境で検証を行っております。

  • Python 3.10.14
  • PyMuPDF 1.26.3
  • boto3 1.34.162

準備

手書き文字の写真

題材は何でもよいのですが、知的財産の侵害が絶対にないように自作しました。
こちらは、レシートの裏に私が適当に書いた偽注文書のjpeg画像です。
手書き注文書.jpg

PDF変換プログラム

今回利用するのはPDFを読む機能なので、jpeg -> PDFへの変換を行います。
複数の写真を一気にPDF化できるようにしていますが、今回は1枚だけで利用します。
他のものにも対応できるようにしておくと、汎用的に利用できそうですね。

jpeg_to_pdf.py
import fitz
import os

def convert_jpeg_to_pdf(jpeg_paths, output_pdf_path):
    """
    JPEG画像をPDFに変換する関数
    Parameters:
        jpeg_path(list): JPEGファイルのパス
        output_pdf_path (str): 出力するPDFファイルのパス
    """
    # 新しいPDFドキュメントを作成
    doc = fitz.open()

    for img_path in jpeg_paths:
        # ファイルを開く
        img = fitz.Pixmap(img_path)

        # RGBでない場合はRGBに変換
        if img.n > 4:
            img = fitz.Pixmap(fitz.csRGB, img)

        # PDFに変換
        rect = fitz.Rect(0, 0, img.width, img.height)
        page = doc.new_page(width=img.width, height=img.height)
        page.insert_image(rect, pixmap=img)

    # 保存して閉じる
    doc.save(output_pdf_path)
    doc.close()
    print(f"[I] PDFに変換完了: {output_pdf_path}")

# 使用例
if __name__ == "__main__":
    jpeg_paths = ["手書き注文書.jpg"]
    output_pdf = "手書き注文書.pdf"
    convert_jpeg_to_pdf(jpeg_paths, output_pdf)

生成AIにPDFを読ませるプログラム

変換したPDFを、boto3からClaude 4 Sonnetを利用して読ませるプログラムです。
せっかくなので、コストも計算しています。

read_pdf.py
import json
import boto3
import base64
import sys
import fitz

# Claude 4 Sonnet のモデルID(クロスリージョン推論用)
MODEL_ID = "apac.anthropic.claude-sonnet-4-20250514-v1:0" 
REGION = "ap-northeast-1" 

# Bedrock Runtime クライアント
client = boto3.client("bedrock-runtime", region_name=REGION)

# プロンプト
PROMPT = f'''
このPDFは手書きの注文書です。
以下の記載内容について教えてください。
 ・注文日
 ・納期
 ・注文内容
'''

def process_pdf(file_path):
    # PDFを読み込む
    doc = fitz.open(file_path)

    for page_index in range(len(doc)):

        # 新しいPDFを作成して1ページだけ追加
        single_page_pdf = fitz.open()
        single_page_pdf.insert_pdf(doc, from_page=page_index, to_page=page_index)

        # バイナリとして保存(メモリ上)
        pdf_bytes = single_page_pdf.write()
        encoded_data = base64.b64encode(pdf_bytes).decode("utf-8")

        # 各ページを読む
        read_pdf(page_index, encoded_data)
        single_page_pdf.close()

    doc.close()


def read_pdf(page_index, encoded_pdf):

    # リクエスト作成
    request_body = {
        "anthropic_version": "bedrock-2023-05-31",
        "messages": [
            {
                "role": "user",
                "content": [
                    {
                        "type": "document",
                        "source": {
                            "type": "base64",
                            "media_type": "application/pdf",
                            "data": encoded_pdf
                        },
                        "title": "注文書",
                        "citations": {"enabled": True},
                        "cache_control": {"type": "ephemeral"}  # プロンプトキャッシュ有効
                    },
                    {
                        "type": "text",
                        "text": PROMPT
                    }
                ]
            }
        ],
        "max_tokens": 4096

    }

    # APIコール
    response = client.invoke_model(
        modelId=MODEL_ID,
        contentType='application/json',
        accept='application/json',
        body=json.dumps(request_body)
    )

    # 結果表示
    response_body = json.loads(response['body'].read())
    print("[I] ===== 結果 =====")

    for block in response_body.get('content', []):
        if block.get('type') == 'text':
            print(block.get('text', ''))

    # 利用情報も出力しておく
    print("[I] ===== 利用情報 =====")
    usage = response_body.get('usage')
    input_tokens = usage['input_tokens']
    cache_create = usage['cache_creation_input_tokens']
    cache_read = usage['cache_read_input_tokens']
    output_tokens = usage['output_tokens']
    cost = (input_tokens * 0.000003) + (output_tokens * 0.000015)

    print(f'''
        入力トークン数:{input_tokens}
        キャッシュREAD:{cache_read}
        出力トークン数:{output_tokens}
        料金ドル   :{cost}
    ''')

if __name__ == "__main__":
    if len(sys.argv) > 1:
        name_argument = sys.argv[1]
        process_pdf(name_argument)
    else:
        print("[E] 引数に入力PDFを指定してください。")

結果

完璧じゃないですか!!
あの汚い文字を、単位なども含めてきちんと読んでいます。料金も良心的。
プロンプトで出力をjsonなどにしてもらえば、立派にOCRとして使えそうですし、一般的なOCRと異なり、文字情報の座標なども気にしなくてよいので、かなり活用の幅はありそうです。

$ python read_pdf.py 手書き注文書.pdf 
[I] ===== 結果 =====
この手書きの注文書の内容について、以下のとおりお答えします:

**注文日:** 
2025年8月12日

**納期:** 
2025年8月20日

**注文内容:**
- 
マダイ 12尾
- 
タコ 8杯
- 
エビ 25kg

この注文書は海産物の注文で、マダイ、タコ、エビの3種類の商品が記載されています。
[I] ===== 利用情報 =====
        入力トークン数:51
        キャッシュREAD:0
        出力トークン数:212
        料金ドル   :0.003333

まとめ

  • ClaudeのPDFサポートで日本語手書き文字が読める
  • Bedrockから利用できるようになったので、AWSだけで日本語OCRが可能
  • プロンプトを工夫すれば、もっと高度なことも簡単にできそう
8
2
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
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?