仕事で OCR を AI に頼むのはユーケースによってはいろいろ問題ある。 それでも人間より精度が高いならもういいのかと思ったり。 いっぽうで精度は高いが人間なら絶対間違わないようなところで間違いがおきるとやっぱり AI に任せるのは無理かと思ったり。 いずれにしてもここまで精度が高くなると、AI が返してきた結果はほぼ間違いがないので、 こたえあわせは「干し草の山から針を探す」感じになる。とても苦痛。 もちろんゼロから自分で文字起こしするのも苦痛。
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 の内容をマークダウンテキストとして出力できます。
軽く試した範囲では縦書き+段組み / 表組 なども読み取ることができました。
32GB の Mac Studio M1 で試しました。
サーバーを起動(初回):
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 に設定しています。
#!/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 形式でテキスト抽出して"