Подтвердить что ты не робот

Как найти таблицу как структуру в изображении

У меня разные типы файлов накладных, я хочу найти таблицу в каждом файле накладной. В этой таблице положение не является постоянным. Итак, я иду на обработку изображений. Сначала я попытался преобразовать свой счет в изображение, затем я нашел контур, основанный на границах таблицы. Наконец, я могу определить положение таблицы. Для задачи я использовал приведенный ниже код.

with Image(page) as page_image:
    page_image.alpha_channel = False #eliminates transperancy
    img_buffer=np.asarray(bytearray(page_image.make_blob()), dtype=np.uint8)
    img = cv2.imdecode(img_buffer, cv2.IMREAD_UNCHANGED)

    ret, thresh = cv2.threshold(img, 127, 255, 0)
    im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    margin=[]
    for contour in contours:
        # get rectangle bounding contour
        [x, y, w, h] = cv2.boundingRect(contour)
        # Don't plot small false positives that aren't text
        if (w >thresh1 and h> thresh2):
                margin.append([x, y, x + w, y + h])
    #data cleanup on margin to extract required position values.

В этом коде thresh1, thresh2 я буду обновляться на основе файла.

Таким образом, используя этот код, я могу успешно читать позиции таблиц на изображениях, используя эту позицию, я буду работать с моим PDF файлом счета. Например,

Образец 1:

enter image description here

Образец 2:

enter image description here

Образец 3: enter image description here

Выход:

Образец 1:

enter image description here

Образец 2:

enter image description here

Образец 3:

enter image description here

Но теперь у меня есть новый формат, у которого нет границ, кроме таблицы. Как это решить? Потому что вся моя работа зависит только от границ таблиц. Но сейчас у меня нет границ таблицы. Как мне этого добиться? Я понятия не имею, чтобы выйти из этой проблемы. У меня вопрос, есть ли способ найти позицию, основанную на структуре таблицы?

Например, моя проблема ввода выглядит следующим образом:

enter image description here

Я хотел бы найти свою позицию, как показано ниже: enter image description here

Как я могу решить это? Это действительно заметно, чтобы дать мне идею решить проблему.

Заранее спасибо.

4b9b3361

Ответ 1

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

  1. Начните с расширения, чтобы преобразовать текст в сплошные пятна.
  2. Затем примените функцию findContours в качестве следующего шага, чтобы найти текст ограничивающие рамки.
  3. После ограничения текста можно применить некоторые эвристический алгоритм для кластеризации текстовых полей по группам по координаты. Таким образом, вы можете найти группы текстовых областей, выровненных на строки и столбцы.
  4. Затем вы можете применить сортировку по координатам х и у и/или некоторым анализ групп, чтобы попытаться найти, могут ли сгруппированные текстовые поля сформировать таблицу.

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

import os
import cv2
import imutils

# This only works if there only one table on a page
# Important parameters:
#  - morph_size
#  - min_text_height_limit
#  - max_text_height_limit
#  - cell_threshold
#  - min_columns


