LoginSignup
27
20

【PaddleOCR】Pythonで簡単に日本語OCR_その2(exe化のおまけつき)

Last updated at Posted at 2023-01-21

0. はじめに

私のQiita記事で今でも週間平均閲覧数が多いのが以下OCRの記事ということで、かなり強い需要があることに気づいたので、同じネタだがAIを搭載したPaddleOCRを扱うことにする。
同じようなレベルのOCRに「EasyOCR」があるが、個人的にはこちらの方が使い勝手がいいと感じてる

本当は話題のexe化ライブラリであるNutikaを使ってexe化させようと思っていたが、無茶苦茶コンパイル時間かかるし、後でわかると思うがPaddleOCRのexe化が超難しくて断念したのは内緒です...

  • 動作環境
    • OS : Windows10 pro
    • python: 3.9.6 
    • PaddleOCR: 2.6.1.2
    • paddlepaddle: 2.4.1
    • pyinstaller: 5.6.2
    • loguru: 0.6.0

純粋にPaddleOCRに興味がある方は「2. PaddleOCRプログラム」まで読んでいただければOKです。

1. ライブラリの準備

poetryでもvenvでも何でもいいので、まずは仮想環境を適当に立ててから・・

1-1. PaddleOCR+paddlepaddle

PaddleOCRとは、中国最大の検索エンジンの「Baidu(百度)」研究機関が開発したOSSであり、PaddlePadlleは、同じくBaiduが提供している深層学習フレームワークである。
今回導入は以下コマンドで行った。(下のpaddlepaddleは公式を確認して従った方がいい)

pip install paddleocr
python -m pip install paddlepaddle==2.4.1 -i https://pypi.tuna.tsinghua.edu.cn/simple

なおPaddleOCRのモデルは以下3種類から成り立っているが、基本はインスタンス化する時の言語指定で最新が自動ダウンロードされる仕組みになっているので、あまり利用者としては気にする必要がない。もし直接扱いたい場合は以下リンクから選べばいい。

・Text Detection Model (det) 
  ⇒ 文字の固まりを検出するモデル ※中国語、英語、その他Multi
・Text Angle Classification Model (cls)
  ⇒ 傾き補正するモデル⇒通常は使わないことが多い ※全言語共通
・Text Recognition Model (rec) 
  ⇒ 文字を推論するモデル ※中国語、英語、日本語・・etc種類が多い

また、この事前学習済モデルは自動で以下に格納される仕様。↑で書いた()内と対応している
C:\Users\***\.paddleocr\whl

実はこいつがexe化の時にもやっかいな動きをするのだが、それはまた後で・・

スクリーンショット 2023-01-21 172030.png

1-2. loguru

また、Nutikaをあきらめた代わりにexe化に必須なloggingにくっそ便利loguruをついでに使用する
import loggingサヨナラ!

pip install loguru

logger.add()でloggerを定義し、後はいつもと同じように書くだけ!らくちん!!

2. PaddleOCRプログラム

2-1. ディレクトリ構成

最終的にexeの際に以下のような構成を考えている。
今の段階ではまだexeはできてないので、exeの位置に.pyがあると思って読んでください。

2-2. OCR対象のサンプル

例にもれず前回記事と同じパブリックドメインである青空文庫より「宮沢賢治」雨ニモマケズを引用させていただく。
※この箇所をスクリーンショットで適当に保存して、dataフォルダに入れてます

2-3. OCRプログラムと出力画像結果

前回の記事との違いは
・OCRにtesseractを使用せずAIの力で性能の向上が見込めるPaddleOCRを使用している
 ⇒英語は圧倒的に強いが、結論としては日本語はどっこいの性能かも??
・print出力ではなく、元画像に日本語を重ねて表示
・loggingとしてloguruを取り入れている

PaddleOCRの出力は
・result[0][0][0][0]  #矩形左上座標
・result[0][0][0][1]  #矩形右上座標
・result[0][0][0][2]  #矩形右下座標
・result[0][0][0][3]  #矩形左下座標
・result[0][1][0]    #テキスト
・result[0][1][1]    #confidence(自信度)
となっている

なお、GPUを使用しなくても気にならないくらい処理は早いです!

sample_ocr.py (OCRプログラム)
# coding: utf-8
from paddleocr import PaddleOCR
from PIL import Image, ImageEnhance, ImageFont, ImageDraw
import numpy as np
import cv2
from loguru import logger
# import logging ←バイバイ!

def japanese_puttext(img, text, position, font, fill = (255, 0, 0)):
    """ cv2.putTextが日本語対応してないので、自分で関数を定義する"""
    img_pil = Image.fromarray(img) # PIL Imageに変換。
    draw = ImageDraw.Draw(img_pil) # drawインスタンスを生成
    draw.text(position, text, font = font , fill = fill) # drawにテキストをのせる
    img = np.array(img_pil) # PILを配列に変換
    return img

