python — удаление горизонтальных подчеркиваний

Я пытаюсь получить текст из нескольких сотен JPG, которые содержат информацию о записях смертной казни; JPG находятся в ведении Департамента уголовного правосудия штата Техас (TDCJ). Ниже приведен пример фрагмента с удаленной личной информацией.

введите описание изображения здесь

Я определил подчеркивание как препятствие для правильного распознавания текста—если я пойду, сделаю снимок экрана с фрагментом кода и вручную вычеркнем линии, получая OCR через pytesseract очень хорошо. Но с присутствующими подчеркиваниями это очень плохо.

Как я могу лучше всего удалить эти горизонтальные линии? Что я пробовал:

Отметить этот вопрос в надежде, что кто-то может помочь перевести Шаг 5 документы прохождение в Python. Я пробовал серию преобразований, таких как преобразование Хью Лайна, но я чувствую себя в темноте внутри библиотеки и области, с которой у меня до этого не было опыта.

import cv2

# Inverted grayscale
img = cv2.imread('rsnippet.jpg', cv2.IMREAD_GRAYSCALE)
img = cv2.bitwise_not(img)

# Transform inverted grayscale to binary
th = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY, 15, -2)

# An alternative; Not sure if `th` or `th2` is optimal here
th2 = cv2.threshold(img, 170, 255, cv2.THRESH_BINARY)[1]

# Create corresponding structure element for horizontal lines.
# Start by cloning th/th2.
horiz = th.copy()
r, c = horiz.shape

# Lost after here - not understanding intuition behind sizing/partitioning

20

Решение

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

Для этого я использую часть вашего образца изображения, показанного ниже.

образец

Загрузите изображение, преобразуйте его в оттенки серого и инвертируйте.

import cv2
import numpy as np
import matplotlib.pyplot as plt

im = cv2.imread('sample.jpg')
gray = 255 - cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)

Перевернутое серое изображение:

перевернутой серый

Если вы отсканируете строку в этом перевернутом изображении, вы увидите, что его профиль выглядит по-разному в зависимости от наличия или отсутствия линии.

plt.figure(1)
plt.plot(gray[18, :] > 16, 'g-')
plt.axis([0, gray.shape[1], 0, 1.1])
plt.figure(2)
plt.plot(gray[36, :] > 16, 'r-')
plt.axis([0, gray.shape[1], 0, 1.1])

Зеленый профиль — это строка, в которой нет подчеркивания, а красный — для строки с подчеркиванием. Если вы возьмете среднее для каждого профиля, вы увидите, что у красного выше среднее.

нет строкилиния

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

for row in range(gray.shape[0]):
avg = np.average(gray[row, :] > 16)
if avg > 0.9:
cv2.line(im, (0, row), (gray.shape[1]-1, row), (0, 0, 255))
cv2.line(gray, (0, row), (gray.shape[1]-1, row), (0, 0, 0), 1)

cv2.imshow("gray", 255 - gray)
cv2.imshow("im", im)

Вот обнаруженные подчеркивания в красном и очищенное изображение.

обнаруженныйочищенный

Тессеракт вывод очищенного изображения:

