Извлечение текстовой информации из файлов PDF с различными форматами - машинное обучение - PullRequest
8 голосов
/ 05 февраля 2020

Мне нужна помощь в проекте ML, который я сейчас пытаюсь создать.

Я получаю много счетов от разных поставщиков - все в их собственном уникальном макете. Мне нужно извлечь 3 ключевые элементы из счетов. Все эти 3 элементы расположены в таблице / отдельных позициях для всех счетов.

. 3 элементов:

  • 1 : номер тарифа (ди git)
  • 2 : количество (всегда ди git)
  • 3 : Общая сумма строки (денежная стоимость)

Пожалуйста, обратитесь к скриншоту ниже, где я отметил эти поля в образце счета.

enter image description here

Я начал этот проект с шаблонным подходом, основанным на регулярных выражениях . Это, однако, не было масштабируемым вообще, и я получил множество различных правил.

Я надеюсь, что машинное обучение может помочь мне здесь - или, может быть, гибридное решение?

Общее знаменатель

В всех моих счетов, несмотря на различные схемы размещения, каждая позиция будет всегда состоять из одного номера тарифа . Этот тарифный номер всегда состоит из 8 цифр и всегда форматируется одним из следующих способов:

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

(где "x" - это ди git от 0 до 9).

Далее , как вы можете видеть на счете цена за единицу и общая сумма на линию. Сумма, которая мне понадобится, составляет всегда самое высокое для каждой строки.

Вывод

Для каждого счета-фактуры, подобного приведенному выше, мне нужен вывод для каждой строки. Например, это может быть что-то вроде этого:

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

Куда go отсюда?

Я не уверен, что то, что я ищу, подпадает под машинное обучение, и если так в какой категории. Это компьютерное зрение? NLP? Признание именованного субъекта?

Моя первоначальная мысль заключалась в следующем:

  1. Преобразовать счет в текст. (Все счета представлены в текстовых PDF-файлах, поэтому я могу использовать что-то вроде pdftotext для получения точных текстовых значений)
  2. Создание пользовательских именованных объектов для quantity, tariff и amount
  3. Экспорт найденных объектов.

Однако я чувствую, что что-то упустил.

Может ли кто-нибудь помочь мне в правильном направлении?

Редактировать:

Пожалуйста, посмотрите ниже еще несколько примеров того, как раздел таблицы счетов может выглядеть так:

Пример счета-фактуры № 2 enter image description here

Пример счета-фактуры № 3 enter image description here

Редактировать 2:

См. Ниже три образца изображений без границ / ограничивающих рамок:

Изображение 1: Sample 1 without bbox

Изображение 2: Sample 2 without bbox

Изображение 3: Sample 3 without bbox

Ответы [ 2 ]

4 голосов
/ 09 февраля 2020

Я работаю над аналогичной проблемой в логистической отрасли и, поверьте мне, когда я говорю, что эти таблицы документов представлены в бесчисленном множестве макетов. Многочисленные компании, которые в некоторой степени решили и улучшают эту проблему, упоминаются как

  • Лидеры: 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 играют жизненно важную роль в классификации атрибутов-значений, их положение на странице, интервалы и межсимвольные расстояния являются очень важными функциями при определении границ таблицы, строки и столбца. Проблема становится еще более интересной, когда строки разбиваются на страницах или некоторые столбцы содержат непустые значения.

В то время как академический c мир и конференции использует термин Интеллектуальная обработка документов , в общем случае для извлечения как особых полей, так и табличных данных. Первый более известен по классификации атрибут-значение, а второй известен по извлечению таблиц или повторяющихся структур в исследовательской литературе.

В ходе нашего исследования этих полуструктурированных документов в течение 3 лет я чувствую, что достижение точности и масштабируемости - долгое и трудное путешествие. Решения, которые предлагают подход «масштабируемость / без шаблонов», имеют аннотированный корпус полуструктурированных бизнес-документов порядка десятков тысяч, если не миллионов. Хотя этот подход является масштабируемым решением, он так же хорош, как и документы, на которых он был обучен. Если ваши документы поступают из сектора логистики или страхования, которые известны своей сложной структурой и требуют сверхточности из-за процедур соблюдения, то «шаблонное» решение станет панацеей от ваших недугов. Это гарантированно даст больше точности.

Если вам нужны ссылки на существующие исследования, укажите в комментариях ниже, и я буду рад поделиться ими.

Кроме того, я бы порекомендовал использовать pdfparser 1 поверх pdf2text или pdfminer, поскольку первый дает информацию об уровне символов в цифровых файлах при значительно лучшей производительности.

Был бы рад включить любые отзывы, так как это мой первый ответ здесь.

3 голосов
/ 13 февраля 2020

Вот попытка использования OpenCV, идея в следующем:

  1. Получение бинарного изображения. Загружаем изображение, увеличиваем, используя imutils.resize чтобы получить лучшие результаты распознавания (см. Тессеракт улучшает качество ), преобразуйте в оттенки серого, затем Порог Оцу для получения двоичного изображения (1 канал).

  2. Удаление линий сетки таблицы. Мы создаем горизонтальное и вертикальное ядра , затем выполняем морфологические операции , чтобы объединить смежные текстовые контуры в один контур. Идея состоит в том, чтобы извлечь строку ROI как одно целое в OCR.

  3. Извлечь ROI строки. Мы найдем контуры , а затем отсортируем сверху снизу используя imutils.contours.sort_contours. Это гарантирует, что мы будем перебирать каждую строку в правильном порядке. Отсюда мы перебираем контуры, извлекаем ROI строки, используя Numpy нарезку, OCR - Pytesseract , затем анализируем данные.


Вот визуализация каждого шага:

Входное изображение

enter image description here

Двоичное изображение

enter image description here

Morph close

enter image description here

Визуализация итерации по каждой строке

enter image description here

Извлеченные ROI строки

enter image description here enter image description here enter image description here

Вывод результат данных счета:

{'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()
...