Skip to main content
みんなで翻刻くずし字OCR ローカル版 — honkoku-ocr-py

PyPI Python 3.10+ MIT upstream

honkoku-ocrがディレクトリの画像を一括翻刻し、行画像とKoji記法の翻刻を出力する様子

みんなで翻刻OCR(橋本雄太、CC BY 4.0)の推論パイプラインを Pythonとonnxruntimeに移した移植版。ブラウザ版と同じ重みを使い、くずし字の古典籍画像から Koji記法(ふりがな・返り点・送り仮名・割書のタグを含む「みんなで翻刻」の記法)の翻刻テキストを得る。 幾何・正規化・復号の手順は原実装と同じである。拡大縮小と回転の補間はPillowのものなので画素値は一致せず、 encoderもブラウザ版のint8版ではなくfp16版(CPUではそれをfp32に直したもの)を使うため、出力は行によって異なる(性能)。

A Python port of the inference pipeline of みんなで翻刻OCR (honkoku-ocr-web, by Yuta Hashimoto, CC BY 4.0). Same weights, same geometry, normalisation and decoding, same output notation; no browser and no UI. Line detection and the encoder can run on CUDA, the decoder runs on the CPU. Resampling is Pillow's and the encoder is the fp16 export rather than the browser's int8 one, so line texts can differ from the browser version.

構成

段階 実装 由来
行検出 RTMDet-s、入力1024×1024レターボックス、入れ子box除去 NDL古典籍OCR-Liteのモデル、honkoku-ocr-webの前後処理
読み順 XY-Cut(縦書きは右の段から左へ) NDL古典籍OCR-Lite / honkoku-ocr-web
行認識 ConvNeXt V2 encoder(fp16、CPUではfp32に変換)+ RoBERTa decoder(int8、KVキャッシュ)、greedy、語彙7,710 honkoku-ocr-web kuzushiji-v18(v17, v16fsも選択可)
出力 特殊トークン列 → Koji記法 / 素テキスト honkoku-ocr-web

モデルの設計と学習・評価についてはdocs/tech.html(原著作物の技術情報ページの複製)を参照。

比較

honkoku-ocr-py みんなで翻刻OCR(ブラウザ版)
動く場所 Python / CLI / サーバ ブラウザ(WebAssembly, Web Worker)
一括処理 ディレクトリ単位。スクリプトやcronから呼べ、失敗した画像を飛ばして続行、--resumeで再開 タブに開いた複数画像を「全画像OCR実行」でまとめて処理。結果は画面から保存する
GPU CUDA(行検出とencoder) WebGPU対応端末のみ
行認識モデル ConvNeXt V2 + RoBERTa(kuzushiji v18) 同じ重み
出力 Koji記法 + JSON(行位置、読み順、停止理由、所要時間、モデルと設定の指紋) Koji記法、縦書き表示
行位置の持ち込み --boxes / process(image, boxes=...) 画面上でbboxを編集
行bboxの編集UI 無し(--previewで番号付き画像を出す) あり
モデルの検証 全ファイルのサイズとSHA-256を照合 IndexedDBキャッシュ
精度 未測定(同じ重み。encoderの精度と画像補間がブラウザ版と異なる) 本文plain micro CER 0.075(v18、技術情報の公表値)

ブラウザ版の強みは行bboxの手直しと縦書きの閲覧で、そこはこの移植には無い。自動化と大量処理、他のツールとの接続がこの移植の役割になる。

なぜ移植したか

ブラウザ版はタブに開いた画像をまとめて処理できるが、画像の読み込みも結果の保存も画面の操作で行う。 他のプログラムから呼ぶ、夜間に数百コマを流す、結果をそのまま次の処理に渡す、という使い方のためにこのリポジトリがある。

  • 一括処理: ディレクトリを渡せば全画像を順に翻刻し、画像ごとにKoji記法のtxtと、行位置・読み順・ 行検出スコア・所要時間を持つJSONを書き出す。読めない画像があっても残りを処理し、終了コードで知らせる。 中断したら--resumeで続きから再開できる。シェルスクリプトやcron、CIからそのまま呼べる。
  • 他のツールとの接続: PythonからOCR().process()を呼ぶだけでページの結果が返る。TEIや翻刻プラットフォーム への流し込み、別のOCRとの突き合わせ、校合ビューアの生成といった後段処理を同じプロセスで書ける。
  • 行位置の持ち込み: --boxesprocess(image, boxes=...)で自前の行bboxを与えられる。たとえばNDL古典籍OCR-Liteの 行検出結果をそのまま渡せば、二つのエンジンの読みを行ごとに一対一で比べられる。与えた座標はそのまま結果に戻る。
  • GPU: 行検出とencoderをCUDAで動かせる。行認識が支配的なので、GPUがあれば1コマ数秒で終わる。
  • 再現性: モデルはバージョン名で固定し、取得時にサイズとSHA-256を照合する。JSONには入力画像・各モデル・語彙・ 設定・パッケージとランタイムの版の指紋が入り、どの重みと設定から得た出力かを後から確かめられる。 onnxruntimeの版やデバイスをまたいで出力が一致するかは確かめていない。
  • ブラウザ不要: サーバやWSL、ヘッドレス環境で動く。IndexedDBのキャッシュもWeb Workerもいらない。

