pdfをOCR処理してテキスト付きpdfをつくる（範囲攻撃）

全文検索に引っかかるように、pdf文書にOCR処理をしてテキスト付きpdf化します。
複数ファイルをまとめて処理します。

印刷した活字をキーワードで検索する様な使い方であれば、精度も問題無いと感じました。
今回、追加の学習などはしていませんが、素のままでは手書き文字の変換は難しいですね。
手書きの日本語は「がんばりましょう」な感じでしたが、数字は思った以上に読み取ってくれました。

#作業環境
Windows10 Pro 64bit
Python3.7 / Anaconda

#処理の概要
今回のやり方ではpdfファイルから直接OCR処理できる訳ではありません。今回主役となるtesseractでは、png, jpg等の画像ファイルからでないとテキスト付きpdfが得られないので、下の様に迂回します。
　　○変換したいpdf
　　　　→ページごとに分割 & png化
　　　　→OCR処理
　　　　→バラバラのテキスト付きpdf
　　　　→1つのpdfファイルに結合

「変換したいpdf」が複数ある場合は、これを繰り返します。

#作業手順
「最新のポルシェが最良のポルシェである」の言葉に従い、一番下の各記事を参考にして、それぞれ現時点で最新っぽいVer.をインストールしました。ポルシェに乗ったことはありません。

1）poppler-0.68.0、poppler-data-0.4.10言語ファイルの追加、環境変数pathの設定
　　ー パスを通すというのが「???」で、理解に時間が掛かりました。
2）tesseract-OCR 5.0.0 alpha/2021-05-06
　　ー LSTMとか素敵！
3）pipで以下を導入。
　　ー jupyter内でpipが使えるんですね。

```python
pip install pdf2image    # --> 1.15.1
pip install fitz    # --> 0.0.1.dev2
pip install PyMuPDF    # --> 1.18.14
```
当初、情報が多かったことから、終盤のpdfファイルの結合は PyPDF2 で検証を進めていましたが、最後の最後で「ファイル名に日本語が含まれているとエラーになる」ことがわかり、fitz(PyMuPDF) に変更しました。

#構成とコード
OCR処理したいpdfファイルを "file_in" に配置、pdfOCR.ipynbを実行すると "operate" で作業し "ocr_out" に出力します（出力処理後、"operate" 下のファイルは削除）。
.
┣ pdfOCR.ipynb（プログラム本体）
┣ file_in（対象ファイル投下用）
┣ operate（作業用）
┗ ocr_out（OCR後のpdf出力用）

```python:pdfOCR.ipynb
import os
import glob
import pdf2image
import fitz

input_dir = 'file_in\\'
operate_dir =  'operate\\'
output_dir = 'ocr_out\\'

only_pdf = '*.pdf'    #pdfに限定
only_png = '*.png'
all_f = '*.*'

input_dir_pdf = input_dir + only_pdf
operate_dir_png = operate_dir + only_png
operate_dir_pdf = operate_dir + only_pdf
operate_dir_all = operate_dir + all_f

input_files = glob.glob(input_dir_pdf)    #入力pdfファイルのリスト取得

for ipf in input_files:
    images = pdf2image.convert_from_path(ipf)    #dpi=200
    file_name = os.path.splitext(ipf)[0]
        
    for index, image in enumerate(images):    #ページごとに分割してpng保存 --> operate_dir
        img_fn = file_name + '-' + str('{:0=4}'.format(index+1))    #-0000
        img_fn_op = img_fn.replace(input_dir,operate_dir)
        image.save(img_fn_op + '.png')

png_files = glob.glob(operate_dir_png)    #作業用ディレクトリ内pngのリスト取得

for pnf in png_files:    #OCR処理のメイン：png --> pdf+OCR
    pdf_name = os.path.splitext(pnf)[0]
    !tesseract {pnf} {pdf_name} -l jpn+eng --psm 3 pdf
    
pdf_files = glob.glob(operate_dir_pdf)    #作業用ディレクトリ内pdfのリスト取得

merge_fns=[]    #入出力複数ファイル対応用の箱

for iof in input_files:    #入力ファイル名 --> 出力ファイル名をつくる
    fna = os.path.splitext(iof)[0]
    merge_fn = fna + '_OCR_.pdf'
    merge_fn_out = merge_fn.replace(input_dir, output_dir)
    merge_fns.append(merge_fn_out)

for mfn in merge_fns:    #同じファイル名のpdfを結合
    mf = os.path.splitext(mfn)[0][:-5]
    mfc = mf.replace(output_dir, '')
    
    mg_f = fitz.open()  #結合先

    for pff in pdf_files:
        pf = os.path.splitext(pff)[0][:-5]
        pfc = pf.replace(operate_dir, '')

        if mfc == pfc:
            cmb_f = fitz.open(pff)  #結合するページ
            
            mg_f_lp = len(mg_f)
            cmb_f_lp = len(cmb_f)
            
            mg_f.insertPDF(cmb_f, from_page=0, to_page=cmb_f_lp,  #結合するファイルの最初から最後までを
                          start_at=mg_f_lp)                       #結合先の最後のページに続けて
            
            cmb_f.close()
    mg_f.save(mfn)

for rmvf in glob.glob(operate_dir_all):    #作業用ディレクトリのファイル削除
    if os.path.isfile(rmvf):
        os.remove(rmvf)
```
tesseractでは、jpn+eng → eng+jpn にすると精度が変わるかもしれません。また文章の抽出ではなく、キーワード検索のレベルであれば、psm6より3の方が良さそうでした。それほど深く追求した訳ではないですけど。
変換したいpdfファイルは、10,000ページ未満/ファイル を想定してます。

#備考
元のファイル名にスペースが含まれているとtesseractのエラーが出ます。事前に処理しておきましょう。

#参考
ありがたや、ありがたや。
　　[Poppler : PDFのコマンドラインツール](http://pdf-file.nnn2.com/?p=863)
　　[Windows 10でPath環境変数を設定／編集する](https://www.atmarkit.co.jp/ait/articles/1805/11/news035.html)
　　[Tesseract at UB Mannheim](https://github.com/UB-Mannheim/tesseract/wiki)
　　[PythonとTesseract OCRで文字認識](https://qiita.com/henjiganai/items/7a5e871f652b32b41a18)
　　[PythonでPDFを複数まとめて自動結合！日本語ファイルを1つに集約](https://fastclassinfo.com/entry/python_joins_pdf/)
　　[スキャンしたpdfファイルにOCR処理を行い,テキスト埋め込みpdfを作成する](https://qiita.com/ssh0/items/1a6e310282f1bc6dd75f)
　　[Pythonでファイル・ディレクトリを削除するos.remove, shutil.rmtreeなど](https://note.nkmk.me/python-os-remove-rmdir-removedirs-shutil-rmtree/)
　　[Qiita Markdown 書き方 まとめ](https://qiita.com/shizuma/items/8616bbe3ebe8ab0b6ca1)