def pre_process_image(img, save_in_file, morph_size=(8, 8)):

    # get rid of the color
    pre = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # Otsu threshold
    pre = cv2.threshold(pre, 250, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    # dilate the text to make it solid spot
    cpy = pre.copy()
    struct = cv2.getStructuringElement(cv2.MORPH_RECT, morph_size)
    cpy = cv2.dilate(~cpy, struct, anchor=(-1, -1), iterations=1)
    pre = ~cpy

    if save_in_file is not None:
        cv2.imwrite(save_in_file, pre)
    return pre


def find_text_boxes(pre, min_text_height_limit=6, max_text_height_limit=40):
    # Looking for the text spots contours
    # OpenCV 3
    # img, contours, hierarchy = cv2.findContours(pre, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
    # OpenCV 4
    contours, hierarchy = cv2.findContours(pre, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

    # Getting the texts bounding boxes based on the text size assumptions
    boxes = []
    for contour in contours:
        box = cv2.boundingRect(contour)
        h = box[3]

        if min_text_height_limit < h < max_text_height_limit:
            boxes.append(box)

    return boxes


def find_table_in_boxes(boxes, cell_threshold=10, min_columns=2):
    rows = {}
    cols = {}

    # Clustering the bounding boxes by their positions
    for box in boxes:
        (x, y, w, h) = box
        col_key = x // cell_threshold
        row_key = y // cell_threshold
        cols[row_key] = [box] if col_key not in cols else cols[col_key] + [box]
        rows[row_key] = [box] if row_key not in rows else rows[row_key] + [box]

    # Filtering out the clusters having less than 2 cols
    table_cells = list(filter(lambda r: len(r) >= min_columns, rows.values()))
    # Sorting the row cells by x coord
    table_cells = [list(sorted(tb)) for tb in table_cells]
    # Sorting rows by the y coord
    table_cells = list(sorted(table_cells, key=lambda r: r[0][1]))

    return table_cells


def build_lines(table_cells):
    if table_cells is None or len(table_cells) <= 0:
        return [], []

    max_last_col_width_row = max(table_cells, key=lambda b: b[-1][2])
    max_x = max_last_col_width_row[-1][0] + max_last_col_width_row[-1][2]

    max_last_row_height_box = max(table_cells[-1], key=lambda b: b[3])
    max_y = max_last_row_height_box[1] + max_last_row_height_box[3]

    hor_lines = []
    ver_lines = []

    for box in table_cells:
        x = box[0][0]
        y = box[0][1]
        hor_lines.append((x, y, max_x, y))

    for box in table_cells[0]:
        x = box[0]
        y = box[1]
        ver_lines.append((x, y, x, max_y))

    (x, y, w, h) = table_cells[0][-1]
    ver_lines.append((max_x, y, max_x, max_y))
    (x, y, w, h) = table_cells[0][0]
    hor_lines.append((x, max_y, max_x, max_y))

    return hor_lines, ver_lines


if __name__ == "__main__":
    in_file = os.path.join("data", "page.jpg")
    pre_file = os.path.join("data", "pre.png")
    out_file = os.path.join("data", "out.png")

    img = cv2.imread(os.path.join(in_file))

    pre_processed = pre_process_image(img, pre_file)
    text_boxes = find_text_boxes(pre_processed)
    cells = find_table_in_boxes(text_boxes)
    hor_lines, ver_lines = build_lines(cells)

    # Visualize the result
    vis = img.copy()

    # for box in text_boxes:
    #     (x, y, w, h) = box
    #     cv2.rectangle(vis, (x, y), (x + w - 2, y + h - 2), (0, 255, 0), 1)

    for line in hor_lines:
        [x1, y1, x2, y2] = line
        cv2.line(vis, (x1, y1), (x2, y2), (0, 0, 255), 1)

    for line in ver_lines:
        [x1, y1, x2, y2] = line
        cv2.line(vis, (x1, y1), (x2, y2), (0, 0, 255), 1)

    cv2.imwrite(out_file, vis)

У меня есть следующий вывод:

Sample table extraction

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

Обновление: Обновлен код с учетом изменений API OpenCV для findContours. Если у вас установлена более старая версия OpenCV - используйте соответствующий вызов. Похожие посты.

Ответ 2

Вы можете попробовать применить некоторые морфологические преобразования (такие как Dilation, Erosion или Gaussian Blur) в качестве этапа предварительной обработки перед вашей функцией findContours

Например

blur = cv2.GaussianBlur(g, (3, 3), 0)
ret, thresh1 = cv2.threshold(blur, 150, 255, cv2.THRESH_BINARY)
bitwise = cv2.bitwise_not(thresh1)
erosion = cv2.erode(bitwise, np.ones((1, 1) ,np.uint8), iterations=5)
dilation = cv2.dilate(erosion, np.ones((3, 3) ,np.uint8), iterations=5)

Последний аргумент, итерации показывает степень дилатации/эрозии, которая будет иметь место (в вашем случае, по тексту). Наличие небольшого значения приведет к небольшим независимым контурам даже внутри алфавита, а большие значения будут включать в себя множество близлежащих элементов. Вам нужно найти идеальное значение, чтобы получить только тот блок вашего изображения.

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

Ответ 3

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

Если вы ищете онлайн-конвертер - закажите ExtractTable.com

Если вы разработчик Python, попробуйте ExtractTable-py - для вывода потребуется всего 3 строки кода

Найдите страницу "dev/docs" на их веб-сайте для доступа к API.