Ваши изображения имеют довольно низкое разрешение, но вы можете попробовать метод под названием Gain Division. Идея состоит в том, что вы пытаетесь построить модель фона, а затем взвешиваете каждый входной пиксель по этой модели. Выходное усиление должно быть относительно постоянным в течение большей части изображения.
После выполнения разделения усиления вы можете попытаться улучшить изображение, применив фильтр области и морфологию. Я попробовал только ваше первое изображение, потому что оно наименее худшее.
Вот шаги, чтобы получить изображение с разделением по усилению:
- Примените мягкий фильтр среднее размытие, чтобы избавиться от высокочастотного шума.
- Получите модель фона через локальный максимум. Примените очень сильную операцию
close
с большим structuring element
(я использую прямоугольное ядро размером 15
).
- Выполните настройку усиления, разделив
255
между каждым локальным максимальным пикселем. Взвешивайте это значение с каждым пикселем входного изображения.
- Вы должны получить красивое изображение, где фоновое освещение в значительной степени нормализовано,
threshold
это изображение, чтобы получить бинарную маску символов.
Теперь вы можете улучшить качество изображения, выполнив следующие дополнительные действия:
Threshold
через Otsu, но добавьте небольшую предвзятость. (К сожалению, это ручной шаг в зависимости от ввода).
Примените фильтр области, чтобы отфильтровать мелкие пятна шума.
Давайте посмотрим код:
import numpy as np
import cv2
# image path
path = "C:/opencvImages/"
fileName = "iA904.png"
# Reading an image in default mode:
inputImage = cv2.imread(path+fileName)
# Remove small noise via median:
filterSize = 5
imageMedian = cv2.medianBlur(inputImage, filterSize)
# Get local maximum:
kernelSize = 15
maxKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernelSize, kernelSize))
localMax = cv2.morphologyEx(imageMedian, cv2.MORPH_CLOSE, maxKernel, None, None, 1, cv2.BORDER_REFLECT101)
# Perform gain division
gainDivision = np.where(localMax == 0, 0, (inputImage/localMax))
# Clip the values to [0,255]
gainDivision = np.clip((255 * gainDivision), 0, 255)
# Convert the mat type from float to uint8:
gainDivision = gainDivision.astype("uint8")
# Convert RGB to grayscale:
grayscaleImage = cv2.cvtColor(gainDivision, cv2.COLOR_BGR2GRAY)
Вот что дает вам деление усиления:
Обратите внимание, что освещение более сбалансировано. Теперь давайте немного улучшим контраст:
# Contrast Enhancement:
grayscaleImage = np.uint8(cv2.normalize(grayscaleImage, grayscaleImage, 0, 255, cv2.NORM_MINMAX))
Вы получаете это, что создает немного больше контраста между передним планом и фоном:
Теперь давайте попробуем установить порог для этого изображения, чтобы получить красивую бинарную маску. Как я и предложил, попробуйте пороговую обработку Оцу, но добавьте (или уберите) немного смещения к результату. Этот шаг, как уже упоминалось, зависит от качества вашего ввода:
# Threshold via Otsu + bias adjustment:
threshValue, binaryImage = cv2.threshold(grayscaleImage, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
threshValue = 0.9 * threshValue
_, binaryImage = cv2.threshold(grayscaleImage, threshValue, 255, cv2.THRESH_BINARY)
В итоге вы получите эту бинарную маску:
Инвертируйте это и отфильтруйте маленькие капли. Я установил пороговое значение area
в 10
пикселей:
# Invert image:
binaryImage = 255 - binaryImage
# Perform an area filter on the binary blobs:
componentsNumber, labeledImage, componentStats, componentCentroids = \
cv2.connectedComponentsWithStats(binaryImage, connectivity=4)
# Set the minimum pixels for the area filter:
minArea = 10
# Get the indices/labels of the remaining components based on the area stat
# (skip the background component at index 0)
remainingComponentLabels = [i for i in range(1, componentsNumber) if componentStats[i][4] >= minArea]
# Filter the labeled pixels based on the remaining labels,
# assign pixel intensity to 255 (uint8) for the remaining pixels
filteredImage = np.where(np.isin(labeledImage, remainingComponentLabels) == True, 255, 0).astype("uint8")
И это окончательная бинарная маска:
Если вы планируете отправить это изображение в OCR
, вы можете сначала применить некоторую морфологию. Может быть, closing
, чтобы попытаться соединить точки, из которых состоят персонажи. Также не забудьте обучить классификатор OCR
шрифту, который близок к тому, что вы на самом деле пытаетесь распознать. Это (перевернутая) маска после операции размером 3
rectangular
closing
с 3
итерациями:
Редактировать:
Чтобы получить последнее изображение, обработайте отфильтрованный вывод следующим образом:
# Set kernel (structuring element) size:
kernelSize = 3
# Set operation iterations:
opIterations = 3
# Get the structuring element:
maxKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernelSize, kernelSize))
# Perform closing:
closingImage = cv2.morphologyEx(filteredImage, cv2.MORPH_CLOSE, maxKernel, None, None, opIterations, cv2.BORDER_REFLECT101)
# Invert image to obtain black numbers on white background:
closingImage = 255 - closingImage
11.01.2021
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3,3)) closing = cv2.morphologyEx(filteredImage, cv2.MORPH_CLOSE, kernel)
Но это не имеет никакого эффекта. Я попытался применить отфильтрованное двоичное изображение и его инверсию. 12.01.2021