Исправьте дыры и недостающие контуры вашей таблицы с помощью OpenCV / Python
Когда документы оцифровываются с помощью сканирования или фотографий, качество изображения может ухудшаться из-за неправильных настроек или плохих условий. В случае распознавания таблицы это может привести к нарушению структуры таблицы. Следовательно, на некоторых линиях могут быть небольшие изъяны или даже дыры, и таблица в целом не распознается как связная система. Иногда таблицы также создаются без линий с некоторых сторон ячеек. В этом случае система закрывает строки строками вышеперечисленных ячеек. Существует большое разнообразие таблиц и типов ячеек, и, как часто бывает, предлагаемый код может работать не для всех идеально. Тем не менее, с небольшими изменениями это полезно во многих случаях.
Большинство алгоритмов распознавания клеток основаны на линиях и клеточной структуре. Отсутствие линий приводит к плохому распознаванию из-за «забытых» ячеек. То же самое и с этим подходом. Линии необходимы. Если в вашей таблице нет четко различимых линий, это не сработает. А теперь взглянем на код!
Во-первых, нам нужно выполнить импорт. В этом случае он ограничен только двумя импортами: OpenCV и NumPy.
import cv2 import numpy as np
Затем нам нужно загрузить изображение / документ, содержащий таблицу. Если это целый документ с текстом, окружающим таблицу, вам нужно сначала распознать таблицу и обрезать изображение до размера таблицы. (Чтобы узнать больше о распознавании таблиц и обрезке до размеров таблицы, нажмите здесь.)
# Load the image image = cv2.imread(‘/Users/marius/Desktop/holes.png’, -1)
Как вы можете видеть на исходном изображении, линии ячеек во втором ряду соединены не полностью. При распознавании таблицы вторая строка не будет распознаваться и рассматриваться алгоритмом, потому что ячейки не являются закрытым прямоугольником. Решение, предлагаемое в этой статье, работает не только в этом случае зазоров. Это также работает для других ломаных линий или отверстий в таблицах.
Теперь нам нужно получить размер изображения (высоту и ширину) и сохранить его в переменных hei и wid.
(hei,wid,_) = image.shape
Следующим шагом является масштабирование серого и размытие с помощью фильтра Гаусса. Это помогает распознавать линии. Для получения дополнительной информации о градациях серого щелкните здесь.
#Grayscale and blur the image gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blur = cv2.GaussianBlur(gray, (3,3), 0)
Затем нам нужно ограничить наше изображение. Если вы хотите узнать немного больше о пороговых значениях, вы можете прочитать об этом в этой статье: Щелкните здесь (это все вплоть до раздела Бинаризация изображения).
#Threshold the image thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
Затем используется алгоритм findContours OpenCV для получения положения всех контуров. Для всех контуров рисуется ограничивающий прямоугольник для создания прямоугольников / ячеек таблицы. Затем поля сохраняются в списке с четырьмя значениями x, y, шириной, высотой.
#Retrieve contours contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) #Create box-list box = [] # Get position (x,y), width and height for every contour for c in contours: x, y, w, h = cv2.boundingRect(c) box.append([x,y,w,h])
Затем все значения высоты, ширины, x и y отдельно сохраняются в списках, и вычисляются минимальные значения высоты, ширины, а также x и y. Кроме того, необходимы максимальные y и x.
#Create separate lists for all values heights=[] widths=[] xs=[] ys=[] #Store values in lists for b in box: heights.append(b[3]) widths.append(b[2]) xs.append(b[0]) ys.append(b[1]) #Retrieve minimum and maximum of lists min_height = np.min(heights) min_width = np.min(widths) min_x = np.min(xs) min_y = np.min(ys) max_y = np.max(ys) max_x = np.max(xs)
Сохраненные значения теперь используются, чтобы понять, где находится таблица. Минимальное значение y можно использовать для получения самой верхней строки таблицы, которую можно рассматривать как начальную точку таблицы. Минимальное значение x - это левый край таблицы. Чтобы получить приблизительный размер, нам нужно получить максимальное значение y, которое является ячейкой или строкой в нижней части таблицы. Y-значение последней строки представляет верхний край ячейки, а не нижнюю часть ячейки. Чтобы учитывать полный размер ячейки и таблицы, необходимо прибавить высоту ячейки последних строк к максимальному значению y, чтобы получить полную высоту таблицы. Максимальный x будет последним столбцом и, соответственно, самой правой ячейкой / строкой таблицы. Значение x - это левый край каждой ячейки, и последовательно нам нужно добавить ширину последнего столбца к максимальному значению x, чтобы получить полную ширину таблицы.
#Retrieve height where y is maximum (edge at bottom, last row of table) for b in box: if b[1] == max_y: max_y_height = b[3] #Retrieve width where x is maximum (rightmost edge, last column of table) for b in box: if b[0] == max_x: max_x_width = b[2]
На следующем этапе все горизонтальные и вертикальные линии извлекаются и сохраняются отдельно. Это делается путем создания ядра, которое устанавливает пороговые значения и применяет морфологические операции. Горизонтальное ядро имеет размер (50,1). Вы можете поиграть с размером в зависимости от размера вашего изображения. Вертикальное ядро имеет размер (1,50).
Морфологические операции осуществляют трансформации обнаруженных структур в зависимости от их геометрии (Soille, с.50, 1998). Дилатация - одна из самых распространенных и основных морфологических операций. Если хотя бы один пиксель под ядром белый, рассматриваемый пиксель исходного изображения будет считаться белым. Следовательно, белые области увеличиваются. Имейте в виду, что из-за инверсии фон черный, а передний - белый, что означает, что строки таблицы в настоящее время белые. Расширение можно рассматривать как наиболее важный шаг. Теперь дыры и ломаные линии исправлены, и для дальнейшего распознавания таблицы будут рассмотрены все ячейки.
# Obtain horizontal lines mask horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (50,1)) horizontal_mask = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=1) horizontal_mask = cv2.dilate(horizontal_mask, horizontal_kernel, iterations=9) # Obtain vertical lines mask vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,50)) vertical_mask = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=1) vertical_mask= cv2.dilate(vertical_mask, vertical_kernel, iterations=9)
Обе маски, горизонтальная и вертикальная, затем объединяются в одну таблицу с помощью операции OpenCV bitwise_or. Чтобы получить исходный задний и передний план, изображение инвертируется путем вычитания cv2.bitwise_or из 255.
# Bitwise-and masks together result = 255 — cv2.bitwise_or(vertical_mask, horizontal_mask)
Если таблица окружена текстом и не стоит отдельно (в моем примере она не окружена), мы вырезаем ее и устанавливаем на белом фоне. Теперь нам нужен размер ранее полученной таблицы. Мы обрезаем окончательное изображение до размера таблицы, используя минимальное значение y (край наверху), максимальное значение y + высота максимальных ячеек y (край внизу), минимальное значение x (которое равно левый край) и максимальный x + ширина максимальных x ячеек (который является правым краем). Затем изображение обрезается до размера таблицы. Будет создан новый фон исходного размера документа, полностью заполненный белыми пикселями. Извлекается центр изображения, а восстановленная таблица объединяется с белым фоном и помещается прямо в центр изображения.
#Cropping the image to the table size crop_img = result[(min_y+5):(max_y+max_y_height), (min_x):(max_x+max_x_width+5)] #Creating a new image and filling it with white background img_white = np.zeros((hei, wid), np.uint8) img_white[:, 0:wid] = (255) #Retrieve the coordinates of the center of the image x_offset = int((wid — crop_img.shape[1])/2) y_offset = int((hei — crop_img.shape[0])/2) #Placing the cropped and repaired table into the white background img_white[ y_offset:y_offset+crop_img.shape[0], x_offset:x_offset+crop_img.shape[1]] = crop_img #Viewing the result cv2.imshow(‘Result’, img_white) cv2.waitKey()
Вот результат. Метод может использоваться для множественного набора ломаных линий, пробелов и дыр в таблицах. Результат является основой для дальнейшего распознавания таблиц, как описано в моей другой статье. Описанный метод был применен к пустой таблице. Вы также можете применить его к таблице, содержащей текст или окруженной текстом. Для таблицы, содержащей текст, по-прежнему необходимо объединить исходное изображение, содержащее таблицу с данными, с окончательным изображением с исправленными отверстиями. Это можно сделать с помощью побитовой операции OpenCV и не должно быть слишком сложным.
Надеюсь, вам понравилось мое руководство, и вы можете использовать его в своих проектах.