😇

EasyOCRを初めて使ってみた

2024/06/09に公開

注意事項

OCRや今回使おうとしているEasyOCRについては全くの無知です。
とりあえず、使ってみようというぐらいの軽い気持ちなので、やっていることが全然違ったり、意味わからないと感じるかもしれないということをご承知おきください。。。

はじめに

前述のとおり、自分はOCR技術について「文字データを抽出してデータ化する」というぐらいの認識しか未だにわかっていません。
自分が読んだ記事のリンクを貼っておきます。。
https://camp.trainocate.co.jp/magazine/about-easyocr/

記事内に「人工知能、画像処理の知識がなくても使える」PythonのEasyOCRライブラリを使って体験してみようというのが試してみようと思った経緯です。

実装

EasyOCRのインストール

Pythonを使うときにやっているいつもの手順で、仮想環境作成→ライブラリのインストールという手順で実施しました

python -m venv venv
.\venv\Scripts\activate
pip install easyocr

STEP1

平仮名・漢数字・アルファベットを書いた画像から文字列を抽出してみます。
※以下の画像をペイントを使い作成しました

入力画像

以下はものすごく単純に画像データの生データをそのまま解析して文字データを抽出する際に実装してみたソースコードです。

import easyocr

# リーダーオブジェクトに日本語と英語を設定
reader = easyocr.Reader(['en', 'ja'])

# EasyOCRを使って画像から文字データを抽出し結果を出力
def analyze_picture(target_path: str):
    results = reader.readtext(target_path_1)
    for result in results:
        print(result)

# 試し実行用
if __name__ == "__main__":
    target_path_1 = "D:/Zenn/OCR/ORC_trial1.png"
    analyze_picture(target_path_1)

実行結果は以下のようになりました。

([[90, 62], [704, 62], [704, 166], [90, 166]], 'あ い うえ お', 0.7533276681349921)
([[185, 211], [594, 211], [594, 313], [185, 313]], '二三四五', 0.88416987657547)
([[96, 338], [713, 338], [713, 476], [96, 476]], 'ABC D EF G', 0.6291972021246112)

EasyOCRの解析結果は特に指定してない限りはリスト形式で結果が返却されます。
返却結果はそれぞれ

  1. 抽出した文字データの座標
  2. 抽出文字列
  3. 精度(と思ってます)

結果を見てみると、おおよそあっている感じがします。
ただ、漢数字の「一」だけ認識されずという感じでした。そしてなぜか漢数字の精度が一番高いのに認識された文字が1つ足りないという・・・

画像の文字列上に空白を入れたつもりはなかったのですが、結果を見てみると空白が入っていたりしていました。
#長い文章というよりは単語レベルの解析には問題なさそう?

STEP2

家計簿アプリを使っていると「レシートから登録」というようなことができるものが最近は多いと思います。無知の状態もライブラリを使用することでどれくらい抽出できるのかやってみます。
#iPhoneは14の無印を使用してます

使用する画像は以下です。

実装 その①

STEP1のソースコードを流用しつつ、読み込んだ画像内に解析対象となった文字列の領域を四角で囲ってみます。

import easyocr
from PIL import Image, ImageDraw

def analyze_picture(target_path: str):
    draw_chararea(target_path, reader.readtext(target_path))
# <追加>入力画像内に文字列の領域を赤枠で囲う
def draw_chararea(target_path, results):
    image = Image.open(target_path).convert('RGB')
    draw = ImageDraw.Draw(image)
    # 座標情報からテキスト領域を四角で囲う
    for result in results:
        print(result)
        p0, p1, p2, p3 = result[0]
        draw.line([ *p0, *p1, *p2, *p3, *p0], fill='red', width=3)
    image.save("draw_chararea.png")
if __name__ == "__main__":
    target_path = "D:/Zenn/OCR/receipt.png"
    # analyze_picture(target_path)
    analyze_picture(target_path)

以下が画像から解析した結果になります。

([[76, 62], [214, 62], [214, 94], [76, 94]], '親切なお店', 0.9905352276748758)
([[79, 75], [617, 75], [617, 207], [79, 207]], 'イリtトキ良', 0.0017967082842267601)
([[248, 667], [460, 667], [460, 717], [248, 717]], '令川又司正', 0.06882998913811791)
([[125, 741], [486, 741], [486, 790], [125, 790]], '538テリアミントE1P', 0.4784281082492745)
([[510, 739], [629, 739], [629, 790], [510, 790]], '#1,740', 0.21481856862354515)
([[211, 783], [403, 783], [403, 827], [211, 827]], '3 X 1580)', 0.15025303911057153)
([[479, 814], [628, 814], [628, 867], [479, 867]], '割引外', 0.9795216119762415)
([[126, 854], [522, 854], [522, 904], [126, 904]], '436アルカリ電池単3形呂', 0.6256159086883563)
([[544, 852], [629, 852], [629, 901], [544, 901]], "'判03", 0.11983729898929596)
([[79, 895], [157, 895], [157, 939], [79, 939]], '小計', 0.9997707288428661)
([[210, 894], [272, 894], [272, 942], [210, 942]], '4点', 0.20989334049312805)
([[513, 893], [627, 893], [627, 937], [513, 937]], '2,543', 0.8706925269606042)
([[85, 930], [221, 930], [221, 980], [85, 980]], '合計', 0.6084563612249462)
([[414, 930], [628, 930], [628, 978], [414, 978]], 'ギ2,ち43', 0.31801069647874775)
([[104, 968], [342, 968], [342, 1020], [104, 1020]], '〈内 消費税等', 0.8946600364363887)
([[479, 969], [573, 969], [573, 1013], [479, 1013]], '6231)', 0.408979063379244)
([[77, 1009], [221, 1009], [221, 1053], [77, 1053]], '(1対象', 0.38654109835624695)
([[294, 1006], [441, 1006], [441, 1054], [294, 1054]], ',593税', 0.45092171291783617)
([[531, 1007], [625, 1007], [625, 1051], [531, 1051]], '231)', 0.9955219030380249)
([[109, 1049], [153, 1049], [153, 1085], [109, 1085]], 'D', 0.9908714958450027)
([[515, 1045], [629, 1045], [629, 1089], [515, 1089]], '12,593', 0.11558677778221504)
([[85, 1083], [219, 1083], [219, 1127], [85, 1127]], 'お金', 0.4420781097690712)