def run_ocr(img_path):
    """ OCRメイン関数"""
    #画像読み込み+前処理(適当)+PaddleOCR入力用にnpへ
    im = Image.open(img_path).convert('L')
    enhancer= ImageEnhance.Contrast(im) #コントラストを上げる
    im_con = enhancer.enhance(2.0) #コントラストを上げる
    np_img = np.asarray(im_con)
    logger.debug('画像読み込み完了') #logは今までと変わらない!

    #PaddleOCRを定義
    ocr = PaddleOCR(
        use_gpu=False, #GPUあるならTrue
        lang = "japan", #英語OCRならen
        det_limit_side_len=im_con.size[1], #画像サイズが960に圧縮されないように必須設定
        max_text_length = 30, #検証してないがテキスト最大長のパラメータ。今回は不要だが紹介
        )
    logger.debug('PaddleOCR設定完了')

    #PaddleOCRでOCR ※cls(傾き設定)は矩形全体での補正なので1文字1文字の補正ではない為不要
    result = ocr.ocr(img = np_img, det=True, rec=True, cls=False)
    logger.debug('PaddleOCR実行完了')

    #OCR結果転記用
    result_img = np_img.copy()
    result_img = cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB)
    #画像に載せる日本語フォント設定 ★Windows10だと C:\Windows\Fonts\以下にフォントがありまs
    fontpath ='C:\Windows\Fonts\HGRPP1.TTC'
    font = ImageFont.truetype(fontpath, 10) #サイズ指定

    #OCR結果を画像に転記
    for detection in result[0]:
        t_left = tuple([int(i) for i in detection[0][0]]) #左上
        # t_right = tuple([int(i) for i in detection[0][1]]) #右上
        b_right = tuple([int(i) for i in detection[0][2]]) #右下
        b_left = tuple([int(i) for i in detection[0][3]]) #左下
        ocr_text = detection[1][0] #テキスト(detection[1][1]なら自信度取得も可能)
        #画像に文字範囲の矩形を載せる(緑色)
        result_img = cv2.rectangle(result_img, t_left, b_right, (0, 255, 0), 3)
        """putTextだと日本語が??になってしまうので自作関数で処理。文字の位置は左下とした"""
        # result_img = cv2.putText(result_img, ocr_text, t_left, cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 1, cv2.LINE_AA)
        result_img = japanese_puttext(result_img, ocr_text, (b_left[0], b_left[1]), font)

    logger.debug('画像にOCR結果記載完了')
    #保存する
    result_img = cv2.cvtColor(result_img, cv2.COLOR_RGB2BGR)
    cv2.imwrite('../out/ocr_result_picture.png', result_img)
    logger.debug('結果画像の保存完了')

if __name__ == '__main__':
    # loggerを定義
    logger.add("../out/samplelog.log", format="[{time:HH.mm:ss}] <lvl>{message}</lvl>", level='DEBUG', enqueue=True)
    img_path = "../data/miyazawa.png"
    run_ocr(img_path)
    # loggerを終了
    logger.remove()

これを実行すると、こんな画像がoutディレクトリに保存されます。
この例題に関しての精度に関しては見学習の場合Tesseractより少し上・・か同じ程度?という感想です。
文字の判定時にこの文字の確立が一番高いだろう!というものを選定するようなAIアルゴなので、このような旧字体入りの文章を事前学習しているとは思えず、少し難易度高い課題でしたね。
日本語でももう少し一般的な文章だとTesseractより明らかに性能良くなると思ってます。
※英語のOCRだと無茶苦茶性能高いのでオススメ!

3.(おまけ) PaddleOCR込みのプログラムをPyInstallerでexe化

おまけという名の本編です。
PaddleOCRだけ気になってた人は、後は蛇足なので「いいね!押していただいた後に(笑)」以下無視してブラウザバックしていただいて構いません。

まさかexe化がこんなにシンドイなんて試すまではわかりませんでした ので、本質ではないですがおまけで載せておきます。実際にやったことある人いればわかりますが、githubのissueにも中国語で多数同様の質問が繰り返されており、どれもうまくいかないです。
今ならNuitkaでもexe化できると思いますが、気力がないのでPyInstallerの方法を書いておきます。

pip install pyinstallerで導入を行う。(以下記事も参照)

3-0.事前知識

以下は私のにわか知識ですが、一応書いておきます。

