レイアウトが異なるPDFファイルからテキスト情報を抽出する-機械学習


8

現在作成しようとしているMLプロジェクトについてサポートが必要です。

多くの異なるサプライヤーから大量の請求書を受け取ります-すべて独自のレイアウトで。請求書から3つの重要な要素を抽出する必要があります。これらの3つの要素はすべて、すべての請求書のテーブル/ラインアイテムにあります。

3要素は次のとおりです。

  • 1:関税番号(桁)
  • 2:数量(常に数字)
  • 3:合計明細金額(金額)

以下のスクリーンショットを参照してください。サンプルの請求書でこれらのフィールドにマークを付けています。

ここに画像の説明を入力してください

このプロジェクトは、正規表現に基づいたテンプレートアプローチから始めました。しかし、これはまったくスケーラブルではなく、大量の異なるルールになってしまいました。

ここで機械学習が役立つことを願っています-あるいは、ハイブリッドソリューションですか?

一般的な分母

では、すべての私の請求書の、異なるレイアウトのにもかかわらず、各ライン項目がします常に 1つので構成されて関税番号。この関税番号は常​​に8桁で、常に次のような方法でフォーマットされます。

  • xxxxxxxx
  • xxxx.xxxx
  • xx.xx.xx.xx

(「x」は0から9までの数字です)。

さらに、請求書で確認できるように、1行あたりの単価と合計金額の両方があります。私が必要とする量は常に各行で最高です。

出力

上記のような各請求書について、各行の出力が必要です。これは、たとえば次のようなものになります。

{
    "line":"0",
    "tariff":"85444290",
    "quantity":"3",
    "amount":"258.93"
},
{
    "line":"1",
    "tariff":"85444290",
    "quantity":"4",
    "amount":"548.32"
},
{
    "line":"2",
    "tariff":"76109090",
    "quantity":"5",
    "amount":"412.30"
}

ここからどこへ行く?

私が何をしようとしているのかが機械学習に該当するかどうか、また該当する場合はどのカテゴリに該当するかわかりません。コンピュータビジョンですか?NLP?名前付きエンティティの認識?

私の最初の考えは:

  1. 請求書をテキストに変換します。(請求書はすべてpdftotextテキスト化可能なPDFであるため、正確なテキスト値を取得するようなものを使用できます)
  2. カスタム作成名前付きエンティティのためにquantitytariffそしてamount
  3. 見つかったエンティティをエクスポートします。

でも、足りないものがあるようです。

誰かが正しい方向に私を助けることができますか?

編集:

請求書テーブルセクションがどのように表示されるかを示す他の例については、以下をご覧ください。

請求書の例2 ここに画像の説明を入力してください

請求書の例3 ここに画像の説明を入力してください

編集2:

境界線/境界ボックスなしの 3つのサンプル画像については、以下を参照してください。

画像1: bboxなしのサンプル1

画像2: bboxなしのサンプル2

画像3: bboxなしのサンプル3


入力PDFの例をいくつか示して、実際にどれだけのバリエーションがあるかを確認できますか?(=ソリューションの柔軟性)
sjaustirni

@sjaustirniあと2つ追加しました!サプライヤーの請求書の最大の違いは、テーブルレイアウトの方法(およびその後のラインアイテム、特定のテキストのフォーマット方法)です
oliverbj

パーフェクト!これらの例を考えると、おそらくpdfをテキストに変換し、そこにある値を前のラベル(Tariff No.:またはまたは$)またはそれが属する列(ここでは、文字の空間情報を保存するのに役立つ場合があります)とペアにします。 OCRツールがそれを行う場合)。私は、この問題(既製のOCRを除く)やNLP(自然言語ではない)を使用して機械学習を行う必要はないと考えています。しかし、これらのツールは、あなたのデータを扱う方法をうまくやって見ることなく、我々は唯一の次のステップが何であるかを推測することができ、何が必要です:D
sjaustirni

@sjaustirniは、私が既に行っているのと同じことで終わりませんか?(テンプレートベース/正規表現アプローチ)。
oliverbj

PDFからデータ構造にテーブル自体を抽出してから、列を処理できませんか?これを行うにはtabula-pyを使用して、数量と合計を直接取得できます。正規表現を使用すると、関税
dhanushka

回答:


4

私はロジスティクス業界で同様の問題に取り組んでおり、これらのドキュメントテーブルが無数のレイアウトで提供されていると言ったら、私を信頼します。この問題を幾分解決し改善している多くの企業が以下のように述べられています

  • リーダー:ABBYY、AntWorks、Kofax、WorkFusion
  • 主な候補者:Automation Anywhere、Celaton、Datamatics、EdgeVerve、Extract Systems、Hyland、Hyperscience、Infrrd、およびParascript
  • 志願者:Ikarus、Rossum、Shipmnts(Alex)、Amazon(Textract)、Docsumo、Docparser、Aidock

テキストと画像の両方のモダリティがこの問題にかなり貢献しているため、この問題を分類したいカテゴリはマルチモーダル学習です。OCRトークンは属性値の分類で重要な役割を果たしますが、ページ上の位置、間隔、および文字間距離は、テーブル、行、および列の境界を検出する上で非常に重要な機能として保持されます。行がページをまたぐ場合、または一部の列に空でない値が含まれる場合、問題はさらに興味深いものになります。

学術界や会議では、「インテリジェントドキュメント処理」という用語を使用していますが、一般的に、特異なフィールドと表形式のデータの両方を抽出するために使用しています。前者は、属性値分類によってよりよく知られており、後者は、研究文献において、表抽出または反復構造抽出によって有名です。

