Home About Contact
Local LLM , Qwen , Gemma , Llama CPP

Local LLM の OCR を試す

仕事で OCR を AI に頼むのはユーケースによってはいろいろ問題ある。 それでも人間より精度が高いならもういいのかと思ったり。 いっぽうで精度は高いが人間なら絶対間違わないようなところで間違いがおきるとやっぱり AI に任せるのは無理かと思ったり。 いずれにしてもここまで精度が高くなると、AI が返してきた結果はほぼ間違いがないので、 こたえあわせは「干し草の山から針を探す」感じになる。とても苦痛。 もちろんゼロから自分で文字起こしするのも苦痛。

Qwen 3.5 9B

OCR を Qwen 3.5 9B で試したのでやり方だけメモしておく。

ツールやモデルが用意できていればこれだけです:

#!/bin/bash

m=~/.lmstudio/models/lmstudio-community/Qwen3.5-9B-GGUF/Qwen3.5-9B-Q4_K_M.gguf
mm=~/.lmstudio/models/lmstudio-community/Qwen3.5-9B-GGUF/mmproj-Qwen3.5-9B-BF16.gguf

llama-mtmd-cli \
  -m $m \
  --mmproj $mm \
  --jinja \
  -ngl 999 \
  -c 32768 \
  --temp 0.0 \ 
  --image ./input.png \
  -p "この画像をOCR して、markdown 形式でテキスト抽出して"

llama.cpp で実行していますがモデルは(諸事情により)LM Studio で入手したものがローカルキャッシュされているので、それを使っています。

これで、カレントディレクトリの input.png の内容をマークダウンテキストとして出力できます。

軽く試した範囲では縦書き+段組み / 表組 なども読み取ることができました。

Gemma 4 26B A4B

32GB の Mac Studio M1 で試しました。

API をたたく方式

サーバーを起動(初回):

llama-server \
  -hf ggml-org/gemma-4-26B-A4B-it-GGUF \
  -c 8192 \
  --n-gpu-layers 999 \
  --port 8080

なお、必要であれば、二回目以後は直接ローカルキャッシュされたモデルを指すことでも実行できる:

basePath=~/.local/llama.cpp/models/models--ggml-org--gemma-4-26B-A4B-it-GGUF/snapshots/ae4d537a6345467d1c86bb5cc0d4505ff3ebe0f3
m=$basePath/gemma-4-26B-A4B-it-Q4_K_M.gguf
mm=$basePath/mmproj-gemma-4-26B-A4B-it-Q8_0.gguf

llama-server \
  -m $m \
  --mmproj $mm \
  -c 8192 \
  --n-gpu-layers 999 \
  --port 8080

なお、この環境では LLAMA_CACHE=$HOME/.local/llama.cpp/models を設定しているため、モデル(のローカルキャッシュ)はこのパスに入っている。

サーバーが起動できたら、/v1/chat/completions エンドポイントに画像とプロンプトを投げる:

import base64
import requests
from pathlib import Path

img_path = "./input.png"

# Encode as base64 data URL
b64 = base64.b64encode(Path(img_path).read_bytes()).decode()
data_url = f"data:image/png;base64,{b64}"

payload = {
    "model": "gemma-4",  # llama-server ignores this but the field is required
    "messages": [
        {
            "role": "user",
            "content": [
                {"type": "image_url", "image_url": {"url": data_url}},
                {"type": "text", "text": "この画像をOCRして、markdown形式でテキスト抽出してください。"},
            ],
        }
    ],
    "max_tokens": 4096,
    "temperature": 0.0,
}

resp = requests.post(
    "http://localhost:8080/v1/chat/completions",
    json=payload,
    timeout=300,
)
resp.raise_for_status()
print(resp.json()["choices"][0]["message"]["content"])

同じ画像に対して同じ結果を得たいので temperature を 0.0 に設定しています。

直接 llama-mtmd-cli コマンドを使う

#!/bin/bash

basePath=~/.local/llama.cpp/models/models--ggml-org--gemma-4-26B-A4B-it-GGUF/snapshots/ae4d537a6345467d1c86bb5cc0d4505ff3ebe0f3
m=$basePath/gemma-4-26B-A4B-it-Q4_K_M.gguf
mm=$basePath/mmproj-gemma-4-26B-A4B-it-Q8_0.gguf

llama-mtmd-cli \
  -m $m \
  --mmproj $mm \
  --jinja \
  -ngl 999 \
  -c 32768 \
  --temp 0.0 \ 
  --image ./input.png \
  -p "この画像をOCR して、markdown 形式でテキスト抽出して"