Ecco un'idea. Suddividiamo questo problema in diversi passaggi:
Determina l'area del contorno rettangolare media. Sogliamo quindi trovare i contorni e filtriamo usando l' area del rettangolo di delimitazione del contorno. La ragione per cui lo facciamo è a causa dell'osservazione che qualsiasi personaggio tipico sarà solo così grande mentre un grande rumore coprirà un'area rettangolare più grande. Determiniamo quindi l'area media.
Rimuovere i contorni esterni più grandi. Esaminiamo nuovamente i contorni e rimuoviamo i contorni più grandi se sono 5x
più grandi dell'area media del contorno riempiendo il contorno. Invece di utilizzare un'area a soglia fissa, utilizziamo questa soglia dinamica per una maggiore robustezza.
Dilatalo con un kernel verticale per connettere i personaggi . L'idea è sfruttare l'osservazione che i personaggi sono allineati in colonne. Dilatandoci con un kernel verticale colleghiamo il testo insieme in modo che il rumore non sia incluso in questo contorno combinato.
Elimina il piccolo rumore . Ora che il testo da conservare è collegato, troviamo i contorni e rimuoviamo eventuali contorni più piccoli 4x
dell'area del contorno media.
Bit a bit e per ricostruire l'immagine . Dal momento che abbiamo solo desiderato che i contorni rimanessero sulla nostra maschera, abbiamo conservato bit per bit e preservare il testo e ottenere il nostro risultato.
Ecco una visualizzazione del processo:
Abbiamo la soglia di Otsu per ottenere un'immagine binaria, quindi troviamo i contorni per determinare l'area del contorno rettangolare media. Da qui rimuoviamo i grandi contorni anomali evidenziati in verde riempiendo i contorni
Quindi costruiamo un kernel verticale e dilatiamo per connettere i personaggi. Questo passaggio collega tutto il testo desiderato per mantenere e isola il rumore nei singoli BLOB.
Ora troviamo contorni e filtri utilizzando l' area del contorno per rimuovere il piccolo rumore
Ecco tutte le particelle di rumore rimosse evidenziate in verde
Risultato
Codice
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()
Nota: l' elaborazione delle immagini tradizionale è limitata a soglie, operazioni morfologiche e filtro dei contorni (approssimazione del contorno, area, proporzioni o rilevamento del blob). Poiché le immagini di input possono variare in base alla dimensione del testo del carattere, trovare una soluzione singolare è piuttosto difficile. Potresti voler allenare il tuo classificatore con l'apprendimento automatico / profondo per una soluzione dinamica.