出力はブラウザ版と同じKoji記法なので、ブラウザ版で作った翻刻と混ぜて扱える。

性能

同じ見開き1コマ(6,496×4,613px、長辺3,500pxに縮小、検出21行)を、同じ機械(AMD Ryzen 7 9800X3D、16スレッド)で処理した所要時間。 ブラウザ版は配信中のhonkoku-ocr-web(2026-09-06、モデルv18)を ヘッドレスChromiumで動かした値で、WebGPUが使えない環境のためencoderはWebAssembly(int8)、認識ワーカーは8本。 モデルの取得とセッション作成は含まない。

実装 行検出 行認識 合計
ブラウザ版(WebAssembly、8ワーカー) 2.9秒 37.5〜43.2秒(2回の実測) 約40〜46秒
honkoku-ocr-py CPU、encoder fp32(既定) 0.2秒 19.5秒(encoder 18.2秒、decoder 0.9秒) 20.6秒
honkoku-ocr-py CPU、encoder fp16(--encoder-precision fp16 0.2秒 約150秒 152秒
honkoku-ocr-py CUDA(RTX 5070 Ti、encoder fp16、--threads 2 --decoder-threads 2 0.04秒 1.1秒(encoder 0.48秒、decoder 0.36秒、前処理 0.21秒) 1.46秒(warm、3回の中央値)

配信されているencoderはfp16で、onnxruntimeのCPUプロバイダではこれをそのまま動かすと1行7.3秒かかる。 同じ重みをfp32に直したファイルは1行0.86秒で、hidden stateの差は最大1e-3程度、この見開きでは21行中1行が 行末の全角空白の有無だけ違った。CPUでは初回に変換してキャッシュし(366MB、数秒)、CUDAではfp16のまま使う。

CUDAの行は同じ見開きを1回の暖機のあと3回処理した中央値(1.508、1.444、1.464秒)で、3回とも同じ文字列を出した。モデルの遅延読み込みを含む最初の1コマは3.51秒、プロセスのピークRSSは約2.0GiB。decoderはCUDAでもCPUで動く。

ブラウザ版とこの移植では同じ見開きで検出行数(22行と21行)も行の読みも一部異なる。 どちらが正しいかは正解翻刻との照合が要る。測定に使った見開きは公開許諾を確かめていない手元のスキャンで、 このリポジトリには含めない。benchmarks/ocr.pyattribution(出典)とsamples(各要素はidimage、任意のframeと正解reference)を持つ JSON manifestを受け取り、ページ単位の所要時間と、正解があればCERを出す。手元の画像と翻刻で同じ測定ができる。読み順とKoji変換の原実装との比較はbenchmarks/README.mdを参照。

使い方

uv sync --extra cpu          # onnxruntime (CPU)
uv sync --extra gpu          # onnxruntime-gpu と CUDA 12 のランタイム (cpu とは排他)

uv run honkoku-ocr --download                    # モデルを取得して照合 (4 ファイル 289 MB、~/.cache/honkoku-ocr/models)
uv run honkoku-ocr page.jpg -o out               # out/page__<hash>.txt (Koji 記法、読み順) と out/page__<hash>.json
uv run honkoku-ocr pages/ -o out --device cuda   # ディレクトリ内の画像を一括処理
uv run honkoku-ocr pages/ -o out --resume        # 済んだページを飛ばして続きから
uv run honkoku-ocr page.jpg -o out --plain       # タグ無しの素テキスト
uv run honkoku-ocr page.jpg -o out --preview     # 行 bbox と読み順を描いた PNG も書く
uv run honkoku-ocr page.jpg -o out --layout-only # 行検出だけ (行認識モデルを読まない)
uv run honkoku-ocr page.jpg -o out --boxes 'out/page__<hash>.json'   # 行位置を与えて認識だけ
uv run honkoku-ocr scans.tif -o out --frame 3    # 多ページ TIFF の 4 コマ目だけ (省略時は全コマ)

主なオプション。全体はhonkoku-ocr --help

オプション 意味
--model {v16fs,v17,v18} 行認識モデルの版(既定v18)
--device {cpu,cuda} 行検出とencoderのデバイス。decoderは常にCPU
--encoder-precision {auto,fp16,fp32} autoはCPUでfp32、CUDAでfp16
--threads N / --decoder-threads N onnxruntimeのスレッド数。0で既定
--offline キャッシュに無いモデルを取りに行かず失敗する
--verify-cache キャッシュ済みモデルのSHA-256を照合して終了
--max-dimension --margin --confidence-threshold --ios-threshold 縮小の長辺(3500)、行cropの余白(45)、行検出のスコア閾値(0.3)、入れ子除去の閾値(0.8)

出力の名前。1画像につき<stem>__<16桁hex>.json.txt--previewなら.preview.pngも)。 hexは入力の絶対パスのSHA-256の先頭16桁で、同じstemの画像が別のディレクトリにあっても衝突せず、 別の呼び出しで一部だけ処理しても名前が変わらない。多ページ画像はさらに__p0001のようにコマ番号が付く。 入力ディレクトリを移動すると名前が変わる。

0.1.0からの変更。0.1.0の出力は<stem>.txt<stem>.jsonで、JSONの行はconfidenceを持ち、--versionはモデルの版を選ぶ オプションだった。0.2.0では出力名に上のhexが付き、行のスコアはdetection_confidence、モデルの版は--modelで選び、 --versionはパッケージの版を表示する。出力ファイルと入力画像の対応はJSONのimageフィールドで取る(stemに__を含む ファイル名もあるので、名前を切って戻さない)。

再開。各ページのJSONは完了記録で、txt(とpreview)を書き終えてから最後に置かれる。書き込みは一時ファイル経由なので 途中で止めても壊れたファイルは残らない。--resumeはJSONの指紋(画像のSHA-256、コマ番号、各モデルと語彙のSHA-256、 設定、パッケージとコードとランタイムの版)が今回と一致し、txt等のSHA-256も記録どおりのときだけそのページを飛ばす。 モデルや設定を変えれば作り直す。

読めない画像や処理中に失敗したページは標準エラーに出して次へ進み、最後にN completed, N skipped, N failedを出す。 失敗が1つでもあれば終了コードは1。

Pythonから

from pathlib import Path

from honkoku_ocr import OCR, Box

ocr = OCR("v18", device="cuda")          # モデルは最初に使う段階で読み込む
for path in sorted(Path("pages").glob("*.jpg")):
    page = ocr.process(path)             # PageResult
    for line in page.lines:
        print(line.reading_order, line.koji)   # line.raw にタグ付きの生文字列、line.plain に素テキスト
    print(page.timings["total"], page.warnings)

1つのOCRを使い回す。ocr_image(path)は1回ごとにモデルを読み直す簡易関数なので、複数ページには向かない。

  • ocr.process(image, boxes=None, *, frame=0, layout_only=False)PageResultを返す。imageはパス、 PIL.Image、またはocr.prepare(path)が返すPreparedPagePreparedPagewith ocr.prepare(path) as prepared:で使うか、使用後にprepared.close()で閉じる。
  • ocr.run(image, boxes=None)process(...).linesocr.layout(image)は行検出だけを行いBoxの一覧を返す。 どちらも必要なモデルしか読まない。
  • 座標はすべてEXIFの向きを反映した元画像のもの(settings.coordinate_spaceexif_oriented_original)。 処理は長辺3,500pxに縮小した画像で行い、結果は元画像の座標に戻す。
  • boxes=[Box(x, y, w, h, confidence), ...]を与えると行検出を飛ばし、与えた順を読み順、与えた座標をそのまま 結果の座標とする。幅と高さは正、confidenceは0〜1で、boxが元画像と重なっている必要がある。 はみ出した部分は認識用の切り出しで除くが、返す座標は変更しない。
  • 行検出器や行認識器を差し替えるにはOCR(detector=..., recognizer=...)detect(image, conf_threshold, ios_threshold)recognize_result(crop)を実装したオブジェクトであればよい。

PageResultの内容。

フィールド 内容
schema_version 1
width, height EXIFの向きを反映した元画像の大きさ
processed_width, processed_height 縮小後、実際にモデルへ渡した画像の大きさ
frame 多ページ画像のコマ番号(0始まり)
model, settings 行認識モデルの版と、デバイス・スレッド・精度・縮小・余白・閾値
lines 読み順に並んだLineResult
timings 段階ごとの秒数。load, detector_setup, layout, reading_order, recognizer_setup, preprocess, encoder, prefill, decode, total
warnings 行末まで復号できなかった行など

LineResultreading_order, x, y, width, height, detection_confidence(行検出のスコア。与えたboxならその値), raw, koji, plain, stop_reasoneosは終端トークンで停止、repetitionは反復崩壊の打ち切り、max_tokensは上限192トークン、 not_runlayout_only), token_count, timings(行ごとのpreprocess, encoder, prefill, decode)を持つ。