文字列の領域を赤枠で囲った結果

赤枠で囲ったものを見てみると、思いのほか高い精度で文字列自体は認識されているという風に感じました。
ただ、領域が認識されているだけで文字列自体が正しく認識されているわけではなさそうです。
この時点で、家計簿アプリなどを開発している人すごすぎると感じてます・・・

実装 その②

認識の精度を上げるためには二値化を行うと良いらしいので解析する前にOpenCVを利用して二値化してみます。

今回、二値化するにあたって閾値は自動判別してくれるように設定しています。
※実行後、コマンドライン上に自動判別した閾値が表示されるようにしてます。

その②のソースコードもその①のものを流用しつつ、二値化部分を追加してます

def analyze_picture_tial(target_path: str):
    image = cv2.imread(target_path)
    # グレースケール化
    gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # 二値化(閾値は自動判別)
    ret, img_thresh = cv2.threshold(gray_image, 0, 255, cv2.THRESH_OTSU)
    # 自動判別した閾値を表示
    print(f'閾値:{ret}')
    cv2.imwrite("thresh.png", img_thresh)
    draw_chararea("thresh.png", reader.readtext(target_path))

以下が二値化処理を行ったうえでの実行結果になります。

([[76, 62], [214, 62], [214, 94], [76, 94]], '親切なお店', 0.9905352276748758)
([[79, 75], [617, 75], [617, 207], [79, 207]], 'イリtトキ良', 0.0017967082842267601)
([[248, 667], [460, 667], [460, 717], [248, 717]], '令川又司正', 0.06882998913811791)
([[125, 741], [486, 741], [486, 790], [125, 790]], '538テリアミントE1P', 0.4784281082492745)
([[510, 739], [629, 739], [629, 790], [510, 790]], '#1,740', 0.21481856862354515)
([[211, 783], [403, 783], [403, 827], [211, 827]], '3 X 1580)', 0.15025303911057153)
([[479, 814], [628, 814], [628, 867], [479, 867]], '割引外', 0.9795216119762415)
([[126, 854], [522, 854], [522, 904], [126, 904]], '436アルカリ電池単3形呂', 0.6256159086883563)
([[544, 852], [629, 852], [629, 901], [544, 901]], "'判03", 0.11983729898929596)
([[79, 895], [157, 895], [157, 939], [79, 939]], '小計', 0.9997707288428661)
([[210, 894], [272, 894], [272, 942], [210, 942]], '4点', 0.20989334049312805)
([[513, 893], [627, 893], [627, 937], [513, 937]], '2,543', 0.8706925269606042)
([[85, 930], [221, 930], [221, 980], [85, 980]], '合計', 0.6084563612249462)
([[414, 930], [628, 930], [628, 978], [414, 978]], 'ギ2,ち43', 0.31801069647874775)
([[104, 968], [342, 968], [342, 1020], [104, 1020]], '〈内 消費税等', 0.8946600364363887)
([[479, 969], [573, 969], [573, 1013], [479, 1013]], '6231)', 0.408979063379244)
([[77, 1009], [221, 1009], [221, 1053], [77, 1053]], '(1対象', 0.38654109835624695)
([[294, 1006], [441, 1006], [441, 1054], [294, 1054]], ',593税', 0.45092171291783617)
([[531, 1007], [625, 1007], [625, 1051], [531, 1051]], '231)', 0.9955219030380249)
([[109, 1049], [153, 1049], [153, 1085], [109, 1085]], 'D', 0.9908714958450027)
([[515, 1045], [629, 1045], [629, 1089], [515, 1089]], '12,593', 0.11558677778221504)
([[85, 1083], [219, 1083], [219, 1127], [85, 1127]], 'お金', 0.4420781097690712)


二値化後文字列の境域を赤枠で囲った結果

やってみる前からなんとなく想像はしていましたが、二値化しても結果も変わらないんじゃないかと思っていましたが、案の定全く同じ結果になってしまっていました。
#元の画像からそもそもほぼ白と黒だし、解析対象の文字列の領域を見てみても予想外のところを認識しているわけじゃないため変わらないかもと思ってました。。

感想

勉強がてら自作してみるというのも良いかもしれませんが、既に似たようなアプリがあるなら多少使いづらさや「こうしたいのに・・・」という気持ちを抑えて使い慣れていく努力をするほうがコスパ良いと改めて思った検証でした。
自分はこれ以上、踏み込むのやめようかなと思ってます。。

仮に今後も検証を続けていくなら、いろいろなOCRのライブラリを使うぐらいでしょうか・・・

Discussion