При создании конвейера оптического распознавания символов для автоматического извлечения данных из довольно грязного отсканированного документа я застрял на этапе предварительной обработки изображений, когда мне нужно обрезать все ячейки таблицы перед запуском на них Tesseract.Одним из основных осложнений является необходимость каким-либо образом маркировать строки и столбцы, поскольку извлеченные данные нуждаются в структуре, чтобы иметь какое-либо значение.Кроме того, возможность извлекать только определенные строки и / или столбцы значительно ускорила бы мой конвейер при работе с несколькими документами аналогичного состава, что является его ожидаемым использованием.
Вот код, который я сейчас использую, почти непосредственно взятыйиз открытого источника алгоритм обнаружения ящика Канана Вьяса, найденный ранее на Github
import cv2
import numpy as np
import os
import glob
def sort_contours(cnts, method="left-to-right"):
reverse = False
i = 0
if method == "right-to-left" or method == "bottom-to-top":
reverse = True
if method == "top-to-bottom" or method == "bottom-to-top":
i = 1
boundingBoxes = [cv2.boundingRect(c) for c in cnts]
(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
key=lambda b: b[1][i], reverse=reverse))
return (cnts, boundingBoxes)
def box_extraction(img_for_box_extraction_path, cropped_dir_path):
img = cv2.imread(img_for_box_extraction_path, 0)
img[int(0):int(img.shape[0]),int(0):int(5)] = [255, 255, 255]
(thresh, img_bin) = cv2.threshold(img, 128, 255,
cv2.THRESH_BINARY | cv2.THRESH_OTSU)
img_bin = 255-img_bin
cv2.imwrite("Image_bin.jpg",img_bin)
kernel_length = np.array(img).shape[1]//40
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, kernel_length))
hori_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_length, 1))
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
img_temp1 = cv2.erode(img_bin, vertical_kernel, iterations=3)
vertical_lines_img = cv2.dilate(img_temp1, vertical_kernel, iterations=3)
cv2.imwrite("vertical_lines.jpg",vertical_lines_img)
img_temp2 = cv2.erode(img_bin, hori_kernel, iterations=3)
horizontal_lines_img = cv2.dilate(img_temp2, hori_kernel, iterations=3)
cv2.imwrite("horizontal_lines.jpg",horizontal_lines_img)
alpha = 0.5
beta = 1.0 - alpha
img_final_bin = cv2.addWeighted(vertical_lines_img, alpha, horizontal_lines_img, beta, 0.0)
img_final_bin = cv2.erode(~img_final_bin, kernel, iterations=2)
(thresh, img_final_bin) = cv2.threshold(img_final_bin, 128, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
cv2.imwrite("img_final_bin.png",img_final_bin)
im2, contours, hierarchy = cv2.findContours(
img_final_bin, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
(contours, boundingBoxes) = sort_contours(contours, method="top-to-bottom")
idx = 0
for c in contours:
x, y, w, h = cv2.boundingRect(c)
if (w > 30 and h > 20) and w > h and h < 150:
idx += 1
new_img = img[y:y+h, x:x+w]
cv2.imwrite(cropped_dir_path+str(idx) + '.png', new_img)
cv2.drawContours(img, contours, -1, (0, 0, 255), 3)
cv2.imwrite("./Temp/img_contour.jpg", img)
output_dir = "./Cropped/"
files = glob.glob(output_dir+"*")
for f in files:
os.remove(f)
box_extraction("./prototype/page_cropped.png", output_dir)
Проблема с этим подходом заключается в сортировке - используемые здесь простые «направленные» директивы не сохраняют структуру таблицыособенно если таблица немного искажена в исходном изображении из-за компромисса между границами деформации деформации и сохранением содержимого таблицы для OCR.Если это важно, я не ожидаю в настоящее время иметь дело с ячейками таблицы, которые занимают более одной строки или столбца.
Мое фактическое изображение содержит конфиденциальные бизнес-данные, поэтому я сделал эту фиктивную фотографию с помощью GIMP.Этого должно быть достаточно для демонстрационных целей, вам нужно только указать на нее функцию box_extraction ().
Учитывая, что неполные ячейки справа должны бытьигнорируемый алгоритмом извлечения ящиков, я ожидал получить 9 x 4 = 36 изображений с именами «1.png» (с ячейкой 0,0), «2.png» (0,1) и т. д., каждый набор из 4 соответствуетячейки одной строки (должна быть возможность получить все ячейки одного столбца, выбрав ячейку заголовка из первой строки и каждого пятого изображения после этого).
Однако, теперь выходные изображения в конечном итоге расположены в очень странном порядке, с «1.png» по «4.png», содержащим ячейки первого ряда в обратном порядке, «5.png» - первая ячейкавторой строки, «6.png» - последняя ячейка второго ряда и после нее развал шаблона.