出力ファイル

CLIのJSONはPageResultimage(入力のファイル名)、fingerprintartifacts(同時に書いたファイルのSHA-256)を加えたもの。

{
  "schema_version": 1,
  "width": 6496, "height": 4613,
  "processed_width": 3500, "processed_height": 2485,
  "frame": 0,
  "model": "v18",
  "settings": {"device": "cpu", "threads": 0, "decoder_threads": 0, "encoder_precision": "auto",
               "max_dimension": 3500, "margin": 45, "conf_threshold": 0.3, "ios_threshold": 0.8,
               "coordinate_space": "exif_oriented_original"},
  "lines": [
    {"reading_order": 1, "x": 4239, "y": 1221, "width": 334, "height": 422,
     "detection_confidence": 0.6066901683807373,
     "raw": "<ruby>大印<rt>おほしつし</rt></ruby>", "koji": "大印(おほしつし)", "plain": "大印おほしつし",
     "stop_reason": "eos", "token_count": 12,
     "timings": {"preprocess": 0.008, "encoder": 0.672, "prefill": 0.004, "decode": 0.037}}
  ],
  "timings": {"load": 0.269, "detector_setup": 0.075, "layout": 0.173, "reading_order": 0.003,
              "recognizer_setup": 0.551, "preprocess": 0.434, "encoder": 18.227, "prefill": 0.197,
              "decode": 0.672, "total": 20.61},
  "warnings": ["line 7: generation stopped by repetition"],
  "image": "0003.jpg",
  "fingerprint": {
    "source_sha256": "e2ed654248a0…", "frame": 0,
    "models": {"layout": {"file": "rtmdet-s-1280x1280.onnx", "sha256": "f46267754d40…"},
               "encoder": {"file": "kuzushiji-v18-encoder-fp32.onnx", "sha256": "3b3c359bd426…"},
               "prefill": {"file": "kuzushiji-v18-decoder-prefill-int8.onnx", "sha256": "6f3f19011f8d…"},
               "step": {"file": "kuzushiji-v18-decoder-step-int8.onnx", "sha256": "bf0e72a80716…"},
               "vocabulary": "cf0621e68b09…"},
    "settings": {"device": "cpu", "...": "..."},
    "model": "v18", "plain": false, "layout_only": false, "boxes": null,
    "package_version": "0.2.0", "code_sha256": "dfde67eac894…",
    "runtime_versions": {"numpy": "2.5.2", "pillow": "12.3.0", "onnxruntime": "1.29.0", "onnx": "1.22.0"}
  },
  "artifacts": {"0003__5de8f1b5d7a5d622.txt": "ca6949193d5e…"}
}

txtはkoji--plainならplain)を読み順に1行ずつ並べたもの。上の例は構造を示すため、行の一部とハッシュ値を省略している。

ブラウザ版との違い

  • UI(画像ビューア、bboxの編集、縦書き表示、PDF/HEICの読み込み、IIIF、LLM連携)は含まない。多ページTIFFは読める。
  • encoderはfp16版を使う。ブラウザ版のWebAssembly経路が使うint8版のConvInteger演算はonnxruntimeのCPU/CUDAプロバイダに無い。 CPUではfp16版をfp32に変換したファイルを使う。対応する版はfp16 encoderが配布されているv16fs / v17 / v18。
  • decoderは--device cudaでもCPUで動く。
  • 拡大縮小と回転はPillow(縮小はLanczos、回転はbicubic)で、ブラウザのcanvasとは補間が異なる。
  • 画像の端にかかる行では傾き推定の二値化閾値が異なる。ブラウザ版は画像外の画素を透明(輝度0)のまま平均に入れ、 この移植は白で埋めてから平均を取る。処理画像で既定の余白45pxが画像外にはみ出す行に影響する。
  • 行検出はRTMDetのみ(ブラウザ版の5クラスYOLOは含まない)。

環境変数

  • HONKOKU_OCR_MODELS … モデルの保存先(既定~/.cache/honkoku-ocr/models)。fp32変換したencoder(366MB)と来歴ファイル <name>.jsonも同じ場所に置く。
  • HONKOKU_OCR_MODEL_URL … モデル配信元(既定は原著作物と同じ公開バケット)

