Подход № 1: Пиксельный анализ
Получение двоичного изображения. Загрузка изображения, преобразование в оттенки серого и пороговое значение Оцу
Сумма пикселей строки. Идея состоит в том, что сумма пикселей строки может использоваться для определения, соответствует ли она тексту или пробелу
Создание нового изображения и добавление дополнительных пробелов. Перебираем массив пикселей и добавляем дополнительные пробелы
Двоичное изображение
![image](https://i.stack.imgur.com/qm9AN.png)
# Load image, grayscale, Otsu's threshold
image = cv2.imread('1.png')
h, w = image.shape[:2]
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
Теперь мы перебираем каждую строку и суммируем белые пиксели, чтобы сгенерировать массив пикселей. Мы можем профилировать столбец данных, сгенерированный из суммы всех пикселей в каждой строке, чтобы определить, какие строки соответствуют тексту. Разделы данных, равные 0
, представляют строки изображения, состоящие из пробелов. Вот визуализация массива данных:
![image](https://i.stack.imgur.com/PxUBc.png)
# Sum white pixels in each row
# Create blank space array and and final image
pixels = np.sum(thresh, axis=1).tolist()
space = np.ones((2, w), dtype=np.uint8) * 255
result = np.zeros((1, w), dtype=np.uint8)
Мы преобразуем данные в список и перебираем данные для построения окончательного изображения. Если строка определена как пробел, мы объединяем массив пустых пространств с конечным изображением. Регулируя размер пустого массива, мы можем изменить объем пространства, добавляемого к изображению.
# Iterate through each row and add space if entire row is empty
# otherwise add original section of image to final image
for index, value in enumerate(pixels):
if value == 0:
result = np.concatenate((result, space), axis=0)
row = gray[index:index+1, 0:w]
result = np.concatenate((result, row), axis=0)
Вот результат
![image](https://i.stack.imgur.com/3ztuN.png)
Код
import cv2
import numpy as np
import matplotlib.pyplot as plt
# import pandas as pd
# Load image, grayscale, Otsu's threshold
image = cv2.imread('1.png')
h, w = image.shape[:2]
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Sum white pixels in each row
# Create blank space array and and final image
pixels = np.sum(thresh, axis=1).tolist()
space = np.ones((1, w), dtype=np.uint8) * 255
result = np.zeros((0, w), dtype=np.uint8)
# Iterate through each row and add space if entire row is empty
# otherwise add original section of image to final image
for index, value in enumerate(pixels):
if value == 0:
result = np.concatenate((result, space), axis=0)
row = gray[index:index+1, 0:w]
result = np.concatenate((result, row), axis=0)
# Uncomment for plot visualization
'''
x = range(len(pixels))[::-1]
df = pd.DataFrame({'y': x, 'x': pixels})
df.plot(x='x', y='y', xlim=(-2000,max(pixels) + 2000), legend=None, color='teal')
'''
cv2.imshow('result', result)
cv2.imshow('thresh', thresh)
plt.show()
cv2.waitKey()
Подход № 2: Извлечение отдельной строки
Для более динамичного подхода c мы можем найти контуры каждой линии, а затем добавить пространство между каждым контуром. Мы используем тот же метод добавления дополнительного пробела, что и при первом подходе.
Получение двоичного изображения. Загрузка изображения, оттенков серого, размытия по Гауссу и порога Оцу
Подключить текст контуры. Мы создаем горизонтальное ядро в форме и расширяем, чтобы соединить слова каждой строки в один контур
Извлечение каждого контура линии. Мы находим контуры Сортируйте сверху вниз, используя imtuils.contours.sort_contours()
и извлекая каждую строку. ROI
Добавьте пробел между каждой строкой. Мы создаем пустой массив и строим новое изображение путем добавления пробела между контурами каждой линии
Двоичное изображение
![enter image description here](https://i.stack.imgur.com/e8b0V.png)
# Load image, grayscale, blur, Otsu's threshold
image = cv2.imread('1.png')
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
invert = 255 - thresh
height, width = image.shape[:2]
Создание горизонтального ядра и расширение
![enter image description here](https://i.stack.imgur.com/D28ss.png)
# Dilate with a horizontal kernel to connect text contours
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10,2))
dilate = cv2.dilate(thresh, kernel, iterations=2)
Извлеченный контур отдельной линии выделен зеленым
# Extract each line contour
lines = []
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
(cnts, _) = contours.sort_contours(cnts, method="top-to-bottom")
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(image, (0, y), (width, y+h), (36,255,12), 2)
line = original[y:y+h, 0:width]
line = cv2.cvtColor(line, cv2.COLOR_BGR2GRAY)
lines.append(line)
Добавить пробел между каждой строкой. Вот результат с массивом шириной 1
пикселей
![enter image description here](https://i.stack.imgur.com/0FJwP.png)
Результат с массивом шириной 5
пикселей
![enter image description here](https://i.stack.imgur.com/Dq0e5.png)
# Append white space in between each line
space = np.ones((1, width), dtype=np.uint8) * 255
result = np.zeros((0, width), dtype=np.uint8)
result = np.concatenate((result, space), axis=0)
for line in lines:
result = np.concatenate((result, line), axis=0)
result = np.concatenate((result, space), axis=0)
Полный код
import cv2
import numpy as np
from imutils import contours
# Load image, grayscale, blur, Otsu's threshold
image = cv2.imread('1.png')
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
invert = 255 - thresh
height, width = image.shape[:2]
# Dilate with a horizontal kernel to connect text contours
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10,2))
dilate = cv2.dilate(thresh, kernel, iterations=2)
# Extract each line contour
lines = []
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
(cnts, _) = contours.sort_contours(cnts, method="top-to-bottom")
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(image, (0, y), (width, y+h), (36,255,12), 2)
line = original[y:y+h, 0:width]
line = cv2.cvtColor(line, cv2.COLOR_BGR2GRAY)
lines.append(line)
# Append white space in between each line
space = np.ones((1, width), dtype=np.uint8) * 255
result = np.zeros((0, width), dtype=np.uint8)
result = np.concatenate((result, space), axis=0)
for line in lines:
result = np.concatenate((result, line), axis=0)
result = np.concatenate((result, space), axis=0)
cv2.imshow('result', result)
cv2.imshow('image', image)
cv2.imshow('dilate', dilate)
cv2.waitKey()