【検証レポート】Mistral OCRで日本語文書をスキャンして試してみた感想と使い方

【検証レポート】Mistral OCRで日本語文書をスキャンして試してみた感想と使い方

先日Xで少し話題となっていたMistral OCR

X上ではかなり精度が高いと言われていましたが、公式を見ると日本語のスコアがなく、実際日本語の資料でどれくらい使えるかがわかりませんでした。

なので、今回は日本でよくある資料を用いて検証してみようと思います!

score-graph
※ Mistral社の公開しているグラフ

セットアップから実行まで

Mistral OCRを使うための準備は簡単です(Pythonでの実行環境がセットアップされている前提です)。

公式ドキュメントはこちら

  1. MistralコンソールからAPIキーを発行
  2. pip install mistralai でパッケージをインストール
  3. 下記のコードを実行

    今回はローカルのPDFファイルをmistral上にアップし、presigned_urlを取得→ocrを行い、ローカルで結果が確認できるよう、画像やmdファイルを適切に保存されるように実装しました

import os
from mistralai import Mistral
from mistralai.models import OCRResponse
import base64
from datetime import datetime
from pathlib import Path

api_key = "API_KEY"
target_filename = "TARGET_FILE_NAME"
client = Mistral(api_key=api_key)

def save_image(image_base64: str, image_path: str) -> bool:
    """
    Base64エンコードされた画像データを保存する

    Args:
        image_base64: Base64エンコードされた画像データ
        image_path: 保存先のパス

    Returns:
        bool: 保存が成功したかどうか
    """
    try:
        # Base64データのプレフィックスチェックと除去
        if "," in image_base64:
            image_base64 = image_base64.split(",")[1]

        # デコードして保存
        image_binary = base64.b64decode(image_base64)
        with open(image_path, "wb") as f:
            f.write(image_binary)
        return True

    except Exception as e:
        print(f"画像の保存中にエラーが発生しました: {e}")
        return False

def create_output_dirs(base_dir: str = "outputs") -> tuple[str, str]:
    """
    出力用の一意のディレクトリを作成する

    Args:
        base_dir: 基本となるディレクトリ名

    Returns:
        tuple[str, str]: (出力ディレクトリパス, 画像ディレクトリパス)
    """
    # タイムスタンプを使用してユニークなディレクトリ名を生成
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    output_dir = Path(base_dir) / timestamp
    images_dir = output_dir / "images"

    # ディレクトリを作成
    os.makedirs(output_dir, exist_ok=True)
    os.makedirs(images_dir, exist_ok=True)

    return str(output_dir), str(images_dir)

def process_image(img_data: dict, images_dir: str) -> tuple[str, str, bool]:
    """
    画像データを処理し、保存する

    Args:
        img_data: 画像データ(id, image_base64を含む)
        images_dir: 画像保存先ディレクトリ

    Returns:
        tuple[str, str, bool]: (画像ID, 画像パス, 成功フラグ)
    """
    img_id = img_data.id.replace(".jpeg", "")
    image_filename = f"{img_id}.png"
    image_path = os.path.join(images_dir, image_filename)

    success = save_image(img_data.image_base64, image_path)
    return img_id, image_path, success

def save_markdown(
    ocr_response: OCRResponse,
    base_dir: str = "outputs",
) -> None:
    """
    OCRの結果をMarkdownファイルとして保存する

    Args:
        ocr_response: OCR処理からのレスポンス
        base_dir: 出力先の基本ディレクトリ名
    """
    try:
        # 出力用ディレクトリの作成
        output_dir, images_dir = create_output_dirs(base_dir)
        output_file = os.path.join(output_dir, "output.md")

        markdown_content = ""
        for page in ocr_response.pages:
            # 画像の処理と保存
            image_mappings = {}
            for img in page.images:
                if img.image_base64:
                    img_id, img_path, success = process_image(img, images_dir)
                    if success:
                        # 相対パスを使用するように修正
                        relative_path = os.path.relpath(
                            img_path, os.path.dirname(output_file)
                        )
                        image_mappings[img.id] = (img_id, relative_path)

            # マークダウンテキストの更新
            page_markdown = page.markdown
            for orig_id, (new_id, path) in image_mappings.items():
                page_markdown = page_markdown.replace(
                    f"![{orig_id}]({orig_id})", f"![{new_id}]({path})"
                )

            markdown_content += page_markdown + "\\n\\n"

        # Markdownファイルの保存
        with open(output_file, "w", encoding="utf-8") as f:
            f.write(markdown_content)

        print(f"Markdownファイルを保存しました: {output_file}")
        print(f"画像ファイルは {images_dir} ディレクトリに保存されました")

    except Exception as e:
        print(f"Markdownファイルの生成中にエラーが発生しました: {e}")

