Linux
tesseract-ocr
poppler
pdftk

全文検索サーバを構築した話 その3(OCR環境構築編)


なぜ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に変換する

一つ一つのファイルを操作するのは流石に面倒なので、スクリプト化してしまいました。


pdfconvert.sh

#!/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でクロールしてインデックス作成する手順を気が向いたら書きます…