Immagine di pulizia per OCR


9

Ho cercato di cancellare le immagini per l'OCR: (le linee)

inserisci qui la descrizione dell'immagine

Ho bisogno di rimuovere queste righe per a volte elaborare ulteriormente l'immagine e mi sto avvicinando abbastanza ma molto spesso la soglia toglie troppo al testo:

    copy = img.copy()
    blur = cv2.GaussianBlur(copy, (9,9), 0)
    thresh = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,11,30)

    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))
    dilate = cv2.dilate(thresh, kernel, iterations=2)

    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 > 300:
            x,y,w,h = cv2.boundingRect(c)
            cv2.rectangle(copy, (x, y), (x + w, y + h), (36,255,12), 3)

Modifica: Inoltre, l'uso di numeri costanti non funzionerà nel caso in cui il carattere cambi. C'è un modo generico per farlo?


2
Alcune di queste righe, o frammenti di esse, hanno le stesse caratteristiche del testo legale e sarà difficile liberarsene senza rovinare il testo valido. In questo caso, potresti concentrarti sul fatto che sono più lunghi dei personaggi e in qualche modo isolati. Quindi un primo passo potrebbe essere quello di stimare la dimensione e la vicinanza dei personaggi.
Yves Daoust,

@YvesDaoust Come si potrebbe trovare la vicinanza dei personaggi? (poiché il filtraggio puramente sulla dimensione viene confuso con i personaggi per la maggior parte del tempo)
K41F4r

1
È possibile trovare, per ogni blob, la distanza dal vicino più vicino. Quindi, mediante l'analisi dell'istogramma delle distanze, si trova una soglia tra "vicino" e "a parte" (qualcosa come la modalità di distribuzione), o tra "circondato" e "isolato".
Yves Daoust,

Nel caso di più piccole linee vicine l'una all'altra, il loro vicino più vicino non sarebbe l'altra piccola linea? Il calcolo della distanza media da tutti gli altri BLOB sarebbe troppo costoso?
K41F4r

"il loro vicino più prossimo non sarebbe l'altra linea piccola?": buona obiezione, Vostro Onore. In effetti un gruppo di brevi segmenti vicini non differisce dal testo legittimo, sebbene in una disposizione completamente improbabile. Potrebbe essere necessario raggruppare i frammenti di linee spezzate. Non sono sicuro che la distanza media da tutti ti salverebbe.
Yves Daoust,

Risposte:


14

Ecco un'idea. Suddividiamo questo problema in diversi passaggi:

  1. 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.

  2. Rimuovere i contorni esterni più grandi. Esaminiamo nuovamente i contorni e rimuoviamo i contorni più grandi se sono 5xpiù 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.

  3. 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.

  4. Elimina il piccolo rumore . Ora che il testo da conservare è collegato, troviamo i contorni e rimuoviamo eventuali contorni più piccoli 4xdell'area del contorno media.

  5. 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

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

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.

inserisci qui la descrizione dell'immagine

Ora troviamo contorni e filtri utilizzando l' area del contorno per rimuovere il piccolo rumore

inserisci qui la descrizione dell'immagine

Ecco tutte le particelle di rumore rimosse evidenziate in verde

inserisci qui la descrizione dell'immagine

Risultato

inserisci qui la descrizione dell'immagine

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.


1
Nel caso di un carattere più grande, anche questo non eliminerebbe il testo?
K41F4r,

Sì, potrebbe essere necessario regolare il valore dell'area della soglia. Per un approccio più dinamico, l'idea è quella di determinare l'area media del personaggio, quindi usarla come soglia
nathancy

Sembra essere troppo specifico per l'esempio, l'utilizzo dell'area media eliminerà comunque il testo molto del tempo che peggiora il risultato per OCR
K41F4r

Hai un'altra immagine di input di esempio che potresti aggiungere al post?
nathancy,

1
Trovare una soluzione che funzioni in tutte le situazioni usando le tradizionali tecniche di elaborazione delle immagini è abbastanza difficile. Potresti voler allenare il tuo classificatore usando l'apprendimento profondo. In bocca al lupo!
nathancy,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.