Cómo encontrar la tabla como estructura en la imagen.

Tengo una factura de archivos, quiero encontrar la tabla en cada factura. Esta posición de la mesa no será constante. Así que llegué al procesamiento de imágenes. Primero intenté convertir mi factura en imagen. Luego encontré que el contorno basado en los bordes de la mesa finalmente tomó la posición de la tabla. Usé el código de abajo para lograr mi tarea.

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. 

En este código thresh1 , thresh2 actualizaré según el archivo.

Entonces, al usar este código puedo leer con éxito las posiciones de las tablas en las imágenes, usando esta posición, trabajaré en mi archivo pdf de factura. Por ejemplo

Muestra 1:

introduzca la descripción de la imagen aquí

Muestra 2:

introduzca la descripción de la imagen aquí

Muestra 3: introduzca la descripción de la imagen aquí

Salida:

Muestra 1:

introduzca la descripción de la imagen aquí

Muestra 2:

introduzca la descripción de la imagen aquí

Muestra 3:

introduzca la descripción de la imagen aquí

Pero ahora tengo un nuevo formato que no tiene bordes pero es una tabla. ¿Cómo resolver esto? Porque toda mi operación depende totalmente de los bordes de la mesa. Pero ahora no tengo una mesa de bordes. ¿Cómo puedo conseguir esto? Como soy un principiante en el procesamiento de imágenes, no tengo idea de salir de este problema. Mi pregunta es: ¿hay alguna forma de encontrar una posición basada en la estructura de la tabla?

Por ejemplo, mi entrada de problema se ve a continuación:

introduzca la descripción de la imagen aquí

Me gustaría encontrar su poistion como abajo: introduzca la descripción de la imagen aquí

¿Como puedo resolver esto? Es realmente apreciable darme una idea para resolver este problema.

Gracias por adelantado.

Vaibhav tiene razón. Puede experimentar con las diferentes transformaciones morfológicas para extraer o agrupar píxeles en diferentes formas, líneas, etc. Por ejemplo, el enfoque puede ser el siguiente:

  1. Comience desde la Dilatación para convertir el texto en los puntos sólidos.
  2. Luego, aplique la función findContours como paso siguiente para encontrar cuadros de delimitación de texto.
  3. Después de tener los cuadros de delimitación de texto, es posible aplicar algún algoritmo heurístico para agrupar los cuadros de texto en grupos por sus coordenadas. De esta manera puedes encontrar grupos de áreas de texto alineadas en filas y columnas.
  4. Luego, puede aplicar la ordenación por coordenadas xey, y / o algún análisis a los grupos para tratar de encontrar si los cuadros de texto agrupados pueden formar una tabla.

Escribí una pequeña muestra que ilustra la idea. Espero que el código sea auto explicativo. He puesto algunos comentarios allí también.

 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 __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) 

Tengo la siguiente salida:

Extracción de la mesa de muestra

Por supuesto, para hacer que el algoritmo sea más robusto y aplicable a una variedad de imágenes de entrada diferentes, debe ajustarse de manera correspondiente.

Puede intentar aplicar algunas transformaciones morfológicas (como la dilatación, la erosión o el desenfoque gaussiano) como un paso de preprocesamiento antes de la función findContours.

Por ejemplo

 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) 

El último argumento, iteraciones muestra el grado de dilatación / erosión que tendrá lugar (en su caso, en el texto). Tener un valor pequeño dará como resultado pequeños contornos independientes incluso dentro de un alfabeto y los valores grandes agruparán muchos elementos cercanos. Necesitas encontrar el valor ideal para que solo se obtenga ese bloque de tu imagen.

Tenga en cuenta que he tomado 150 como parámetro de umbral porque he estado trabajando para extraer texto de imágenes con diferentes orígenes y esto funcionó mejor. Puede elegir continuar con el valor que ha tomado, ya que es una imagen en blanco y negro.