前回の上記リンクのexe化記事では書いてませんが、Pyinstallerには基本「onefile」と「onedir(one directory)」の2パターンのexe化の方法があります。だいたいは私の前回記事のようにonefileを選択するのですが、これだと「起動が遅い」「重い」等のデメリットや、なにより「依存ライブラリ」に弱い弱点があります。
今回のPaddleOCRもまさにこの「依存ライブラリ」をうまくPyinstallerが汲んでくれないため、エラー地獄が待ってます。
そんな時、onedirにしておけば後で依存関係を修復できるのである。

3-1.事前準備(プログラム改造)

まずはsubprocessでエラーを起こすのでshell=Trueに修正しておく。(exe後に戻してもいいので)
なお、subprocessは標準ライブラリなのでPython本体側の修正が必要(私はpyenvなので以下の場所)
"C:\Users\***\.pyenv\pyenv-win\versions\3.9.6\Lib\subprocess.py"

subprocess.py
class Popen(object):
    """"""
    def __init__(self, args, bufsize=-1, executable=None,
                 stdin=None, stdout=None, stderr=None,
                 preexec_fn=None, close_fds=True,
-                 #shell=False, cwd=None, env=None, universal_newlines=None,
+                 shell=True, cwd=None, env=None, universal_newlines=None,
                 startupinfo=None, creationflags=0,
                 restore_signals=True, start_new_session=False,
                 pass_fds=(), *, user=None, group=None, extra_groups=None,
                 encoding=None, errors=None, text=None, umask=-1):

次にpaddleocr\ppocrの中にあるnetwork.pyでエラーを起こすので以下をコメントアウトしておく。

これはexe化して配布する際に、paddleOCRの3モデルを自動ダウンロードするのだが、コンソールに進捗が出せなくてエラーになってしまう対応である。コメントアウトしてもバーが出なくなるだけでしっかりダウンロードされる。
※これが1-1. PaddleOCR+paddlepaddleで語っていた「exe化の時にもやっかいな動きをする」箇所である

.venv\Lib\site-packages\paddleocr\ppocr\utils\network.py
def download_with_progressbar(url, save_path):
    logger = get_logger()
    response = requests.get(url, stream=True)
    if response.status_code == 200:
        total_size_in_bytes = int(response.headers.get('content-length', 1))
        block_size = 1024  # 1 Kibibyte
+        #progress_bar = tqdm(
+        #    total=total_size_in_bytes, unit='iB', unit_scale=True)
        with open(save_path, 'wb') as file:
            for data in response.iter_content(block_size):
+        #        progress_bar.update(len(data))
                file.write(data)
+        #progress_bar.close()
    else:
        logger.error("Something went wrong while downloading models")
        sys.exit(0)

3-2. pyinstallerでexe化+ライブラリを追加導入

さて、事前準備の後にはいよいよ実行なわけだが、今回は「-D(--onedir)」オプションが必須となる。
後は--add-dataで強制的にexe後にも依存関係を残させている。
なお;paddle/はexe化前のライブラリpathを「paddle」という名前で強制的に依存させるという意味である。

pyinstaller -D -w --add-data "C:\Users\**(略)**\.venv\Lib\site-packages\paddle;paddle/" 
--add-data "C:\Users\**(略)**\.venv\Lib\site-packages\paddleocr;paddleocr/" 
sample_ocr.py

大事なのはonefileではなく、onedir(-D)にすること。
これをしないとexe化した後に「あれが足りない。これが足りない」とエラー地獄に陥り大変になる。
もちろん↑のコマンドに同様に--add-dataしていってもいいが、数が多すぎたので私は以下をコピペした

基本的には仮想環境化(venv\Lib\site-packages\)に入っている上記ライブラリを以下のようにexeの後にonedirとしてexeが入っているのと同じ階層にコピペすることになる。
また1つだけ.pyがあるがこれはPython本体から持ってくる必要がある。
"C:\Users\***\.pyenv\pyenv-win\versions\3.9.6\Lib\imghdr.py"

はい、おめでとうございます。
これでやっとexeが動くようになります。

4. おわりに

PaddleOCRは中国のOSSなので情報も中国語メインだし、中国版Qiita(Zenn)のCSDNを探してもどれもうまくいかないので情報として残しておく。私はこの方法で100%exe化を再現できたので、超マニアックな記事ですがぜひ活用してみてください。

OCRツールというのはDX化の中核にくるポテンシャルがあるのに、依頼元はさぞ簡単なんだろ?という態度で臨まれるのがいたたまれないですよね・・(頑張りましょう!)

如果你在中国,并且发现它很有用,请按下喜欢(心)按钮!!!

5. 追記

さらにDonutというOCRではなく、画像として文字生成する仕組みも書きましたのでご参考まで

参考

27
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
20