Ось ідея. Ми розбиваємо цю проблему на кілька кроків:
Визначте середню прямокутну площу контуру. Потім порог знаходимо контури і фільтруємо за допомогою обмежуючої прямокутника області контуру. Причиною цього ми є те, що спостерігаємо, що будь-який типовий персонаж буде настільки великим, тоді як великий шум буде охоплювати більшу прямокутну площу. Потім визначаємо середню площу.
Видаліть великі сторонні контури. Знову повторюємо контури і видаляємо великі контури, якщо вони 5x
перевищують середню площу контуру, заповнюючи контур. Замість використання фіксованої порогової області ми використовуємо цей динамічний поріг для більшої надійності.
Діляйте вертикальне ядро для з'єднання символів . Ідея - скористатися спостереженням, що символи вирівнюються у стовпцях. Розширюючи вертикальне ядро, ми з'єднуємо текст разом, щоб шум не включався до цього комбінованого контуру.
Видаліть невеликий шум . Тепер, коли текст, який потрібно зберегти, пов'язаний, ми знаходимо контури та видаляємо контури, менші за 4x
середню площу контуру.
Побітові - і для реконструкції зображення . Оскільки у нас є лише бажані контури, щоб зберегти нашу маску, ми побіжно - і зберегли текст і отримаємо результат.
Ось візуалізація процесу:
Ми встановлюємо поріг Оцу для отримання двійкового зображення, а потім знаходимо контури для визначення середньої прямокутної площі контуру. Звідси видаляємо великі зовнішні контури, виділені зеленим кольором, заповнюючи контури
Далі ми будуємо вертикальне ядро і розширюємо для з'єднання символів. Цей крок з'єднує весь потрібний текст, щоб утримати та ізолювати шум в окремих крапках.
Тепер ми знаходимо контури і фільтруємо за допомогою контурної області для видалення невеликого шуму
Тут усі вилучені шумові частинки, виділені зеленим кольором
Результат
Код
import cv2
# Load image, grayscale, and Otsu's threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Determine average contour area
average_area = []
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
area = w * h
average_area.append(area)
average = sum(average_area) / len(average_area)
# Remove large lines if contour area is 5x bigger then average contour area
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
area = w * h
if area > average * 5:
cv2.drawContours(thresh, [c], -1, (0,0,0), -1)
# Dilate with vertical kernel to connect characters
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,5))
dilate = cv2.dilate(thresh, kernel, iterations=3)
# Remove small noise if contour area is smaller than 4x average
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
area = cv2.contourArea(c)
if area < average * 4:
cv2.drawContours(dilate, [c], -1, (0,0,0), -1)
# Bitwise mask with input image
result = cv2.bitwise_and(image, image, mask=dilate)
result[dilate==0] = (255,255,255)
cv2.imshow('result', result)
cv2.imshow('dilate', dilate)
cv2.imshow('thresh', thresh)
cv2.waitKey()
Примітка: Традиційна обробка зображень обмежена пороговим рівнем, морфологічними операціями та контурною фільтрацією (наближення контуру, площа, співвідношення сторін чи виявлення крапки). Оскільки вхідні зображення можуть змінюватись залежно від розміру тексту символів, знайти єдине рішення досить складно. Можливо, ви захочете вивчити власний класифікатор за допомогою машинного / глибокого навчання для динамічного рішення.