web-dev-qa-db-fra.com

Comment trouver un tableau comme une structure dans une image

J'ai un fichier de factures, je veux trouver un tableau dans chaque facture. cette position dans la table ne sera pas constante. Je suis donc venu au traitement d'images. J'ai d'abord essayé de convertir ma facture en image. puis j'ai trouvé le contour basé sur les bordures de la table qui a finalement pris la position de la table. J'ai utilisé le code ci-dessous pour accomplir ma tâche.

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.

Dans ce code thresh1, thresh2 je mettrai à jour en fonction du fichier.

Donc, en utilisant ce code, je peux lire avec succès les positions des tableaux dans les images, en utilisant cette position, je vais travailler sur mon fichier pdf de facture. Par exemple 

Échantillon 1:

 enter image description here

Échantillon 2:

 enter image description here

Échantillon 3:  enter image description here

Sortie:

Échantillon 1:

 enter image description here

Échantillon 2:

 enter image description here

Échantillon 3:

 enter image description here

Mais maintenant, j'ai un nouveau format qui n'a pas de frontière mais c'est un tableau. Comment résoudre ceci? Parce que toute mon opération dépend entièrement des limites de la table. Mais maintenant, je n'ai pas de bordures de table. Comment puis-je atteindre cet objectif? En tant que débutant en traitement d’image, je n’ai aucune idée de sortir de ce problème. Ma question est la suivante: y a-t-il un moyen de trouver une position basée sur la structure de la table? 

Par exemple, l'entrée de mon problème est présentée ci-dessous:

 enter image description here

Je voudrais trouver sa poistion comme ci-dessous:  enter image description here

Comment puis-je résoudre ce problème? C'est vraiment appréciable de me donner une idée à résoudre à partir de ce problème.

Merci d'avance.

5
Mohamed Thasin ah

Vaibhav a raison. Vous pouvez expérimenter différentes transformations morphologiques pour extraire ou grouper des pixels dans différentes formes, lignes, etc. Par exemple, l'approche peut être la suivante:

  1. Commencez par la Dilatation pour convertir le texte en points solides.
  2. Appliquez ensuite la fonction findContours comme étape suivante pour rechercher du texte Des cadres de sélection.
  3. Après avoir les boîtes de texte, il est possible d'appliquer un algorithme heuristique .__ pour regrouper les zones de texte en groupes en fonction de leurs coordonnées De cette façon, vous pouvez trouver un groupe de zones de texte alignées En rangées et en colonnes.
  4. Ensuite, vous pouvez appliquer un tri par coordonnées x et y et/ou une analyse Aux groupes afin de déterminer si les zones de texte groupées peuvent Former un tableau.

J'ai écrit un petit échantillon illustrant l'idée. J'espère que le code est explicite. J'ai mis quelques commentaires là aussi.

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)

J'ai la sortie suivante:

Sample table extraction

Bien sûr, pour rendre l'algorithme plus robuste et applicable à une variété d'images d'entrée différentes, il doit être ajusté en conséquence.

12
Dmytro

Vous pouvez essayer d'appliquer certaines transformations morphologiques (telles que Dilatation, Érosion ou Flou Gaussien) comme étape de pré-traitement avant votre fonction findContours.

Par exemple 

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)

Le dernier argument, les itérations, indique le degré de dilatation/érosion qui aura lieu (dans votre cas, sur le texte). Avoir une petite valeur aura pour résultat de petits contours indépendants même au sein d'un alphabet et de grandes valeurs encombreront de nombreux éléments proches. Vous devez trouver la valeur idéale pour ne recevoir que ce bloc de votre image.

Veuillez noter que j'ai pris 150 comme paramètre de seuil parce que je travaillais à extraire du texte à partir d'images avec différents arrière-plans et que cela fonctionnait mieux. Vous pouvez choisir de continuer avec la valeur que vous avez prise car il s'agit d'une image en noir et blanc.

1
Vaibhav Mehrotra