Extrahieren Sie Textinformationen aus PDF-Dateien mit unterschiedlichen Layouts - maschinelles Lernen


8

Ich benötige Unterstützung bei einem ML-Projekt, das ich gerade erstellen möchte.

Ich erhalte viele Rechnungen von vielen verschiedenen Lieferanten - alle in ihrem eigenen Layout. Ich muss 3 Schlüsselelemente aus den Rechnungen extrahieren . Diese 3 Elemente befinden sich alle in einer Tabelle / Werbebuchung für alle Rechnungen.

Die 3 Elemente sind:

  • 1 : Tarifnummer (Ziffer)
  • 2 : Menge (immer eine Ziffer)
  • 3 : Gesamtzeilenbetrag (Geldwert)

Bitte beachten Sie den folgenden Screenshot, in dem ich dieses Feld auf einer Musterrechnung markiert habe.

Geben Sie hier die Bildbeschreibung ein

Ich habe dieses Projekt mit einem Vorlagenansatz gestartet, der auf regulären Ausdrücken basiert . Dies war jedoch überhaupt nicht skalierbar und ich endete mit Tonnen verschiedener Regeln.

Ich hoffe, dass maschinelles Lernen mir hier helfen kann - oder vielleicht eine hybride Lösung?

Der gemeinsame Nenner

In allen meinen Rechnungen besteht trotz der unterschiedlichen Layouts jede Werbebuchung immer aus einer Tarifnummer . Diese Tarifnummer besteht immer aus 8 Ziffern und ist immer wie folgt formatiert:

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

(Wobei "x" eine Ziffer von 0 bis 9 ist).

Des Weiteren , wie Sie auf der Rechnung sehen kann , gibt es sowohl einen Einheitspreis und ein Gesamtbetrag pro Zeile. Der Betrag, den ich benötige, ist immer der höchste für jede Zeile.

Die Ausgabe

Für jede Rechnung wie oben benötige ich die Ausgabe für jede Zeile. Dies könnte zum Beispiel so aussehen:

{
    "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"
}

Wohin von hier aus?

Ich bin mir nicht sicher, was ich tun möchte, fällt unter maschinelles Lernen und wenn ja, in welche Kategorie. Ist es Computer Vision? NLP? Named Entity Recognition?

Mein erster Gedanke war:

  1. Konvertieren Sie die Rechnung in Text. (Die Rechnungen sind alle in textfähigen PDFs, so dass ich so etwas wie verwenden kann pdftotext, um die genauen Textwerte zu erhalten)
  2. Erstellen Sie benutzerdefinierte benannte Entitäten für quantity, tariffundamount
  3. Exportieren Sie die gefundenen Entitäten.

Ich habe jedoch das Gefühl, dass mir etwas fehlt.

Kann mir jemand in die richtige Richtung helfen?

Bearbeiten:

Im Folgenden finden Sie einige weitere Beispiele dafür, wie ein Abschnitt mit einer Rechnungstabelle aussehen kann:

Musterrechnung Nr. 2 Geben Sie hier die Bildbeschreibung ein

Musterrechnung Nr. 3 Geben Sie hier die Bildbeschreibung ein

Bearbeiten 2:

Die drei Beispielbilder ohne Rahmen / Begrenzungsrahmen finden Sie weiter unten :

Bild 1: Probe 1 ohne bbox

Bild 2: Probe 2 ohne bbox

Bild 3: Probe 3 ohne bbox


Können Sie weitere Beispiele für die Eingabe-PDFs zeigen, um zu sehen, wie unterschiedlich diese tatsächlich sind? (= wie flexibel die Lösung sein soll)
sjaustirni

@sjaustirni Habe gerade zwei weitere hinzugefügt! Ich glaube, die größte Abweichung zwischen den Lieferantenrechnungen besteht darin, wie das Tabellenlayout (und anschließend die
Werbebuchungen

Perfekt! Angesichts dieser Beispiele würde ich wahrscheinlich das PDF in Text konvertieren und versuchen, die darin enthaltenen Werte mit dem vorhergehenden Etikett (z. B. Tariff No.:oder $) oder der Spalte, zu der es gehört, zu koppeln (hier kann es Ihnen helfen, die räumlichen Informationen der Buchstaben zu speichern). wenn irgendein OCR-Tool das tut). Ich glaube, Sie müssen sich weder mit diesem Problem (abgesehen von der vorgefertigten OCR) noch mit NLP (es ist keine natürliche Sprache) mit maschinellem Lernen befassen. Ohne zu sehen, wie gut diese Tools mit Ihren Daten funktionieren, können wir nur spekulieren, was der nächste Schritt ist und was notwendig ist: D
sjaustirni

@sjaustirni würde das nicht mit dem enden, was ich bereits mache, was nicht skalierbar ist? (Template-basierter / Regex-Ansatz).
Oliverbj

Können Sie die Tabelle selbst nicht aus dem PDF in eine Datenstruktur extrahieren und dann die Spalten verarbeiten? Vielleicht können Sie tabula-py verwenden, um dies zu tun, und dann die Menge und die Summe direkt und mit etwas Regex den Tarif erhalten
dhanushka

Antworten:


4

Ich arbeite an einem ähnlichen Problem in der Logistikbranche und vertraue mir, wenn ich sage, dass diese Dokumententabellen in unzähligen Layouts vorliegen. Zahlreiche Unternehmen, die dieses Problem etwas gelöst haben und verbessern, sind wie folgt aufgeführt

  • Führungskräfte: ABBYY, AntWorks, Kofax und WorkFusion
  • Hauptkonkurrenten: Automation Anywhere, Celaton, Datamatics, EdgeVerve, Extract Systems, Hyland, Hyperscience, Infrrd und Parascript
  • Aspiranten: Ikarus, Rossum, Shipmnts (Alex), Amazon (Textract), Docsumo, Docparser, Aidock

Die Kategorie, in die ich dieses Problem einordnen möchte, wäre multimodales Lernen , da sowohl Text- als auch Bildmodalitäten einen großen Beitrag zu diesem Problem leisten. Obwohl OCR-Token eine wichtige Rolle bei der Klassifizierung von Attributwerten spielen, sind ihre Position auf der Seite, der Abstand und die Abstände zwischen Zeichen wichtige Merkmale bei der Erkennung von Tabellen-, Zeilen- und Spaltengrenzen. Das Problem wird umso interessanter, wenn Zeilen über mehrere Seiten verteilt sind oder einige Spalten nicht leere Werte enthalten.

Während die akademische Welt und Konferenzen den Begriff Intelligente Dokumentverarbeitung verwenden , werden im Allgemeinen sowohl einzelne Felder als auch tabellarische Daten extrahiert. Ersteres ist in der Forschungsliteratur eher durch Attributwertklassifizierung bekannt, und letzteres ist durch Tabellenextraktion oder Extraktion mit wiederholter Struktur bekannt.

Bei unserem Versuch, diese halbstrukturierten Dokumente in den letzten drei Jahren zu verarbeiten, halte ich es für eine lange und mühsame Reise , sowohl Genauigkeit als auch Skalierbarkeit zu erreichen . Die Lösungen, die Skalierbarkeit / "vorlagenfreier" Ansatz bieten, haben einen Korpus von halbstrukturierten Geschäftsdokumenten in der Größenordnung von Zehntausenden, wenn nicht Millionen, kommentiert. Obwohl dieser Ansatz eine skalierbare Lösung ist, ist er so gut wie die Dokumente, für die er geschult wurde. Wenn Ihre Dokumente aus der Logistik- oder Versicherungsbranche stammen, die für ihre komplexen Layouts bekannt sind und aufgrund der Compliance-Verfahren sehr genau sein müssen, ist eine "vorlagenbasierte" Lösung das Allheilmittel gegen Ihre Krankheiten. Es wird garantiert mehr Genauigkeit geben.

Wenn Sie Links zu vorhandenen Forschungsergebnissen benötigen, erwähnen Sie diese in den Kommentaren unten und ich werde sie gerne teilen.

Außerdem würde ich empfehlen, pdfparser 1 über pdf2text oder pdfminer zu verwenden, da erstere Informationen auf Zeichenebene in digitalen Dateien mit deutlich besserer Leistung liefern.

Würde mich über Feedback freuen, da dies meine erste Antwort hier ist.


Wenn Sie nach einem Open Source Repo suchen, könnte github.com/invoice-x/invoice2data ein guter Ausgangspunkt sein
SIDDHARTH SAHANI

3

Hier ist ein Versuch mit OpenCV, die Idee ist:

  1. Erhalten Sie ein Binärbild. Wir laden das Bild, vergrößern imutils.resizees, um bessere OCR-Ergebnisse zu erzielen (siehe Tesseract verbessert die Qualität ), konvertieren in Graustufen und dann den Otsu-Schwellenwert , um ein Binärbild (1-Kanal) zu erhalten.

  2. Entfernen Sie die Tabellengitterlinien. Wir erstellen einen horizontalen und einen vertikalen Kernel und führen dann morphologische Operationen durch , um benachbarte Textkonturen zu einer einzigen Kontur zu kombinieren. Die Idee ist, eine ROI-Zeile als ein Stück für OCR zu extrahieren.

  3. Zeilen-ROIs extrahieren. Wir finden Konturen und sortieren sie dann von oben nach unten mit imutils.contours.sort_contours. Dies stellt sicher, dass wir jede Zeile in der richtigen Reihenfolge durchlaufen. Von hier aus durchlaufen wir die Konturen, extrahieren den Zeilen-ROI mithilfe von Numpy Slicing, OCR mithilfe von Pytesseract und analysieren dann die Daten.


Hier ist die Visualisierung jedes Schritts:

Bild eingeben

Geben Sie hier die Bildbeschreibung ein

Binäres Bild

Geben Sie hier die Bildbeschreibung ein

Morph schließen

Geben Sie hier die Bildbeschreibung ein

Visualisierung der Iteration durch jede Zeile

Geben Sie hier die Bildbeschreibung ein

Extrahierte Zeilen-ROIs

Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein

Ergebnis der Ausgabe der Rechnungsdaten:

{'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'}

Leider bekomme ich beim Anprobieren des 2. und 3. Bildes gemischte Ergebnisse. Diese Methode führt bei den anderen Bildern nicht zu hervorragenden Ergebnissen, da das Layout der Rechnungen alle unterschiedlich ist. Dieser Ansatz zeigt jedoch, dass es möglich ist, herkömmliche Bildverarbeitungstechniken zum Extrahieren der Rechnungsinformationen zu verwenden, unter der Annahme, dass Sie ein festes Rechnungslayout haben.

Code

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
Danke @nathancy! Obwohl diese Antwort keine generische Lösung für alle meine veröffentlichten Rechnungen ist, denke ich immer noch, dass sie die nächste ist, die ich je gesehen habe - und dies verwendet nur OpenCV. Sehr cool und dein Codebeispiel hat mir viel beigebracht! Nochmals vielen Dank, dass Sie sich die Zeit genommen haben, dies zu veröffentlichen.
oliverbj
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.