国立国会図書館のOCRライブラリが凄くよかった件(Windows向け)

# はじめに

日本語OCR, 有料の業務用ソフトには色々と高性能なものがあるんですが、無料の場合の選択肢は意外に限られてます。最近ではGeminiなどにOCRさせることも試みてますが、縦書きに弱いのが欠点。加えて、私がやっているような著作権が切れた戦前の本のデジタル化の場合、認識率の低い旧字体が多いのが悩みの種。

そこで最近知ったのが、国立国会図書館が公開しているOCRライブラリ、[NDLOCR](https://lab.ndl.go.jp/news/2023/2023-07-12/)です。国会図書館のデジタル資料（[国立国会図書館デジタルコレクション](https://dl.ndl.go.jp/))から全文テキストデータを作成するために開発されたとのこと。

NDLOCRは、現在ver2.1が[Githubに公開](https://github.com/ndl-lab/ndlocr_cli)されています。古い本が多い国会図書館の資料向けに作られているだけあって、旧字体でも高い精度で認識してくれます。今回は、このライブラリを試してみました。基本、Githubサイトに書いてある通りなんですが、いくつか注意すべき点をメモしておきたいので、記事にしました。

# 前提条件

Githubサイトにある通り、NVIDIA Driverが

- Linuxの場合: 450.36.06以上
- Windowsの場合:520.06以上

である必要があります。Macでは動かないです。私はWindowsをつかってますので、以下はWindowsだけの情報になります。

# Dockerの準備

[Docker Desktopをダウンロード](https://www.docker.com/ja-jp/products/docker-desktop/)して、インストールしてください。

インストールが済んだらPCを再起動してください。再起動するとDocker Desktopが開いてセットアップの続きになるはずです。デフォルトの設定では Docker DesktopはWSL(Windows Subsystem for Linux)を使うのですが、エラー画面とコンソールが出て、WSLのアップデートを要求されるかもしれません。その時は指示通りアップデートしてください。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/4b25e1ea-1c2a-41d6-a8f0-5a96c088cdb9.png" width=50%>

アップデートが終わったら、マシンを再起動し、再起動後にDocker Destopを起動して下さい。ユーザー登録などを済ませれば使用開始できます。

あと、そこそこリソースを使うので、[こちらの記事](https://qiita.com/shibahead/items/709082d910f30f5c7b8b)を参考に.wslconfigを作成または修正して、割り当てメモリを増やしておきましょう。私はそこそこのマシンを使ってるので、以下のような設定にしています。

```
[wsl2]
memory=96GB
processors=16
swap=32GB
localhostForwarding=true
```
設定が終わったら
```
wsl --shutdown
```
をコマンドプロンプトで実行してください。おそらくDocker Desktopがエラーメッセージを出すと思いますが、Quitして**Docker Desktopを改めて起動してください**（以下の作業で必須）。

# Githubレポジトリのクローン

コマンドプロンプトを開き、レポジトリをおきたいディレクトリへ移動して、

```
git clone --recursive https://github.com/ndl-lab/ndlocr_cli
```

を実行してください。

# Dockerコンテナのビルド

公式にある通り

```
cd ndlocr_cli
docker\dockerbuild.bat
```
でビルドします。そこそこ時間かかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/78794a0d-954e-4733-84a2-ec30026bbe80.png)

ビルドが終わったら

```
docker images
```
で、ビルドしたimageがあるかどうか確認しましょう。

```
C:\Users\yanosen\Downloads\ndlocr_cli>docker images
REPOSITORY                    TAG       IMAGE ID       CREATED        SIZE
ocr-v2-cli-py38               latest    1940bd9e523a   14 hours ago   23.2GB
```

# フォルダのマウント

Docker内で読ませる画像ファイルが入っているフォルダをマウントさせます。dockerフォルダ内にあるrun_docker.batを書き換えて設定します。たとえば、

```
docker run --gpus all -d --rm --name ocr_cli_runner -v /c/Users/yanosen/Documents/ocr_images:/root/tmpdir/img -i ocr-v2-cli-py38:latest
```
であれば、Dockerコンテナ起動時に、Windows上のDocuments/ocr_imagesフォルダがDocker内の/root/tmpdir/imgとしてマウントされます。(*yanosen*はダミーのアカウント名なので、自分用に書き換えてください）


# Dockerコンテナの起動

```
docker\run_docker.bat
```
で起動してください。Docker Desktopで

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/a29afcd1-1bd9-4268-9242-d0588b50621c.png)

のように緑のランプが出るはずです。この状態であれば、run_docker.batを推論のたびに走らせる必要はありません。

止める時は

```
docker stop ocr_cli_runner
```
を実行します。

# 推論の実行
まず、Dockerにログインします。
```
docker exec -it ocr_cli_runner bash
```
画像のような、Docker内のコマンドプロンプトになるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/1f2b5c64-d2e0-4cc5-abe6-1384dd2a3a7d.png)

ここで以下のコマンドで推論を実行してください
```
python main.py infer /root/tmpdir /root/tmpdir/output -s s
```
ここの```/root/tmpdir```は、先ほどマウントしたimgフォルダの親フォルダのパスを使ってます。```/root/tmpdir/output```は、すでにそのフォルダがあると別の名前のフォルダを勝手に作ってそこにアウトプットを保存しますので注意してください。それを避けたいなら

```
rm -rf /root/tmpdir/output
```
でフォルダを削除しておいてください。推論が終わると以下のようになります。

```
### Page Deskew Process ###
### Layout Extraction Process ###
### Line OCR Process ###
Predicting DataLoader 0: : 1it [00:00,  1.10it/s]
### Line Order Process ###
### Ruby Read Process ###
### Line Attribute Process ###
########  END PAGE INFERENCE PROCESS  ########
================== PROCESSING TIME ==================
Average processing time (0_page_sep)         :   1.3004 sec / image file
Average processing time (1_page_deskew)      :   0.0385 sec / image file
Average processing time (2_layer_ext)        :   0.6567 sec / image file
Average processing time (3_line_ocr)         :   4.6434 sec / image file
Average processing time (ex1_line_order)     :   0.0117 sec / image file
Average processing time (ex2_ruby_read)      :   0.0091 sec / image file
Average processing time (ex3_line_attribute) :   0.1655 sec / image file
Average processing time (total)              :   6.8253 sec / image file
```
終わったらExitでDockerから出てください。

## 結果をとりだす

結果をdockerから取り出しましょう。上に書いた通りExitしたあとに

```
docker cp ocr_cli_runner:/root/tmpdir/output C:\Users\yanosen\Documents\
```

を実行して、アウトプットをDocker内からWindows上のDocumentsフォルダにコピーします。Documents\outputフォルダを見ると、

```
03/26/2025  06:55 AM    <DIR>          .
03/26/2025  06:56 AM    <DIR>          ..
03/26/2025  06:55 AM    <DIR>          .text_recognition
03/26/2025  06:55 AM             2,025 opt.json
03/26/2025  06:55 AM    <DIR>          tmpdir
```
のように、3つのフォルダが存在することがわかります。出力はtmpdir\textフォルダにあり、

```
03/26/2025  06:55 AM                 1 doc_Page_024_cap.txt
03/26/2025  06:55 AM               877 doc_Page_024_main.txt
03/26/2025  06:55 AM             1,130 doc_Page_024_ruby.txt
```
という3つのファイルが生成されていることが確認できます。main.txtに読み取り結果があり、rubyは推定された読み仮名です（ただ、あまり精度は高くない）。

## 細かい指定でカスタマイズ

### 処理をスキップする

認識実行時にオプションを設定することで、処理をカスタマイズできます。たとえば、ノド元分割（広げた本の画像を中央で分割する）や傾き補正が必要ないなら、相当するプロセス番号0,1をスキップして、レイアウト抽出(プロセス番号:2)、文字認識(プロセス番号:3)だけを実行するよう指定することができます。

```
python main.py infer /root/tmpdir /root/tmpdir/output -s s -p 2..3
```

一方、読み順認識、漢字ルビ推定見出し・著者認識をスキップするには、config.ymlのline_order、ruby_read、add_title_authorのオプションを変更する必要があります。TrueをFalseに変えれば無効化できます。

```config.yml
line_order: True
ruby_read: True
line_attribute:
  add_title_author: True
  classifier: 'rf'
  title_model: 'submodules/text_recognition_lightning/models/rf_title/model.pkl'
  author_model: 'submodules/text_recognition_lightning/models/rf_author/model.pkl'
```
[こちら](https://github.com/ndl-lab/ndlocr_cli/blob/master/config.yml)にあるconfig.ymlの内容をコピー修正したものを保存し、推論実行時に```-c```オプションでパスを指定します。一番簡単なのは、上で画像用にマウントしたWindowsのフォルダに保存する方法で、そうすればDockerで推論を開始する時に、以下のように指定できます。

```
python main.py infer /root/tmpdir /root/tmpdir/output -c /root/tmpdir/img/config.yml
```

config.ymlでは、OCRの際に柱（章タイトルなどページの端にあるテキスト）、ノンプル（ページ番号）、ルビを無視するよう指定することもできます。無視する時は、以下のパラメータをTrueからFalseに変えてください。
```
line_ocr:
  char_list: 'submodules/text_recognition_lightning/ndldata/mojilist_NDL.txt'
  saved_model: 'submodules/text_recognition_lightning/models/resnet-orient2.ckpt'
  additional_elements:
    柱: True
    ノンブル: True
    ルビ: True
```



### 行の座標を取得する
日本語の新書などで、縦書きで本文は右から左なのが明らかなのに、行の順番を間違えてしまうことがあります。その場合は、```-x```オプションを使って、xmlを出力してください。

```
python main.py infer /root/tmpdir /root/tmpdir/output -s s -x
```
すると、以下のようなxmlが得られます。
```
<?xml version='1.0' encoding='utf-8'?>
<OCRDATASET><PAGE IMAGENAME="0055_0000_L.jpg" WIDTH="1900" HEIGHT="2538">
  <TEXTBLOCK CONF="0.992">
    <LINE TYPE="本文" X="1828" Y="636" WIDTH="35" HEIGHT="1383" CONF="0.996" STRING="稻手塚氏によりて、有名なる外國の傑作に費された著作期間の長短を、調査の勞も無くて〓" ORDER="0" TITLE="FALSE" AUTHOR="FALSE" />
  <LINE TYPE="本文" X="1768" Y="597" WIDTH="36" HEIGHT="1420" CONF="1.000" STRING="く知る事を得たのであつた。著作完了期間の長い中に算ふべきものには、有名なゲーテのファ" ORDER="1" TITLE="FALSE" AUTHOR="FALSE" />
    <LINE TYPE="本文" X="1704" Y="600" WIDTH="38" HEIGHT="1427" CONF="1.000" STRING="フストがあり、五十八年を費したと謂はれてゐるが、馬琴が八犬傳、一九が膝栗毛も可なり長" ORDER="2" TITLE="FALSE" AUTHOR="FALSE" />
    <LINE TYPE="本文" X="1640" Y="602" WIDTH="39" HEIGHT="1417" CONF="1.000" STRING="く、廿年の星霜を費したと云ふ。併し此等は皆他の著述も書き物もしつゝであるから、一〓に" ORDER="3" TITLE="FALSE" AUTHOR="FALSE" />
    <LINE TYPE="本文" X="1572" Y="600" WIDTH="39" HEIGHT="1427" CONF="1.000" STRING="歳月の數を打算して云々する事も出來まいが、式部も亦寡居中こそ大分打ちかゝる事も出來た" ORDER="4" TITLE="FALSE" AUTHOR="FALSE" />
    <LINE TYPE="本文" X="1511" Y="598" WIDTH="37" HEIGHT="1427" CONF="1.000" STRING="であらうが、宮仕の間にも、大分執筆したものと假定し、且殊に紙なども豊富には容易に得難" ORDER="5" TITLE="FALSE" AUTHOR="FALSE" />
    <LINE TYPE="本文" X="1447" Y="605" WIDTH="38" HEIGHT="1423" CONF="1.000" STRING="い當時、なるべく書損じなども少なくなどゝ注意しつゝ、墨を摺り筆を整へて、丁寧に書き記" ORDER="6" TITLE="FALSE" AUTHOR="FALSE" />
    <LINE TYPE="本文" X="1385" Y="596" WIDTH="36" HEIGHT="1433" CONF="1.000" STRING="したであらう事を思へば、返す〴〵もあの長篇の著作、而かも隨分推敲練熟したあとの見える" ORDER="7" TITLE="FALSE" AUTHOR="FALSE" />
    <LINE TYPE="本文" X="1321" Y="602" WIDTH="39" HEIGHT="1421" CONF="1.000" STRING="源氏物語は、何う考へても如何に天才の式部が筆だとて、終生の作品であつたらうと云ふ事は、" ORDER="8" TITLE="FALSE" AUTHOR="FALSE" />
    <LINE TYPE="本文" X="1260" Y="596" WIDTH="35" HEIGHT="729" CONF="1.000" STRING="先づ大した間違の無い推定であらうと思はれる。" ORDER="9" TITLE="FALSE" AUTHOR="FALSE" />
    <SHAPE><POLYGON POINTS="1266,605,1255,623,1252,705,1255,1085,1242,1131,1251,1184,1241,1194,1236,1227,1240,1706,1251,1336,1265,1303,1285,1297,1291,1311,1318,1622,1324,2004,1331,2016,1352,2022,1841,2022,1852,2000,1852,662,1846,614,1822,601" /></SHAPE>
    </TEXTBLOCK>
  <TEXTBLOCK CONF="0.948">
    <LINE TYPE="本文" X="1199" Y="633" WIDTH="31" HEIGHT="203" STRING="異説について" ORDER="10" TITLE="FALSE" AUTHOR="FALSE" />
  <SHAPE><POLYGON POINTS="1202,633,1199,635,1199,833,1200,835,1202,835,1203,836,1228,836,1230,833,1230,636,1229,635,1229,633" /></SHAPE>
    </TEXTBLOCK>
  <LINE TYPE="本文" X="1197" Y="863" WIDTH="38" HEIGHT="1162" CONF="0.989" STRING="猶此の源氏物語の著作に就いて、古來傳はつた一説には、父爲時の原作を式" ORDER="11" TITLE="FALSE" AUTHOR="FALSE" />
  <TEXTBLOCK CONF="0.949">
    <LINE TYPE="本文" X="1137" Y="604" WIDTH="35" HEIGHT="1420" CONF="1.000" STRING="部が修飾したといひ、式部の書いたのを道長が加筆したとか、又當時〓に世に知られた源氏の" ORDER="12" TITLE="FALSE" AUTHOR="FALSE" />
    <LINE TYPE="本文" X="1073" Y="603" WIDTH="38" HEIGHT="1421" CONF="0.999" STRING="物語と云ふ書のあるのに、式部が書き繼いだものであるといふ樣な異説もあつたが、是等は今は" ORDER="13" TITLE="FALSE" AUTHOR="FALSE" />
    <LINE TYPE="本文" X="1011" Y="596" WIDTH="38" HEIGHT="1430" CONF="0.998" STRING="否定せられる事になつた樣である。と同時に宇治十帖は、女の大貳三位の著だといふ説、猶他" ORDER="14" TITLE="FALSE" AUTHOR="FALSE" />
  <SHAPE><POLYGON POINTS="1016,603,1011,622,1012,2003,1021,2025,1175,2025,1188,2016,1202,2017,1205,1849,1202,1205,1195,1203,1190,1235,1191,1301,1188,1312,1180,1299,1184,1238,1178,1188,1178,627,1174,608,1166,603" /></SHAPE>
    </TEXTBLOCK>
  <BLOCK TYPE="柱" X="959" Y="821" WIDTH="26" HEIGHT="457" CONF="0.959" STRING="三翌氏物語の〓出期、〓育期、及び大哉〓" />
  <BLOCK TYPE="ノンブル" X="963" Y="1773" WIDTH="23" HEIGHT="41" CONF="0.982" STRING="六七" />
```
この中のTYPEは行の種類（「本文」がメインのテキスト）、X,Yは、認識された行の座標を示しています。Xが大きいほどページの右になるので、「本文」の行をXが大きい順に行をならべていけば、右から左へ行を並べることができます。通常は必要ないですが、行の順序ミスを頻発する（改行、段落分けが多いラノベなどで起こりやすいように見えます）場合は試してください。

## (オプション）Visual Studio Codeを使う。
Visual Studio CodeのDocker拡張機能を使うと、作業がかなり楽になります。拡張機能をインストールすると、左の柱にDockerのあいこんがでてくるので、それをクリックすると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/0c25db79-3699-4dd2-810c-1753f88f4352.png)

のように、ocr...コンテナが出てきます。これを右マウスボタンクリックでAttach Visual Codeを選ぶと、アタッチされたコンテナ用のウィンドウが出てきます。ここでExplorerでrootを選べば、先ほど使ったtmpdirなどのフォルダを見ることができるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/89f35e34-cdeb-4ad9-b83d-b2af21e34c37.png)

outputフォルダを右クリックすれば、PCに「ダウンロード」もできます。

## 注意点
- メモリが少ない場合、処理するファイル数が多すぎるとエラーになって推論が途中で止まってしまうようです。16GB程度のメモリなら、一度に処理するのは30枚くらいにした方が良さそうです。
- 画質が悪いと認識率が下がるようです。また、国会図書館が使っている画像は本の周囲の余白が大きいせいなのか、画像の端ギリギリまで文字があるとき、境界に近い字の認識が下がる傾向があるようです。