トラブルシューティング

  • CUDAExecutionProvider is not availableuv sync --extra gpuでonnxruntime-gpuとCUDA 12 / cuDNN 9のランタイムを入れる。 cpuとgpuのextraは同時に入らない。--device cpuに戻せば動く。
  • SHA-256 mismatch / size ... != expected … 取得途中で壊れたか配信元が変わった。~/.cache/honkoku-ocr/modelsの 該当ファイルを消して再実行する。--verify-cacheでキャッシュ全体を照合できる。
  • missing from model cache in offline mode--offlineまたは--verify-cacheでキャッシュに無いモデルを求めた。 ネットワークのある環境でhonkoku-ocr --download --model v18を先に実行する。
  • CPUで1行に数秒かかる--encoder-precision fp16を指定しているか、fp32変換が失敗している。JSONの fingerprint.models.encoder.file-fp32.onnxになっているか確かめる。
  • DecompressionBombWarning / DecompressionBombError … Pillowの既定は約8,900万画素で警告、その2倍で停止する。 それより大きなスキャンは事前に縮小するか、PIL.Image.MAX_IMAGE_PIXELSを上げる。
  • box must overlap the EXIF-oriented image--boxesのbboxが画像と重なっていない。座標は回転を反映した 元画像のもの。少しはみ出したboxは認識時に画像内へ切り詰め、返す座標は元のままにする。
  • メモリ … CPUのfp32 encoderは約1GB、CUDAのfp16 encoderはVRAM約1GBを使う。ワーカーを並列に立てるなら その分だけ増える。

テスト

uv sync --extra cpu
uv run pytest
uv run ruff check .

CIはPython 3.10〜3.14でlintとテストを走らせ、ビルドしたwheelから語彙ファイルが読めることを確かめる。

ライセンスと帰属

このリポジトリのPythonコードとテストはMITライセンス(LICENSE)。

原著作物に由来する部分はそれぞれの著作者のCC BY 4.0のままである(LICENSES/CC-BY-4.0.txt、 一覧はNOTICE.md): みんなで翻刻OCR(橋本雄太)— モデル、語彙ファイルhonkoku_ocr/config/docs/tech.html、および移植元となった推論手順。 NDL古典籍OCR-Lite(国立国会図書館)— 行検出モデル、XY-Cutの手続き。 学習データは「みんなで翻刻」の翻刻成果に基づく。

引用する場合は原著作物を挙げること: 橋本雄太「みんなで翻刻OCR — 市民の力で作ったくずし字AI-OCR」 https://yuta1984.github.io/honkoku-ocr-web/

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

honkoku_ocr_py-0.2.0.tar.gz (490.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

honkoku_ocr_py-0.2.0-py3-none-any.whl (107.4 kB view details)

Uploaded Python 3

File details

Details for the file honkoku_ocr_py-0.2.0.tar.gz.

File metadata

  • Download URL: honkoku_ocr_py-0.2.0.tar.gz
  • Upload date:
  • Size: 490.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for honkoku_ocr_py-0.2.0.tar.gz
Algorithm Hash digest
SHA256 86575b790fd1e50ea68c230e60b2b17117f23271111f29e4810d5d6e6c01448f
MD5 1674fee466e3abb1a3835697f56751c1
BLAKE2b-256 bd741f7eaab97f7ec107a1e1dcd7dbeb9bd21e5ca5793bb6846393b7f84556ea

See more details on using hashes here.

File details

Details for the file honkoku_ocr_py-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: honkoku_ocr_py-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 107.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for honkoku_ocr_py-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2619ede78dd83c34be1060e82274f1550437dd5fa4dfb2b3df1982d688cdc166
MD5 8425becbf332fa4795a11200fe67cb48
BLAKE2b-256 ec5835cc2ce55258e190852db8fb2c351b6beac32fc5173dafd44859e02121bf

See more details on using hashes here.

Release history Release notifications | RSS feed

0.3.0

2 files

This release

0.2.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page