it-swarm.com.de

Wie finde ich eine tabellenartige Struktur im Bild?

Ich habe eine Rechnungsdatei, ich möchte in jeder Rechnung eine Tabelle finden. Diese Tischposition wird nicht konstant sein. Also kam ich zur Bildverarbeitung. Zuerst habe ich versucht, meine Rechnung in ein Bild umzuwandeln. dann fand ich die kontur basierend auf tischrändern endlich tischposition. Ich habe den folgenden Code verwendet, um meine Aufgabe zu erfüllen.

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.

In diesem Code thresh1, thresh2 werde ich basierend auf der Datei aktualisieren.

Mit diesem Code kann ich erfolgreich Positionen von Tabellen in Bildern lesen. Mit dieser Position arbeite ich an meiner PDF-Datei mit der Rechnung. Zum Beispiel 

Probe 1:

 enter image description here

Probe 2:

 enter image description here

Probe 3:  enter image description here

Ausgabe:

Probe 1:

 enter image description here

Probe 2:

 enter image description here

Probe 3:

 enter image description here

Aber jetzt habe ich ein neues Format, das keine Grenzen hat, aber es ist eine Tabelle. Wie löse ich das? Weil meine gesamte Operation vollständig von Tabellenrändern abhängt. Aber jetzt habe ich keine Tischgrenzen. Wie kann ich das erreichen? Als Anfänger in der Bildverarbeitung habe ich keine Ahnung, dieses Problem zu lösen. Meine Frage ist: Gibt es eine Möglichkeit, eine Position basierend auf der Tabellenstruktur zu finden? 

Zum Beispiel sieht meine Problemeingabe unten aus:

 enter image description here

Ich würde gerne die folgende Piste finden:  enter image description here

Wie kann ich das lösen? Es ist wirklich wertvoll, mir eine Idee zu geben, um dieses Problem zu lösen.

Danke im Voraus.

5

Vaibhav hat recht. Sie können mit den verschiedenen morphologischen Transformationen experimentieren, um Pixel in verschiedene Formen, Linien usw. zu extrahieren oder zu gruppieren. Die Vorgehensweise kann beispielsweise folgendermaßen aussehen:

  1. Beginnen Sie mit der Erweiterung, um den Text in die festen Punkte zu konvertieren.
  2. Wenden Sie dann die Funktion findContours als nächsten Schritt an, um Text Begrenzungsrahmen zu finden.
  3. Nach dem Verwenden der Textbegrenzungsfelder ist es möglich, einen Heuristikalgorithmus anzuwenden, um die Textfelder anhand ihrer -Koordinaten in Gruppen zu gruppieren. Auf diese Weise können Sie eine Gruppe von Textbereichen finden, die in Zeilen und Spalten ausgerichtet sind.
  4. Anschließend können Sie die Sortierung nach x- und y-Koordinaten und/oder einigen -Analysen auf die Gruppen anwenden, um herauszufinden, ob die gruppierten Textfelder Eine Tabelle bilden können.

Ich habe ein kleines Beispiel zur Veranschaulichung der Idee geschrieben. Ich hoffe, der Code ist selbsterklärend. Ich habe dort auch einige Kommentare eingefügt.

import os
import cv2
import imutils

# This only works if there's 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
    contours = cv2.findContours(pre, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
    contours = contours[0] if imutils.is_cv2() else contours[1]

    # 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 __== "__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)

Ich habe folgende Ausgabe erhalten:

Sample table extraction

Um den Algorithmus robuster zu machen und auf eine Vielzahl verschiedener Eingangsbilder anwendbar zu machen, muss er natürlich entsprechend angepasst werden.

12
Dmytro

Sie können versuchen, einige morphologische Transformationen (wie Dilation, Erosion oder Gaußsche Unschärfe) als Vorverarbeitungsschritt vor der findContours-Funktion anzuwenden

Zum Beispiel 

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)

Das letzte Argument, Iterationen, zeigt den Grad der Ausdehnung/Erosion (in Ihrem Fall im Text). Ein kleiner Wert führt zu kleinen unabhängigen Konturen, selbst innerhalb eines Alphabets, und große Werte werden viele nahegelegene Elemente vereinen. Sie müssen den idealen Wert finden, damit nur der Block Ihres Bildes erhalten wird.

Bitte beachten Sie, dass ich 150 als Schwellenwert genommen habe, da ich an der Extraktion von Text aus Bildern mit unterschiedlichen Hintergründen gearbeitet habe und dies besser geklappt hat. Sie können mit dem Wert fortfahren, den Sie genommen haben, da es sich um ein Schwarzweißbild handelt.

1