なぜOCRが必要になったのか?
要求があり構築を進めた全文検索システムですが、環境を作る前にそもそも問題がありまして…
PDFは基本押印されたものをスキャンして保存しているという点。
つまり、PDFファイル内にテキストデータは保存されていない状態ということです。
画像ファイルと同じってことですね。
まぁ、全文検索できないですよね。
検索キーワードを紐づけるデータがそもそもファイルに書いてないわけですから。
しかもPDFにはパスワードも設定しているらしく、色々と難航しそうなので
半分諦めモードでしたが…
調べてみるとそれなりに解決方法があるようなので、機械的な文書起こしでも
ないよりはマシかと思い、やってみることにしました。
OCRするために使うツール
- tesseract-ocr
-
画像データをOCRして抽出した文字情報を埋め込んだPDFファイルを生成するために使用します。
この作業で一番処理に時間のかかる工程になります。
インストールするパッケージは以下の2つ。
tesseract tesseract-langpack-jpn
- poppler-utils
-
poppler-utilsはPDFのコマンドラインツールらしいです。今回は元のPDFをpngに変換する、
元のPDFから情報(パスワード設定されているか、印刷可能か)を参照するために使用します。
以下の2つのコマンドをこのパッケージから使用しています。
pdfinfo pdftoppm - pdftk
-
こちらもPDFのコマンドラインツールなんですが、メインはPDFファイルの結合に使用します。
PDFの結合自体はpoppler-utilsに含まれているpdfuniteコマンドでも実装できるのですが、
PDFファイルにパスワードを設定すること、印刷可否をコントロールすること
これができません。
一見pdftkのほうが高機能な感じがするのですが、PDFをpngファイルに変換する機能はありません。
なので、今回の作業では両方のパッケージが必要となりました。
必要なパッケージのインストール
tesseract-ocr
tesseractはCentOS標準リポジトリでは提供されていません。
なので、epelのリポジトリを追加してインストールします。
# epelをインストール
$ sudo yum install epel-release
インストール:
epel-release.noarch 0:7-11
完了しました!
# tesseractをインストール
$ sudo yum install tesseract tesseract-langpack-jpn
インストール:
tesseract.x86_64 0:3.04.00-3.el7 tesseract-langpack-jpn.noarch 0:3.04.00-3.el7
依存性関連をインストールしました:
cairo.x86_64 0:1.15.12-3.el7 dejavu-fonts-common.noarch 0:2.33-6.el7
dejavu-sans-fonts.noarch 0:2.33-6.el7 fontconfig.x86_64 0:2.13.0-4.3.el7
fontpackages-filesystem.noarch 0:1.44-8.el7 fribidi.x86_64 0:1.0.2-1.el7
giflib.x86_64 0:4.1.6-9.el7 graphite2.x86_64 0:1.3.10-1.el7_3
harfbuzz.x86_64 0:1.7.5-2.el7 jbigkit-libs.x86_64 0:2.0-11.el7
leptonica.x86_64 0:1.72-2.el7 libICE.x86_64 0:1.0.9-9.el7
libSM.x86_64 0:1.2.2-2.el7 libX11.x86_64 0:1.6.5-2.el7
libX11-common.noarch 0:1.6.5-2.el7 libXau.x86_64 0:1.0.8-2.1.el7
libXdamage.x86_64 0:1.1.4-4.1.el7 libXext.x86_64 0:1.3.3-3.el7
libXfixes.x86_64 0:5.0.3-1.el7 libXft.x86_64 0:2.3.2-2.el7
libXrender.x86_64 0:0.9.10-1.el7 libXxf86vm.x86_64 0:1.1.4-1.el7
libglvnd.x86_64 1:1.0.1-0.8.git5baa1e5.el7 libglvnd-egl.x86_64 1:1.0.1-0.8.git5baa1e5.el7
libglvnd-glx.x86_64 1:1.0.1-0.8.git5baa1e5.el7 libicu.x86_64 0:50.1.2-17.el7
libjpeg-turbo.x86_64 0:1.2.90-6.el7 libthai.x86_64 0:0.1.14-9.el7
libtiff.x86_64 0:4.0.3-27.el7_3 libwayland-client.x86_64 0:1.15.0-1.el7
libwayland-server.x86_64 0:1.15.0-1.el7 libwebp.x86_64 0:0.3.0-7.el7
libxcb.x86_64 0:1.13-1.el7 libxshmfence.x86_64 0:1.2-1.el7
mesa-libEGL.x86_64 0:18.0.5-4.el7_6 mesa-libGL.x86_64 0:18.0.5-4.el7_6
mesa-libgbm.x86_64 0:18.0.5-4.el7_6 mesa-libglapi.x86_64 0:18.0.5-4.el7_6
pango.x86_64 0:1.42.4-2.el7_6 pixman.x86_64 0:0.34.0-1.el7
完了しました!
tesseractは依存関係が多いので結構追加パッケージが入りますね。
変換作業もCPUリソースを食うので、PDFコンバート用サーバが用意できるなら
別に用意するほうが良いかもしれません。
追加手順
yumでtesseractをインストールしてtesseractを実行すると、以下のようなエラーが出ます。
Tesseract Open Source OCR Engine v3.04.00 with Leptonica
Error opening data file /usr/share/tesseract/tessdata/osd.traineddata
Please make sure the TESSDATA_PREFIX environment variable is set to the parent directory of your "tessdata" directory.
Failed loading language 'osd'
Tesseract couldn't load any languages!
Warning: Auto orientation and script detection requested, but osd language failed to load
理由はよくわかりませんが、言語ファイルを読み込むための大元のファイル、"osd.traineddata"というファイルが
初期状態で欠如してしましっているようです。
なので、バージョンに合わせた"osd.traineddata"ファイルを取得して、/usr/share/tesseract/tessdata/に
保存します。
$ cd /usr/share/tesseract/tessdata/
$ sudo curl -OL https://github.com/tesseract-ocr/tessdata/raw/3.04.00/osd.traineddata
これで、tesseractが正常に動作して日本語を読み込める状態になります。
##poppler-utils
こちらは標準レポジトリでインストールできます。
$ yum install poppler-utils
インストール:
poppler-utils.x86_64 0:0.26.5-20.el7
依存性関連をインストールしました:
lcms2.x86_64 0:2.6-3.el7 openjpeg-libs.x86_64 0:1.5.1-18.el7 poppler.x86_64 0:0.26.5-20.el7
poppler-data.noarch 0:0.4.6-3.el7
完了しました!
pdftk
pdftkのインストールはまた一癖ありました。
nuxというリポジトリからrpmでリポジトリを追加してpdftkをインストールします。
$ sudo rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm
http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm を取得中
警告: /var/tmp/rpm-tmp.5Nweqm: ヘッダー V4 RSA/SHA1 Signature、鍵 ID 85c6cd8a: NOKEY
準備しています... ################################# [100%]
更新中 / インストール中...
1:nux-dextop-release-0-5.el7.nux ################################# [100%]
そのあと、yumでインストール。
$ sudo yum install pdftk
インストール:
pdftk.i686 0:2.02-1.el7.nux
完了しました!
これでpdftkが使えるかと思うと…エラーが出ます。
どうやら64ビット用のpdftkはないようでして、32ビット用しかないようです。
(pdftk.i686ですからね…)
なので、追加で以下3点をインストールします。
$ sudo yum install ld-linux.so.2
インストール:
glibc.i686 0:2.17-260.el7_6.5
依存性関連をインストールしました:
nss-softokn-freebl.i686 0:3.36.0-5.el7_5
完了しました!
$ sudo yum install libstdc++.so.6
インストール:
libstdc++.i686 0:4.8.5-36.el7_6.2
依存性関連をインストールしました:
libgcc.i686 0:4.8.5-36.el7_6.2
完了しました!
$ sudo yum install libz.so.1
インストール:
zlib.i686 0:1.2.7-18.el7
完了しました!
これで、無事pdftkが使える状態になります。
ここからは、変換作業になります。
上記ツールを使って画像PDFをテキスト埋め込みPDFに変換する
一つ一つのファイルを操作するのは流石に面倒なので、スクリプト化してしまいました。
#!/bin/bash
# 引数のチェック
if [ $# -eq 0 ]; then
echo "usage : $ pdfconvert.sh [対象ディレクトリ] [pdfパスワード]"
echo " [対象ディレクトリ]は必須"
echo " [pdfパスワード]を指定しない場合、作成したPDFにパスワードは設定されません."
echo "終了します."
exit 1
fi
# 引数に指定したディレクトリが存在しない場合は処理を中止する
if [ ! -d $1 ]; then
echo "指定したディレクトリが存在しません."
echo "処理を終了します."
exit 1
fi
# PDFパスワードを読み込む
if [ ! x"$2" = "x" ];then
PWDTXT=$2
else
PWDTXT=0
fi
# PDFファイルのリスト化
LIST=`find "$1" -type f -name "*.pdf"`
### リストをループに読み込み ###
for TARGET in ${LIST}; do
# 拡張子を除いたファイル名を抽出
FILENAME=`basename ${TARGET} .pdf`
# ディレクトリ名を抽出
DIRNAME=`dirname ${TARGET}`
# パスワード付きPDFかをチェック
PDFPWD=`pdfinfo ${TARGET} | grep "^Encrypted" | awk '{ print $2;}'`
# 画像保存ディレクトリの作成
TMPDIR=${TARGET}.d
mkdir -p ${TMPDIR}
# 画像変換処理実行
pdftoppm -png -r 200 ${TARGET} ${TMPDIR}/page
# 文字抽出したPDFを作成
find "${TMPDIR}/" -type f -name "*.png" | sed 's/\.png$//' | xargs -P8 -n1 -I% tesseract %.png % -l eng+jpn pdf
# 作成したPDFを1ファイルにまとめる
# PDFパスワード有無により処理を分岐
if [ ${PDFPWD} = "yes" ] && [ ${PWDTXT} != "0" ]; then
# ファイルの印刷可否を抽出
PRINTABLE=`pdfinfo ${TARGET} | grep "^Encrypted" | awk '{ print $3;}' | cut -d':' -f2`
# 印刷可否によって処理を分岐
if [ ${PRINTABLE} = "yes" ]; then
# パスワード付きで印刷可の場合
pdftk ${TMPDIR}/*.pdf output ${TARGET} owner_pw ${PWDTXT} allow printing
else
# パスワード付きで印刷不可の場合
pdftk ${TMPDIR}/*.pdf output ${TARGET} owner_pw ${PWDTXT}
fi
else
# パスワード無の場合
pdftk ${TMPDIR}/*.pdf output ${TARGET}
fi
# 作成したディレクトリを削除する
rm -rf ${TMPDIR}
### リストループ終了 ###
done
ざっと仕様を記載します。
- 第一引数は、変換するファイルが格納されたディレクトリを指定します。
- 第二引数は、変換後のPDFファイルに設定するパスワードを指定します。
- 第一引数に指定されたディレクトリ内のPDFファイルはテキスト埋め込みPDFに置換されます。オリジナルを取っておく必要がある場合は、必ずバックアップしてから実行してください。
- 元のPDFファイル名.dフォルダを一時ディレクトリとして作成し、その中にpngファイルを作成します。作成したpngファイルをtesseractでOCRしてpngファイルと同名のテキスト埋め込みPDFファイルを作成し、最後にpdftkで結合と同時にパスワード設定、印刷設定をしています。
- ログ出力機能は実装していません。必要は方は"/bin/bash -x pdfconvert.sh [対象ディレクトリ] [pdfパスワード] > /tmp/pdfconvert.log 2>&1 &"的にコマンドを工夫してログを出力させてください。
- 細かいところはスクリプトをご覧ください。イケてないところ、間違いがございましたらご指摘頂けると幸いです。
最後に
上記スクリプトで1.2GB弱、1500程度のファイルを変換するのに約2日かかりました。
CPUの性能にもよるとは思いますが、Xeron X3430でxargs8パラレルで実行してこれだけ時間がかかりました。
参考程度ですが、検討する際に数値をご検討にご利用いただければと思います。
変換後のファイルサイズですが、1.2GB弱→約5GBに増加しました。
この辺もストレージとの相談になると思いますので、数値をご参考までにご利用いただければと思います。
次は、変換したPDFファイルをFessでクロールしてインデックス作成する手順を気が向いたら書きます…