Convthed as th(
shot once in the
she stepped fr<
brother-in-lawii
collect on life in
applied for man
to the scheme i|

Причина использования части изображения должна быть уже ясна. Поскольку личная информация была удалена в исходном изображении, порог не сработал бы. Но это не должно быть проблемой, когда вы применяете его для обработки. Иногда вам может потребоваться отрегулировать пороговые значения (16, 0,9).

Результат выглядит не очень хорошо с удаленными частями букв и некоторыми слабыми строками. Буду обновлять, если я смогу улучшить его немного больше.

ОБНОВИТЬ:

Dis некоторые улучшения; очистить и связать недостающие части букв. Я прокомментировал код, поэтому я считаю, что процесс понятен. Вы также можете проверить полученные промежуточные изображения, чтобы увидеть, как это работает. Результаты немного лучше.

11-чистый

Тессеракт вывод очищенного изображения:

Convicted as th(
shot once in the
she stepped fr<
brother-in-law. ‘
collect on life ix
applied for man
to the scheme i|

22-чистый

Тессеракт вывод очищенного изображения:

)r-hire of 29-year-old .
revolver in the garage ‘
red that the victim‘s h
{2000 to kill her. mum
250.000. Before the kil
If$| 50.000 each on bin
to police.

код Python:

import cv2
import numpy as np
import matplotlib.pyplot as plt

im = cv2.imread('sample2.jpg')
gray = 255 - cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
# prepare a mask using Otsu threshold, then copy from original. this removes some noise
__, bw = cv2.threshold(cv2.dilate(gray, None), 128, 255, cv2.THRESH_BINARY or cv2.THRESH_OTSU)
gray = cv2.bitwise_and(gray, bw)
# make copy of the low-noise underlined image
grayu = gray.copy()
imcpy = im.copy()
# scan each row and remove lines
for row in range(gray.shape[0]):
avg = np.average(gray[row, :] > 16)
if avg > 0.9:
cv2.line(im, (0, row), (gray.shape[1]-1, row), (0, 0, 255))
cv2.line(gray, (0, row), (gray.shape[1]-1, row), (0, 0, 0), 1)

cont = gray.copy()
graycpy = gray.copy()
# after contour processing, the residual will contain small contours
residual = gray.copy()
# find contours
contours, hierarchy = cv2.findContours(cont, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
for i in range(len(contours)):
# find the boundingbox of the contour
x, y, w, h = cv2.boundingRect(contours[i])
if 10 < h:
cv2.drawContours(im, contours, i, (0, 255, 0), -1)
# if boundingbox height is higher than threshold, remove the contour from residual image
cv2.drawContours(residual, contours, i, (0, 0, 0), -1)
else:
cv2.drawContours(im, contours, i, (255, 0, 0), -1)
# if boundingbox height is less than or equal to threshold, remove the contour gray image
cv2.drawContours(gray, contours, i, (0, 0, 0), -1)

# now the residual only contains small contours. open it to remove thin lines
st = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
residual = cv2.morphologyEx(residual, cv2.MORPH_OPEN, st, iterations=1)
# prepare a mask for residual components
__, residual = cv2.threshold(residual, 0, 255, cv2.THRESH_BINARY)

cv2.imshow("gray", gray)
cv2.imshow("residual", residual)

# combine the residuals. we still need to link the residuals
combined = cv2.bitwise_or(cv2.bitwise_and(graycpy, residual), gray)
# link the residuals
st = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (1, 7))
linked = cv2.morphologyEx(combined, cv2.MORPH_CLOSE, st, iterations=1)
cv2.imshow("linked", linked)
# prepare a msak from linked image
__, mask = cv2.threshold(linked, 0, 255, cv2.THRESH_BINARY)
# copy region from low-noise underlined image
clean = 255 - cv2.bitwise_and(grayu, mask)
cv2.imshow("clean", clean)
cv2.imshow("im", im)
7

Другие решения

Можно попробовать это.

img = cv2.imread('img_provided_by_op.jpg', 0)
img = cv2.bitwise_not(img)

# (1) clean up noises
kernel_clean = np.ones((2,2),np.uint8)
cleaned = cv2.erode(img, kernel_clean, iterations=1)

# (2) Extract lines
kernel_line = np.ones((1, 5), np.uint8)
clean_lines = cv2.erode(cleaned, kernel_line, iterations=6)
clean_lines = cv2.dilate(clean_lines, kernel_line, iterations=6)

# (3) Subtract lines
cleaned_img_without_lines = cleaned - clean_lines
cleaned_img_without_lines = cv2.bitwise_not(cleaned_img_without_lines)

plt.imshow(cleaned_img_without_lines)
plt.show()
cv2.imwrite('img_wanted.jpg', cleaned_img_without_lines)

демонстрация

введите описание изображения здесь

Метод основан на ответ Зоу Лин. Он / она идентифицировал линии на изображении и просто вычел, чтобы избавиться от них. тем не мение, мы не можем просто вычесть строки здесь, потому что у нас есть буквы е, T, Е, T, содержащие строки также! Если мы просто вычтем горизонтальные линии из изображения, е будет почти идентичным с. уйдет …

Q: Как мы находим строки?

Чтобы найти строки, мы можем использовать erode функция. Чтобы использовать erode, нам нужно определить ядро. (Вы можете думать о ядре как о окне / форме, над которыми работают функции.)

ядро скользит через
изображение (как в 2D свертке). Пиксель в исходном изображении
(1 или 0) будет считаться 1, только если все пиксели под
Ядро равно 1, в противном случае оно размыто (обнуляется).
(Источник).

Чтобы извлечь строки, мы определяем ядро, kernel_line как np.ones((1, 5)), [1, 1, 1, 1, 1], Это ядро ​​будет скользить по изображению и разрушать пиксели, которые имеют 0 под ядром.

Более конкретно, в то время как ядро ​​применяется к одному пикселю, оно будет захватывать два пикселя слева и два справа.

 [X X Y X X]
^
|
Applied to Y, `kernel_line` captures Y's neighbors. If any of them is not
0, Y will be set to 0.

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

clean_lines = cv2.erode(cleaned, kernel_line, iterations=6)

В: Как нам избежать выделения строк в пределах e, E, t, T и -?

Мы совместим erosion а также dilation с итерация параметр.

clean_lines = cv2.erode(cleaned, kernel_line, iterations=6)

Вы могли заметить iterations=6 часть. Эффект этого параметра сделает плоскую часть в е, е, т, т, — исчезают. Это потому, что, хотя мы применяем одну и ту же операцию несколько раз, граничная часть этих линий будет уменьшаться. (При применении того же ядра только граничная часть встретится с 0 и в результате станет 0.) Мы используем этот трюк, чтобы линии в этих символах исчезли.

Это, однако, сопровождается побочным эффектом, заключающимся в уменьшении длины подчеркивания, от которого мы хотим избавиться. Мы можем вырастить это с dilate!

clean_lines = cv2.dilate(clean_lines, kernel_line, iterations=6)

В отличие от эрозии, которая сжимает изображение, расширение увеличивает изображение. Хотя у нас все еще есть то же ядро, kernel_line, если какая-либо часть под ядром равна 1, целевой пиксель будет 1. Применяя это, граница будет расти снова. (Часть в е, е, т, т, — не будет расти, если мы тщательно выберем параметр, чтобы он исчез в части эрозии.)

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


7

Несколько предложений:

  • Учитывая, что вы начинаете с JPEG, не усугубляйте потери. Сохраните ваши промежуточные файлы в формате PNG. Тессеракт отлично с этим справляется.
  • Масштабировать изображение в 2 раза (используя cv2.resizeВручая Тессеракт.
  • Попробуйте обнаружить и удалить черное подчеркивание. (Этот вопрос может помочь). Делать это при сохранении потомков может быть сложно.
  • Изучите параметры командной строки Tesseract, которых много (и они ужасно задокументированы, некоторые требуют погружений в исходный код C ++, чтобы попытаться их понять). Похоже, лигатуры вызывают некоторое горе. IIRC (это было давно), есть настройка или два, которые могут помочь.
2

Поскольку большинство строк, которые будут обнаружены в вашем источнике, являются горизонтальными-длинными линиями, аналогично моему другому ответу, то есть Найти одноцветные горизонтальные пробелы в изображении

Это исходное изображение:

Вот два моих основных шага для удаления длинной горизонтальной линии:

  1. Делаем морф-закрытие с длинным ядром на сером изображении
kernel = np.ones((1,40), np.uint8)
morphed = cv2.morphologyEx(gray, cv2.MORPH_CLOSE, kernel)

затем получить морфированное изображение, содержащее длинные строки:

введите описание изображения здесь

  1. Инвертируйте преобразованное изображение и добавьте к исходному изображению:
dst = cv2.add(gray, (255-morphed))

затем получите изображение с удаленными длинными строками:

введите описание изображения здесь


Достаточно просто, верно? А также существуют small line segmentsЯ думаю, что это мало влияет на распознавание текста. Обратите внимание, что почти все символы остаются оригинальными, кроме g,j,p,q,y,Q, может быть, немного отличается. Но современные инструменты OCR, такие как Tesseract( с LSTM технология) имеет способность справляться с такой простой путаницей.

0123456789abcdefгПриветJklmnoрдrstuvwxYzABCDEFGHIJKLMNOPQRSTUVWXYZ


Общий код для сохранения удаленного изображения как line_removed.png:

#!/usr/bin/python3
# 2018.01.21 16:33:42 CST

import cv2
import numpy as np

## Read
img = cv2.imread("img04.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

## (1) Create long line kernel, and do morph-close-op
kernel = np.ones((1,40), np.uint8)
morphed = cv2.morphologyEx(gray, cv2.MORPH_CLOSE, kernel)
cv2.imwrite("line_detected.png", morphed)## (2) Invert the morphed image, and add to the source image:
dst = cv2.add(gray, (255-morphed))
cv2.imwrite("line_removed.png", dst)

Обновление @ 2018.01.23 13:15:15 CST:

Тессеракт — это мощный инструмент для распознавания текста. Сегодня я устанавливаю tesseract-4.0 и pytesseract. Тогда я делаю ocr используя pytesseract на мой результат line_removed.png,

line_removed.png

import cv2
import pytesseract
img = cv2.imread("line_removed.png")
print(pytesseract.image_to_string(img, lang="eng"))

Это правда, хорошо для меня.

Convicted as the triggerman in the murder—for—hire of 29—year—old .

shot once in the head with a 357 Magnum revolver in the garage of her home at ..
she stepped from her car. Police discovered that the victim‘s husband,
brother—in—law, _ ______ paid _ $2,000 to kill her, apparently so .. _
collect on life insurance policies totaling $250,000. Before the killing, .

applied for additional life insurance policies of $150,000 each on himself and his wife
to the scheme in three different statements to police.

was

and
could
had also

. confessed
2
По вопросам рекламы [email protected]