Saltar al contenido

¿Cómo extraer una tabla como texto del PDF usando Python?

Luego de consultar expertos en esta materia, programadores de diversas áreas y profesores hemos dado con la respuesta a la cuestión y la plasmamos en este post.

Solución:

  • Le sugiero que extraiga la tabla usando tabula.
  • Pase su pdf como argumento a la tabula api y le devolverá la tabla en forma de marco de datos.
  • Cada tabla en su pdf se devuelve como un marco de datos.
  • La tabla se devolverá en una lista de dataframea, para trabajar con dataframe necesita pandas.

Este es mi código para extraer pdf.

import pandas as pd
import tabula
file = "filename.pdf"
path = 'enter your directory path here'  + file
df = tabula.read_pdf(path, pages = '1', multiple_tables = True)
print(df)

Consulte este repositorio mío para obtener más detalles.

Esta respuesta es para cualquier persona que encuentre archivos PDF con imágenes y necesite usar OCR. No pude encontrar una solución lista para usar viable; nada que me diera la precisión que necesitaba.

Aquí están los pasos que encontré para trabajar.

  1. Utilizar pdfimages de https://poppler.freedesktop.org/ para convertir las páginas del pdf en imágenes.

  2. Use Tesseract para detectar rotación e ImageMagick mogrify arreglarlo.

  3. Use OpenCV para buscar y extraer tablas.

  4. Use OpenCV para buscar y extraer cada celda de la tabla.

  5. Use OpenCV para recortar y limpiar cada celda para que no haya ruido que confunda al software OCR.

  6. Use Tesseract para OCR cada celda.

  7. Combine el texto extraído de cada celda en el formato que necesite.

Escribí un paquete de python con módulos que pueden ayudar con esos pasos.

Repo: https://github.com/eihli/image-table-ocr

Documentos y fuente: https://eihli.github.io/image-table-ocr/pdf_table_extraction_and_ocr.html

Algunos de los pasos no requieren código, aprovechan herramientas externas como pdfimages y tesseract. Proporcionaré algunos ejemplos breves para un par de pasos que requieren código.

  1. Encontrar tablas:

Este enlace fue una buena referencia al descubrir cómo encontrar tablas. https://answers.opencv.org/question/63847/how-to-extract-tables-from-an-image/

import cv2

def find_tables(image):
    BLUR_KERNEL_SIZE = (17, 17)
    STD_DEV_X_DIRECTION = 0
    STD_DEV_Y_DIRECTION = 0
    blurred = cv2.GaussianBlur(image, BLUR_KERNEL_SIZE, STD_DEV_X_DIRECTION, STD_DEV_Y_DIRECTION)
    MAX_COLOR_VAL = 255
    BLOCK_SIZE = 15
    SUBTRACT_FROM_MEAN = -2

    img_bin = cv2.adaptiveThreshold(
        ~blurred,
        MAX_COLOR_VAL,
        cv2.ADAPTIVE_THRESH_MEAN_C,
        cv2.THRESH_BINARY,
        BLOCK_SIZE,
        SUBTRACT_FROM_MEAN,
    )
    vertical = horizontal = img_bin.copy()
    SCALE = 5
    image_width, image_height = horizontal.shape
    horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (int(image_width / SCALE), 1))
    horizontally_opened = cv2.morphologyEx(img_bin, cv2.MORPH_OPEN, horizontal_kernel)
    vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, int(image_height / SCALE)))
    vertically_opened = cv2.morphologyEx(img_bin, cv2.MORPH_OPEN, vertical_kernel)

    horizontally_dilated = cv2.dilate(horizontally_opened, cv2.getStructuringElement(cv2.MORPH_RECT, (40, 1)))
    vertically_dilated = cv2.dilate(vertically_opened, cv2.getStructuringElement(cv2.MORPH_RECT, (1, 60)))

    mask = horizontally_dilated + vertically_dilated
    contours, hierarchy = cv2.findContours(
        mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE,
    )

    MIN_TABLE_AREA = 1e5
    contours = [c for c in contours if cv2.contourArea(c) > MIN_TABLE_AREA]
    perimeter_lengths = [cv2.arcLength(c, True) for c in contours]
    epsilons = [0.1 * p for p in perimeter_lengths]
    approx_polys = [cv2.approxPolyDP(c, e, True) for c, e in zip(contours, epsilons)]
    bounding_rects = [cv2.boundingRect(a) for a in approx_polys]

    # The link where a lot of this code was borrowed from recommends an
    # additional step to check the number of "joints" inside this bounding rectangle.
    # A table should have a lot of intersections. We might have a rectangular image
    # here though which would only have 4 intersections, 1 at each corner.
    # Leaving that step as a future TODO if it is ever necessary.
    images = [image[y:y+h, x:x+w] for x, y, w, h in bounding_rects]
    return images
  1. Extraer celdas de la tabla.

Esto es muy similar a 2, así que no incluiré todo el código. La parte a la que me referiré será en la clasificación de las celdas.

Queremos identificar las celdas de izquierda a derecha, de arriba a abajo.

Encontraremos el rectángulo con la esquina superior izquierda. Luego encontraremos todos los rectángulos que tienen un centro que está dentro de los valores de arriba y abajo de ese rectángulo de arriba a la izquierda. Luego ordenaremos esos rectángulos por el valor x de su centro. Eliminaremos esos rectángulos de la lista y repetiremos.

def cell_in_same_row(c1, c2):
    c1_center = c1[1] + c1[3] - c1[3] / 2
    c2_bottom = c2[1] + c2[3]
    c2_top = c2[1]
    return c2_top < c1_center < c2_bottom

orig_cells = [c for c in cells]
rows = []
while cells:
    first = cells[0]
    rest = cells[1:]
    cells_in_same_row = sorted(
        [
            c for c in rest
            if cell_in_same_row(c, first)
        ],
        key=lambda c: c[0]
    )

    row_cells = sorted([first] + cells_in_same_row, key=lambda c: c[0])
    rows.append(row_cells)
    cells = [
        c for c in rest
        if not cell_in_same_row(c, first)
    ]

# Sort rows by average height of their center.
def avg_height_of_center(row):
    centers = [y + h - h / 2 for x, y, w, h in row]
    return sum(centers) / len(centers)

rows.sort(key=avg_height_of_center)

Acuérdate de que te brindamos la opción de interpretar tu experiencia si descubriste tu apuro .

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *