【Python】画像タイプのPDFからOCRでテキスト抽出する

以前に、PDFからテキスト抽出をしてデータフレームにする方法を紹介しました。

ですが、実際には紙の資料をスキャンしてPDF化した資料も多々あり、その場合は純粋なテキスト抽出ができません。

そこで今回は、OCRという画像から文字を読み取る方法で、スキャンされた資料のPDFからテキストを抽出する方法を紹介します。

処理の流れ

今回の処理の一連の流れは、

  1. PDFの各ページをそれぞれ画像に変換
  2. OCRで各画像から文字を読み取る
  3. ページ内での文字の位置情報も含めたデータフレームに変換

です。

事前準備

Tesseractのインストール

画像からのテキスト抽出には、Tesseractというオープンソースのソフトウェアを用います。光学式文字認識エンジンであるTesseractは、オープンソースながら精度も十分と評判です。

Tesseractのインストール手順は、以下サイトが非常に参考になります(Windows)。

https://gammasoft.jp/blog/tesseract-ocr-install-on-windows/#download

読み取るPDF

今回読み取るPDFは、2012年のとある論文とします。

今回読み取るPDF。英論文

このPDFではそのままテキスト抽出もできますが、今回は画像からOCRでテキスト抽出していきます。

プログラム

コード全容

まずは、今回のプログラム全容を紹介します。

import os
import pathlib
from pdf2image import convert_from_path
import sys
import pyocr
import pyocr.builders
import pandas as pd


        
#画像ごとにOCR実行    
def exe_ocr(imgs):
    
    # tesseract-OCRのパスを通す
    tessera_path = "C:\Program Files\Tesseract-OCR"
    # pathsepは環境変数に追加するときの区切り;
    os.environ["PATH"] += os.pathsep + str(tessera_path)

    tools = pyocr.get_available_tools()
    if len(tools) == 0:
        print("No OCR tool found")
        sys.exit(1)  # 引数1は終了ステータスで1を返す

    tool = tools[0]

    df = pd.DataFrame()
    index = 0 
    #画像の数(ページ数)ごとの処理
    for i, img in enumerate(imgs):
        
        #OCR実行。WordBoxBuilderでは座標も取得可能
        word_boxes = tool.image_to_string(
            img,
            lang="eng",
            builder=pyocr.builders.WordBoxBuilder(tesseract_layout=6)
        )
        
        #文字の塊ごとに分けてデータフレームに変換
        for box in word_boxes:
            df_page = pd.DataFrame({
                                    "x_start":box.position[0][0],
                                    "x_end"  :box.position[1][0],
                                    "y_start":box.position[0][1],
                                    "y_end"  :box.position[1][1],
                                    "text"   :box.content,
                                    "page"   :i+1},index=[index]
                                    )
            
            df = df.append(df_page)
            index += 1
        
        print(word_boxes)
        
    return df
            
##ここから
if __name__ == '__main__':
    # PDFファイルのパス
    pdf_path = pathlib.Path('./input/pxc3882784.pdf')

    #PDFの各ページを画像に変換
    imgs = convert_from_path(pdf_path)
    df = exe_ocr(imgs)

PDFを画像に変換

convert_from_path()で指定したパスのPDFファイルをページごとの画像に変換します。このとき画像はnumpy配列として格納されます。このとき、PDFのパスはpathlib.Path()形式で渡す必要があります。

ちなみに画像を1つずつ出力すると、以下のように1ページずつ分けられているのがわかります。

PDFを分割して画像として保存した場合

OCR実行

関数exe_ocrの内部でOCRを実行します。

まずはTesseractのパスを一時的に環境変数に登録します。

# tesseract-OCRのパスを通す
tessera_path = "C:\Program Files\Tesseract-OCR"
# pathsepは環境変数に追加するときの区切り;
os.environ["PATH"] += os.pathsep + str(tessera_path)

toolsには呼び出したTesseractのモジュールを格納します。ここで、Tesseractをうまく呼び出せない場合は処理終了としています。

tools = pyocr.get_available_tools()
if len(tools) == 0:
    print("No OCR tool found")
    sys.exit(1)  # 引数1は終了ステータスで1を返す

**OCRに使用するtoolはtoolsの0番目を指定します。

OCRは画像1枚(1ページ)ずつ行います。toolの image_to_string()メソッドでOCRを実行します。このとき、builderに pyocr.builders.WordBoxBuilder()を指定することで、テキストだけでなくその座標情報も取得することができます。

今回は英語の論文なので、lang=”eng”としています。日本語の場合は”jpn”としてください。

#OCR実行。WordBoxBuilderでは座標も取得可能
        word_boxes = tool.image_to_string(
            img,
            lang="eng",
            builder=pyocr.builders.WordBoxBuilder(tesseract_layout=9)
        )

tesseract_layout

pyocr.builders.WordBoxBuilder()ではtesseract_layoutを設定しています。これはテキスト読み取りルールの指定パラメータで、デフォルトは3です。このパラメータによって読み取り精度も大きく変わってくるようです。

0Orientation and script detection (OSD) only.
1Automatic page segmentation with OSD.
2Automatic page segmentation, but no OSD, or OCR
3Fully automatic page segmentation, but no OSD. (Default)
4Assume a single column of text of variable sizes.
5Assume a single uniform block of vertically aligned text.
6Assume a single uniform block of text.
7Treat the image as a single text line.
8Treat the image as a single word.
9Treat the image as a single word in a circle.
10Treat the image as a single character.

ちなみに、これの公式ドキュメントはどこかにあるんですか??

データフレームに変換

ここからは、必要な情報をデータフレームにまとめていきます。

テキスト本文とテキストを囲った時の座標、ページ番号をともにまとめていきます。

#文字の塊ごとに分けてデータフレームに変換
for box in word_boxes:
    df_page = pd.DataFrame({
                            "x_start":box.position[0][0],
                            "x_end"  :box.position[1][0],
                            "y_start":box.position[0][1],
                            "y_end"  :box.position[1][1],
                            "text"   :box.content,
                            "page"   :i+1},index=[index]
                            )
            
    df = df.append(df_page)
    index += 1

**空のデータフレームを予め作成しています。

x座標はstart(左)→end(右)、y座標はstart(下)→end(上)を示します。こうして作成したデータフレームは以下のようになります。

データフレームの中身

テキスト情報とともに座標、ページ番号が出力されています。

まとめ

スキャンされた資料のPDFファイルからテキスト情報を収集する方法を紹介しました。古い資料のPDFなどはこの手のものが多いので、こうやってテキスト抽出ができると便利だと思います。

座標等と組合わせれば、単純なテキスト分析だけでなく、さらに可用性が増しそうです^^

今回のように簡単に実装できるので、是非お試しあれ!

ではでは👋