tesseractを使ってPDFから数値を抽出してみる


こんにちは、無名です。
研究室の実験で取得したグラフの数値をOCRで抽出してデータをまとめているのですが、知識整理のため書きます。
普段はRTKやGPSの受信に関することを研究室で学んでいます。
今回はドローンからとったデータをOCRを使って必要な情報を抽出します。

## tesseract
tesseractは、google(とHP)が開発したオープンソースの光学文字認識（OCR: Optical Character Recognition）エンジンです。OCRエンジンは、画像内の文字を認識し、デジタルテキストに変換するためのソフトウェアです。Python、Java、C++などのプログラミング言語から利用することもできます。
tesseractのダウンロードは省略します。たしかanacondaで仮想環境作ってダウンロードしました。

## 今回使用するデータ
今回使うグラフは下の画像になります。ドローンを飛ばしてデータを取りました。![スクリーンショット (316).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3778844/26471ae9-439a-d8fc-3d54-752feebf9b39.png)

このグラフのAVE、STD、RMSの数値をOCRしていきたいと思います。

## pythonでコードを書いてみる
Pythonでコードを書いていきます。
必要なライブラリはこんな感じです。

``` 
import pdf2image
import pandas as pd
import re
import sys
import pyocr
import pyocr.builders
from PIL import Image, ImageEnhance, ImageFilter
```
OCRの精度を高めるためにImageEnhance, ImageFilterを使ってコントラスト比を調整したり、グレースケールに変換します。

### OCRをするコード
ORCをするコードはこんな感じです。
```
file_path = r"../../XXX.pdf"

gray_pdf = []

tools = pyocr.get_available_tools()
if len(tools) == 0:
    print("No OCR tool found")
    sys.exit(1)
# The tools are returned in the recommended order of usage
tool = tools[0]
print("Will use tool '%s'" % (tool.get_name()))
# Ex: Will use tool 'libtesseract'

langs = tool.get_available_languages()
print("Available languages: %s" % ", ".join(langs))
lang = langs[0]
print("Will use lang '%s'" % (lang))

all_text = []

#pdfを画像に変換
images = pdf2image.convert_from_path(file_path, dpi=350, fmt='png')
lang = 'eng'

#画像オブジェクトからテキストに変換
for image in images:

 txt = tool.image_to_string(
        image,
        lang=lang,
        builder=pyocr.builders.TextBuilder(tesseract_layout=6)
    )
    all_text.append(txt)

full_text = "\n".join(all_text)
print(full_text)
```
このコードを実行するとpdfにある文字や画像の文字をOCRしてくれます。
pdfを1ページずつ画像に変換し、OCRして抽出した文字をall_textに格納してくれます。
OCRが可能な場合、コードを実行すると以下の文が最初に出てきます。
```
Will use tool 'Tesseract (sh)'
Available languages: afr, amh, ara, asm, aze, aze_cyrl, bel, ben, bod, bos, bre, bul, cat, ceb, ces, chi_sim, chi_sim_vert, chi_tra, chi_tra_vert, chr, cos, cym, dan, deu, div, dzo, ell, eng, enm, epo, equ, est, eus, fao, fas, fil, fin, fra, frk, frm, fry, gla, gle, glg, grc, guj, hat, heb, hin, hrv, hun, hye, iku, ind, isl, ita, ita_old, jav, jpn, jpn_vert, kan, kat, kat_old, kaz, khm, kir, kmr, kor, kor_vert, lao, lat, lav, lit, ltz, mal, mar, mkd, mlt, mon, mri, msa, mya, nep, nld, nor, oci, ori, osd, pan, pol, por, pus, que, ron, rus, san, sin, slk, slv, snd, spa, spa_old, sqi, srp, srp_latn, sun, swa, swe, syr, tam, tat, tel, tgk, tha, tir, ton, tur, uig, ukr, urd, uzb, uzb_cyrl, vie, yid, yor
Will use lang 'afr'
```

####  実行結果
上のグラフを実行するとこうなりました。

![スクリーンショット (319).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3778844/ce22b825-fbda-5250-3b91-0e49189ae608.png)

見てみると、AVE = 51206 STD=11:6857m RMS=12.7488mとなっておりうまくOCR出来ていませんでした....
原因はグラフの線が青色で文字と線が重なってしまっているので多分精度が悪くなったと思います。
精度をよくするために線の色を明るい色にして、コントラスト比を変えてグレースケールに変えてみます。
コードをこんな風に変更します。
```
for image in images:
    # コントラストを強化
    enhancer = ImageEnhance.Contrast(image)
    enhanced_image = enhancer.enhance(2.0)  #コントラスト係数を調整

    #コントラスト強化された画像をグレースケールに変換
    gray_image = enhanced_image.convert("L")
    
    #グレースケール画像を二値化
    threshold = 128
    binary_image = gray_image.point(lambda x: 0 if x < threshold else 255, '1')
    gray_pdf.append(binary_image)

    #二値化された画像にOCRを適用
    txt = tool.image_to_string(
        binary_image,
        lang=lang,
        builder=pyocr.builders.TextBuilder(tesseract_layout=6)
    )
    all_text.append(txt)

full_text = "\n".join(all_text)
print(full_text)
```
これで画像がグレースケールに変換してからその画像をOCRしています。
実行結果を確認します。

![スクリーンショット (327).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3778844/3997605f-c0d8-3ab2-4279-bb16c711a4ab.png)

画像を調整したことによって精度を上げることが出来ました。
ここから正規表現を使ってAVE,STD,RMSの数値を抽出していきます。

```
ocr_list = []

# 正規表現パターンのコンパイル
pattern = re.compile(r"(AVE|STD|RMS)=?(-?\d+[.,]\d*(?:\/\d+)?)[a-zA-Z]*(?:\/[a-zA-Z]*)?m")

# テキストからの数値抽出
matches = pattern.findall(full_text)

# 結果の表示
for match in matches:
    print(f"{match[0]}: {match[1]}")
    try:
        value = float(match[1])
    except ValueError:
        value = float('nan')  
    ocr_list.append((match[0], value))

```
正規表現は誤字があったものも対応できるようにしてあるので少し複雑ですが、特定の文字を抽出できるようにコンパイルしました。
空の配列を作成し、そこに抽出した文字を入れています。抽出できなかったものはnanが入るようにしています。

#### リストの中のものをpandasで整理
ocr_listの中に入っているデータをpandasを使って見やすくします。

```
data = {'AVE': [], 'STD': [], 'RMS': []}
for key, value in ocr_list:
    data[key].append(value)

df = pd.DataFrame(data)
df
```
AVE,STD,RMSの順に並べ替えて格納します。
dfの中身は実行するとこんな感じになりました。


AVE	STD	RMS
12.2429	28.0249	30.5595
-23.3785	31.6345	39.3129
5.1206	11.6857	12.7488

これをcsvで出力して終わりです。
研究室での作業をこれを利用して自動化し、4時間ほど時間短縮できました。

## 最後に
今までは信号処理に関することを書いていましたが、今回初めて別分野の記事を書きました。
基本は無線や信号関連の記事を書いていきますが、たまには無線以外の分野も書いていきたいと思います。