uploaded_pdf = client.files.upload(
    file={
        "file_name": target_filename,
        "content": open(target_filename, "rb"),
    },
    purpose="ocr",
)

signed_url = client.files.get_signed_url(file_id=uploaded_pdf.id)

# OCR処理の実行
ocr_response = client.ocr.process(
    model="mistral-ocr-latest",
    document={
        "type": "document_url",
        "document_url": signed_url.url,
    },
    include_image_base64=True,  # 画像のbase64データを含めるように指定
)

# 結果を保存
save_markdown(ocr_response)

これで準備OKです!

日本語の精度を見るなら、ちゃんとした日本語文書やよく見る解説資料などを基にしたほうが良いと思っったので、消費者庁の措置命令のPDFと厚生労働省から出ている資料を使うことにしました。

sochimeirei-preview
kaisha-ho-preview

検証結果

OCR処理を実行した結果、そこそこいい感じな印象でした。いくつか例をピックアップしてみます。すべて見たい方は実際に手元で試してみるのが良いと思います!

1. やや構造が特殊な部分

テキスト部分はほぼ完璧に認識できていますね。句読点の位置も正確です。

元データの該当箇所

original-file1

OCRによる認識結果

result1

2. 表組み部分

元データの該当箇所

original-file2

OCRによる認識結果

result2

3. 通常の文章っぽい部分

元データの該当箇所

original-file3

OCRによる認識結果

result3

4. うまく構造化できそうにない部分

元データの該当箇所

original-file4

OCRによる認識結果

result4

5. 図やイラストが使用されている箇所

元データの該当箇所

original-file5

OCRによる認識結果

result5

良かった点と気になった点

良かった点

  • 日本語の基本精度は高い
    • 普通の文章なら大体認識できているように見えます
    • 普通の文章や表程度であれば十分精度は出ている
  • 画像も含めて処理できる
    • テキストだけじゃなく複雑な関係式やグラフがある場合は画像として出力されて扱えるのは便利

気になった点

  • 構造認識にムラがある
    • 「(1)対象役務」は普通のテキストなのに「(2)対象表示」は見出し(##)として処理されるなど、一貫性がないことも
  • 日本語の特殊フォントが苦手
    • 「消費者庁」が「浦費者庁」になってたりします
    • そこまで特殊なフォントでもないですが、少しずれると認識できないようです
  • 複雑なレイアウトは苦戦
    • 一般的な表であれば問題なく出力できるが、表組みが複雑だったり当然だが人間の目でも意見が少しわかりにくい構造だと出力が崩れる

まとめ

Mistral OCRは全体的に見るとかなり使えるツールだと思います。

「自己ホスティング(オンプレミス)」オプションも提供しているので、機密性の高いデータも扱うことができそうです。

Private and portable.

A comprehensive, enterprise-grade AI platform that can be deployed anywhere—on-premises, cloud, edge, devices, data centers, and more.

参照:https://mistral.ai/

もちろん完璧ではないので、法律文書や医療文書など100%の精度が必要な場面では人間のチェックも必要でしょうね。特殊なフォントや複雑なレイアウトがある場合は、事前に試してみることをおすすめします。

個人的には、Google Document AIやGeminiに加えて、新たな選択肢として持っておくと便利だなと思いました。

Anycloudではプロダクト開発の支援を行っています

プロダクト開発をお考えの方はぜひAnycloudにご相談ください。

まずは相談する

記事を書いた人

やました

PdM

やました

Twitter

株式会社AnycloudでPdMをしています