Каким образом программно можно удалить все линии и границы изображения (сохранить тексты)?

Я пытаюсь извлечь текст из изображения с помощью Tesseract OCR. В настоящее время с исходным входным изображением (как показано ниже) вывод очень низкого качества (около 50%). Но когда я пытаюсь удалить все линии и границы на входном изображении (используя Photoshop), выход значительно улучшится (~ 90%). Итак, есть ли способ удалить все линии и границы изображения (сохранить тексты) программно (используя OpenCV, Image magick,..)?

Исходное изображение: Original Image

Ожидайте изображения: Expect Image

Ответ 1

Не использовать OpenCV, а просто однострочный образ ImageMagick в терминале, но он может дать вам представление о том, как это сделать в OpenCV. ImageMagick установлен на большинстве дистрибутивов Linux и доступен для OSX и Windows.

Суть концепции состоит в том, чтобы создать новое изображение, в котором каждый пиксель установлен на медиану из 100 соседних пикселей слева и 100 соседних пикселей справа. Таким образом, пиксели с большим количеством горизонтальных соседей, которые являются черными (то есть горизонтальными черными линиями), будут белыми на выходном изображении. Затем одна и та же обработка применяется в вертикальном направлении для удаления вертикальных линий.

Команда, которую вы вводите в терминал, будет:

convert input.png                                                 \
   \( -clone 0 -threshold 50% -negate -statistic median 200x1 \)  \
   -compose lighten -composite                                    \
   \( -clone 0 -threshold 50% -negate -statistic median 1x200 \)  \
   -composite result.png

В первой строке указывается загрузка исходного изображения.

Во второй строке начинается некоторая "обработка в сторону", которая копирует исходное изображение, порождает его и инвертирует, затем вычисляется медиана всех соседних пикселей 100 с каждой стороны.

Третья строка затем берет результат второй линии и компонует ее над исходным изображением, выбирая более легкие пиксели в каждом месте - то есть те, которые моя маска горизонтальной линии отбелена.

Следующие две строки делают то же самое снова, но ориентированы вертикально для вертикальных линий.

Результат выглядит так:

enter image description here

Если я отличаюсь тем, что с вашим исходным изображением, вот так, я вижу, что он сделал:

convert input.png result.png -compose difference -composite diff.png

enter image description here

Я думаю, если бы вы хотели удалить немного больше строк, вы могли бы немного размыть изображение различий и применить это к оригиналу. Конечно, вы можете играть с длинами фильтров, порогами и т.д.

Ответ 2

Существует лучший способ сделать это с помощью ImageMagick.

Определение формы линии и ее удаление

ImageMagick имеет аккуратную функцию, называемую морфологией фигур. Вы можете использовать его для идентификации фигур, таких как строки таблицы, и их удаления.

Один лайнер

convert in.png                              \
-type Grayscale                             \
-negate                                     \
-define morphology:compose=darken           \
-morphology Thinning 'Rectangle:1x80+0+0<'  \
-negate                                     \
out.png

объяснение

  • convert in.png: загрузить изображение.
  • -type Оттенки серого: убедитесь, что ImageMagick знает изображение в оттенках серого.
  • -negate: инвертировать цветные слои изображения (уже правильно отрегулированы путем настройки оттенков серого). Линии и символы будут белыми и черными.
  • -define морфология: compose = darken: определить, что области, идентифицированные по морфологии, будут затемнены.
  • -morphology Размывание "Прямоугольник: 1x80 + 0 + 0 <" определяет ядро прямоугольника размером 1px на 80 пикселей, которое будет использоваться для идентификации фигур линий. Только если это ядро вписывается в белую форму (помните, что мы отрицаем цвета) это большое или большее, оно будет затемнено. Флаг < позволяет ему вращаться.
  • -negate: инвертировать цвета второй раз. Теперь символы снова будут черными, а фон будет белым.
  • out.png: выходной файл, который будет сгенерирован.

Результирующее изображение

После применения

convert in.png -type Grayscale -negate -define morphology:compose=darken -morphology Thinning 'Rectangle:1x80+0+0<' -negate out.png

это было выходное изображение:

Output image after one liner was applied

наблюдения

  • Вы должны выбрать размер ядра прямоугольника, который больше размера вашего большего символа, чтобы убедиться, что прямоугольник не помещается внутри символа.
  • Некоторые небольшие пунктирные линии и небольшие ячейки ячейки ячейки по-прежнему остаются, но это потому, что они меньше 80 пикселей.
  • Суть этой методики заключается в том, что она сохраняет символы лучше, чем медианный подход к разнице в цветах пикселей, предложенный здесь другим пользователем, и, несмотря на небольшой беспорядок, он по-прежнему имеет действительно лучший результат, удаляя строки таблицы.

Ответ 3

Столкнулась с той же проблемой. И я считаю, что более логичным решением может быть (Ссылка: Извлечь таблицы)

//assuming, b_w is the binary image
inv = 255 - b_w    
horizontal_img = new_img
vertical_img = new_img

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (100,1))
horizontal_img = cv2.erode(horizontal_img, kernel, iterations=1)
horizontal_img = cv2.dilate(horizontal_img, kernel, iterations=1)


kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,100))
vertical_img = cv2.erode(vertical_img, kernel, iterations=1)
vertical_img = cv2.dilate(vertical_img, kernel, iterations=1)

mask_img = horizontal_img + vertical_img
no_border = np.bitwise_or(b_w, mask_img)

Ответ 4

Вы можете использовать алгоритм обнаружения кромок от Sobel/Laplacian/Canny и использовать преобразование Hough для идентификации линий в OpenCV и покрасить их в белый цвет, чтобы удалить линии:

laplacian = cv2.Laplacian(img,cv2.CV_8UC1) # Laplacian OR
edges = cv2.Canny(img,80,10,apertureSize = 3) # canny Edge OR
# Output dtype = cv2.CV_8U # Sobel
sobelx8u = cv2.Sobel(img,cv2.CV_8U,1,0,ksize=5)
# Output dtype = cv2.CV_64F. Then take its absolute and convert to cv2.CV_8U
sobelx64f = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
abs_sobel64f = np.absolute(sobelx64f)
sobel_8u = np.uint8(abs_sobel64f)

# Hough Probabilistic Line Transform 
minLineLength = 900
maxLineGap = 100
lines = cv2.HoughLinesP(edges,1,np.pi/180,100,minLineLength,maxLineGap)
for line in lines:
    for x1,y1,x2,y2 in line:
        cv2.line(img,(x1,y1),(x2,y2),(255,255,255),2)

cv2.imwrite('houghlines.jpg',img)

Ответ 5

Поскольку никто не опубликовал полное решение OpenCV, здесь простой подход

  • Преобразовать изображение в оттенки серого
  • Адаптивный порог для получения двоичного изображения
  • Удалить горизонтальные линии с горизонтальным ядром
  • Удалить вертикальное удержание с вертикальным ядром

Адаптивный порог для получения двоичного изображения

image = cv2.imread('1.png')
result = image.copy()
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

enter image description here

Теперь мы создадим горизонтальное ядро для обнаружения горизонтальных линий с помощью cv2.getStructuringElement() и поиска контуров. Чтобы удалить горизонтальные линии, мы используем cv2.drawContours() и заливаем контур белым. Здесь обнаружены горизонтальные линии

# Remove horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (40,1))
remove_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(remove_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(result, [c], -1, (255,255,255), 5)

enter image description here

Точно так же мы создаем вертикальное ядро, чтобы удалить вертикальные линии, найти контуры и залить белым. Здесь обнаружены вертикальные линии

# Remove vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,40))
remove_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(remove_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(result, [c], -1, (255,255,255), 5)

enter image description here

Результат

enter image description here

Полный код

import cv2

image = cv2.imread('1.png')
result = image.copy()
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Remove horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (40,1))
remove_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(remove_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(result, [c], -1, (255,255,255), 5)

# Remove vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,40))
remove_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(remove_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(result, [c], -1, (255,255,255), 5)

cv2.imshow('thresh', thresh)
cv2.imshow('result', result)
cv2.imwrite('result.png', result)
cv2.waitKey()

Ответ 6

У меня есть идея. Но он будет работать, только если у вас есть абсолютно горизонтальные и вертикальные линии. Вы можете сначала выполнить бинаризацию на этом изображении (если это еще не так). Затем напишите некоторый код, который повторяется через каждую строку изображения, в то же время проверяя, есть ли последовательность черных пикселей, содержащих более определенного порога. Например, если в некоторой строке есть непрерывная последовательность черных точек, начиная от 100-го пикселя до 150-го пикселя, сделайте эти пиксели белыми. После нахождения всех горизонтальных линий вы можете сделать то же самое, чтобы избавиться от вертикальных линий.

Здесь, в моем примере, я считаю, что черная последовательность пикселей начинается точно с 100-го пикселя и заканчивается на 150-й, потому что, если в 151-м пикселе есть еще один черный пиксель, тогда я должен добавить этот пиксель тоже. Другими словами, попробуйте полностью найти линии.

Если вы решите этот вопрос, пожалуйста, дайте мне знать)

Ответ 8

###   tested "Abhishek Chatterjee" code it works fine i just changed
###   horizontal_img = new_img  to horizontal_img = b_w.copy()
####  and  vertical_img = new_img  to vertical_img = b_w.cpy()

import cv2
import numpy as np

b_w = cv2.imread("input.png", 1)

inv = 255 - b_w
horizontal_img =  b_w.copy()
vertical_img = b_w.copy()

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (100,1))
horizontal_img = cv2.erode(horizontal_img, kernel, iterations=1)
horizontal_img = cv2.dilate(horizontal_img, kernel, iterations=1)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,100))
vertical_img = cv2.erode(vertical_img, kernel, iterations=1)
vertical_img = cv2.dilate(vertical_img, kernel, iterations=1)
mask_img = horizontal_img + vertical_img

no_border = np.bitwise_or(b_w, mask_img)
cv2.imshow("no_border", no_border)
cv2.waitKey(0)