3年間にわたるこれらの半構造化ドキュメントの処理における私たちの進出において、私は正確さとスケーラビリティの両方を達成することは長く困難な旅である感じています。スケーラビリティ/「テンプレートフリー」アプローチを提供するソリューションでは、数百万ではないにしても、数万程度の半構造化ビジネスドキュメントのコーパスに注釈が付けられています。このアプローチはスケーラブルなソリューションですが、トレーニングを受けたドキュメントと同じくらい優れています。複雑なレイアウトで知られているロジスティクスまたは保険セクターからの文書が採用されており、コンプライアンス手順のために非常に正確である必要がある場合、「テンプレートベースの」ソリューションはあなたの病気の万能薬になります。より高い精度が保証されます。

既存の研究へのリンクが必要な場合は、下のコメントで言及してください。喜んでそれらを共有します。

また、前者はpdf2textまたはpdfminerよりもpdfparser 1を使用することをお勧めします。前者の方がデジタルファイルの文字レベルの情報を大幅に向上させるためです。

これが私の最初の回答なので、フィードバックを組み込んでいただければ幸いです。


オープンソースのリポジトリを探している場合は、github.com
/ invoice

3

OpenCVを使用した試みを以下に示します。アイデアは次のとおりです。

  1. バイナリイメージを取得します。画像を読み込み、拡大し imutils.resizeてOCR結果を改善しTesseractの品質向上を参照)、グレースケールに変換してから、大津のしきい値 を使用してバイナリ画像(1チャネル)を取得します。

  2. テーブルのグリッド線を削除します。水平方向と垂直方向のカーネルを作成し、形態学的操作を実行て、隣接するテキストの輪郭を1つの輪郭に結合します。アイデアは、ROI行をOCRへの1つのピースとして抽出することです。

  3. 行ROIを抽出します。輪郭見つけて、を使用して上から下に並べ替えますimutils.contours.sort_contours。これにより、各行を正しい順序で反復処理することが保証されます。ここから、等高線を反復処理し、Numpyスライスを使用して行ROIを抽出し、Pytesseractを使用してOCRを抽出してから、データを解析します。


これが各ステップの視覚化です:

入力画像

ここに画像の説明を入力してください

バイナリイメージ

ここに画像の説明を入力してください

モーフを閉じる

ここに画像の説明を入力してください

各行の反復の視覚化

ここに画像の説明を入力してください

抽出された行ROI

ここに画像の説明を入力してください ここに画像の説明を入力してください ここに画像の説明を入力してください

請求書データ結果の出力:

{'line': '0', 'tariff': '85444290', 'quantity': '3', 'amount': '258.93'}
{'line': '1', 'tariff': '85444290', 'quantity': '4', 'amount': '548.32'}
{'line': '2', 'tariff': '76109090', 'quantity': '5', 'amount': '412.30'}

残念ながら、2番目と3番目の画像を試すと、結果が混在します。請求書のレイアウトがすべて異なるため、この方法では他の画像に大きな効果はありません。ただし、このアプローチは、従来の画像処理技術を使用して、固定された請求書レイアウトがあるという前提で請求書情報を抽出できることを示しています。

コード

import cv2
import numpy as np
import pytesseract
from imutils import contours
import imutils

pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"

# Load image, enlarge, convert to grayscale, Otsu's threshold
image = cv2.imread('1.png')
image = imutils.resize(image, width=1000)
height, width = image.shape[:2]
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Remove horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (50,1))
detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(thresh, [c], -1, 0, -1)

# Remove vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,50))
detect_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(thresh, [c], -1, 0, -1)

# Morph close to combine adjacent contours into a single contour
invoice_data = []
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (85,5))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=3)

# Find contours, sort from top-to-bottom
# Iterate through contours, extract row ROI, OCR, and parse data
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
(cnts, _) = contours.sort_contours(cnts, method="top-to-bottom")

row = 0
for c in cnts:
    x,y,w,h = cv2.boundingRect(c)
    ROI = image[y:y+h, 0:width]
    ROI = cv2.GaussianBlur(ROI, (3,3), 0)
    data = pytesseract.image_to_string(ROI, lang='eng', config='--psm 6')
    parsed = [word.lower() for word in data.split()] 
    if 'tariff' in parsed or 'number' in parsed:
        row_data = {}
        row_data['line'] = str(row)
        row_data['tariff'] = parsed[-1]
        row_data['quantity'] = parsed[2]
        row_data['amount'] = str(max(parsed[10], parsed[11]))
        row += 1

        print(row_data)
        invoice_data.append(row_data)

        # Visualize row extraction
        '''
        mask = np.zeros(image.shape, dtype=np.uint8)
        cv2.rectangle(mask, (0, y), (width, y + h), (255,255,255), -1)
        display_row = cv2.bitwise_and(image, mask)

        cv2.imshow('ROI', ROI)
        cv2.imshow('display_row', display_row)
        cv2.waitKey(1000)
        '''
print(invoice_data)
cv2.imshow('thresh', thresh)
cv2.imshow('close', close)
cv2.waitKey()

3
@nathancyありがとうございます!この回答は私の投稿されたすべての請求書の一般的な解決策ではありませんが、私はまだそれが私が誰かが来るのを見た最も近いものだと思います-これはOpenCVを使用しているだけです。とてもクールで、あなたのコード例は私にたくさん教えてくれました!繰り返しますが、これを投稿していただきありがとうございます。
oliverbj
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.