Come migliorare il riconoscimento delle cifre di un modello addestrato su MNIST?


12

Sto lavorando al riconoscimento a più cifre stampato a mano con Java, usando la OpenCVlibreria per la preelaborazione e la segmentazione, e un Kerasmodello addestrato su MNIST (con una precisione di 0,98) per il riconoscimento.

Il riconoscimento sembra funzionare abbastanza bene, a parte una cosa. La rete abbastanza spesso non riesce a riconoscere quelli (numero "uno"). Non riesco a capire se ciò accade a causa della preelaborazione / implementazione errata della segmentazione o se una rete addestrata su MNIST standard non ha visto il numero uno che assomiglia ai miei casi di test.

Ecco come appaiono le cifre problematiche dopo la preelaborazione e la segmentazione:

inserisci qui la descrizione dell'immaginediventa inserisci qui la descrizione dell'immagineed è classificato come 4.

inserisci qui la descrizione dell'immaginediventa inserisci qui la descrizione dell'immagineed è classificato come 7.

inserisci qui la descrizione dell'immaginediventa inserisci qui la descrizione dell'immagineed è classificato come 4. E così via...

È qualcosa che potrebbe essere risolto migliorando il processo di segmentazione? O meglio migliorando il set di allenamento?

Modifica: migliorare il set di addestramento (aumento dei dati) sarebbe sicuramente di aiuto, cosa che sto già testando, la questione della corretta preelaborazione rimane ancora.

La mia preelaborazione consiste nel ridimensionamento, conversione in scala di grigi, binarizzazione, inversione e dilatazione. Ecco il codice:

Mat resized = new Mat();
Imgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC);

Mat grayscale = new Mat();
Imgproc.cvtColor(resized, grayscale, Imgproc.COLOR_BGR2GRAY);

Mat binImg = new Mat(grayscale.size(), CvType.CV_8U);
Imgproc.threshold(grayscale, binImg, 0, 255, Imgproc.THRESH_OTSU);

Mat inverted = new Mat();
Core.bitwise_not(binImg, inverted);

Mat dilated = new Mat(inverted.size(), CvType.CV_8U);
int dilation_size = 5;
Mat kernel = Imgproc.getStructuringElement(Imgproc.CV_SHAPE_CROSS, new Size(dilation_size, dilation_size));
Imgproc.dilate(inverted, dilated, kernel, new Point(-1,-1), 1);

L'immagine preelaborata viene quindi segmentata in singole cifre come segue:

List<Mat> digits = new ArrayList<>();
List<MatOfPoint> contours = new ArrayList<>();
Imgproc.findContours(preprocessed.clone(), contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

// code to sort contours
// code to check that contour is a valid char

List rects = new ArrayList<>();

for (MatOfPoint contour : contours) {
     Rect boundingBox = Imgproc.boundingRect(contour);
     Rect rectCrop = new Rect(boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height);

     rects.add(rectCrop);
}

for (int i = 0; i < rects.size(); i++) {
    Rect x = (Rect) rects.get(i);
    Mat digit = new Mat(preprocessed, x);

    int border = 50;
    Mat result = digit.clone();
    Core.copyMakeBorder(result, result, border, border, border, border, Core.BORDER_CONSTANT, new Scalar(0, 0, 0));

    Imgproc.resize(result, result, new Size(28, 28));
    digits.add(result);
}

1
stai usando la maschera o i pixel in scala di grigi originali (mascherati?) come input per la tua classificazione?
Micka,

@Micka Sto usando la versione preelaborata (binarizzata, invertita, dilatata). Quelli che corrispondono al set di allenamento MNIST. Ci sono esempi del numero "1" dopo la preelaborazione nel mio post.
Youngpanda,

Risposte:


5

Credo che il tuo problema sia il processo di dilatazione. Capisco che desideri normalizzare le dimensioni dell'immagine, ma non dovresti interrompere le proporzioni, dovresti ridimensionare al massimo desiderato di un asse (quello che consente la ridimensionamento più grande senza lasciare che un'altra dimensione dell'asse superi la dimensione massima) e riempire con il colore di sfondo il resto dell'immagine. Non è che "MNIST standard non ha visto il numero uno che assomiglia ai tuoi casi di test", fai apparire le tue immagini come numeri addestrati diversi (quelli che sono riconosciuti)

Sovrapposizione della fonte e delle immagini elaborate

Se hai mantenuto l'aspetto corretto delle tue immagini (sorgente e post-elaborazione), puoi vedere che non hai solo ridimensionato l'immagine ma "distorta". Può essere il risultato di una dilatazione non omogenea o di un ridimensionamento errato


Credo che @SiR abbia un certo peso, prova a non modificare le proporzioni dei letterali numerici.
ZdaR,

Mi dispiace, non lo seguo del tutto. Pensi che il mio processo di dilatazione o ridimensionamento sia il problema? Ridimensiono l'immagine solo all'inizio con questa riga Imgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC);. Qui la proporzione rimane invariata, dove rompo le proporzioni?
Youngpanda,

@SiR in risposta alle tue modifiche sopra: sì, non ridimensiono solo l'immagine, applico diverse operazioni, una delle quali è la dilatazione, che è morfologica, che provoca una leggera "distorsione" poiché provoca regioni luminose all'interno di un immagine per "crescere". O vuoi dire ridimensionamento alla fine, dove
realizzo

@youngpanda, potresti trovare interessante la discussione qui stackoverflow.com/questions/28525436/… . Potrebbe darti un'idea del perché il tuo approccio non porta buoni risultati
SiR

@SiR grazie per il link, ho familiarità con LeNet, ma è bello rileggerlo
youngpanda

5

Sono già state pubblicate alcune risposte, ma nessuna delle due risponde alla tua vera domanda sulla preelaborazione delle immagini .

A mia volta, non vedo problemi significativi con la tua implementazione purché sia ​​un progetto di studio, ben fatto.

Ma una cosa da notare potrebbe mancare. Esistono operazioni di base in morfologia matematica: erosione e dilatazione (usate da te). E ci operazioni complesse: varie combinazioni di quelle di base (es. Apertura e chiusura). Il link Wikipedia non è il miglior riferimento CV, ma puoi iniziare con esso per avere l'idea.

Di solito è meglio usare l' apertura anziché l'erosione e la chiusura anziché la dilatazione poiché in questo caso l'immagine binaria originale cambia molto meno (ma si raggiunge l'effetto desiderato di pulizia di spigoli vivi o riempimento di spazi vuoti). Quindi nel tuo caso dovresti controllare la chiusura (dilatazione dell'immagine seguita da erosione con lo stesso kernel). Nel caso in cui un'immagine extra-piccola 8 * 8 venga notevolmente modificata quando si dilata anche con 1 * 1 kernel (1 pixel è più del 16% dell'immagine) che è meno nelle immagini più grandi).

Per visualizzare l'idea vedi le seguenti immagini (dai tutorial di OpenCV: 1 , 2 ):

dilatazione: simbolo originale e dilatato

chiusura: simbolo originale e chiuso

Spero che sia d'aiuto.


Grazie per l'input! In realtà è non è un progetto di studio, quindi quale sarebbe il problema, allora? .. La mia immagine è abbastanza grande quando applico la dilatazione, 8x8 non è la dimensione dell'immagine, è il fattore di ridimensionamento per l'altezza e la larghezza. Ma può ancora essere un'opzione di miglioramento per provare diverse operazioni matematiche. Non sapevo di aprire e chiudere, lo proverò! Grazie.
Youngpanda,

Colpa mia, ridimensionare erroneamente la chiamata com'era con 8 * 8 come nuova dimensione. Nel caso in cui desideri utilizzare l'OCR nel mondo reale, dovrai prendere in considerazione un'opzione di trasferimento imparando la tua rete originale sui dati tipici della tua area di utilizzo. Almeno controlla se migliora la precisione, in genere dovrebbe fare.
f4f,

Un'altra cosa da controllare è l'ordine di pre-elaborazione: scala di grigi-> binario-> inverso-> ridimensionamento. Il ridimensionamento è un'operazione costosa e non vedo la necessità di applicarla all'immagine a colori. E la segmentazione dei simboli può essere eseguita senza rilevamento dei contorni (con qualcosa di meno costoso) se si dispone di un formato di input specifico ma potrebbe essere difficile da implementare.
f4f,

Se avessi un altro set di dati oltre a MNIST, potrei provare a trasferire l'apprendimento :) Cercherò di cambiare l'ordine di preelaborazione e di ricontattarti. Grazie! Non ho ancora trovato un'opzione più semplice del rilevamento dei contorni per il mio problema ...
Youngpanda,

1
Ok. Puoi raccogliere te stesso set di dati da quelle immagini che utilizzerai OCR su è una pratica comune.
f4f,

4

Quindi, hai bisogno di un approccio complesso perché ogni fase della tua cascata informatica si basi sui risultati precedenti. Nel tuo algoritmo hai le seguenti funzionalità:

  1. Preelaborazione delle immagini

Come accennato in precedenza, se si applica il ridimensionamento, si perdono informazioni sulle proporzioni dell'immagine. Devi fare lo stesso ritrattamento delle immagini delle cifre per ottenere gli stessi risultati impliciti nel processo di addestramento.

Modo migliore se ritagli l'immagine semplicemente con immagini di dimensioni fisse. In quella variante non sarà necessario trovare contorni e ridimensionare l'immagine delle cifre prima del processo di allenamento. Quindi potresti apportare una piccola modifica all'algoritmo di ritaglio per riconoscerlo meglio: è sufficiente trovare il contorno e posizionare la cifra senza ridimensionare al centro della cornice immagine rilevante per il riconoscimento.

Inoltre, dovresti prestare maggiore attenzione all'algoritmo di binarizzazione. Ho avuto esperienza nello studio dell'effetto dei valori soglia di binarizzazione sull'errore di apprendimento: posso dire che questo è un fattore molto significativo. Puoi provare un altro algoritmo di binarizzazione per verificare questa idea. Ad esempio, è possibile utilizzare questa libreria per testare algoritmi di binarizzazione alternativi.

  1. Algoritmo di apprendimento

Per migliorare la qualità del riconoscimento, utilizzare la convalida incrociata durante il processo di formazione. Questo ti aiuta a evitare il problema del sovradimensionamento dei dati di allenamento. Ad esempio puoi leggere questo articolo dove è spiegato come usarlo con Keras.

Talvolta tassi più elevati di misurazione dell'accuratezza non dicono nulla sulla reale qualità del riconoscimento, poiché la RNA addestrata non ha trovato il modello nei dati di formazione. Può essere collegato al processo di addestramento o all'insieme di dati di input come spiegato sopra, oppure può essere causato dalla scelta dell'architettura ANN.

  1. Architettura ANN

È un grosso problema. Come definire la migliore architettura ANN per risolvere il compito? Non ci sono modi comuni per fare quella cosa. Ma ci sono alcuni modi per avvicinarsi all'ideale. Ad esempio potresti leggere questo libro . Ti aiuta a rendere una visione migliore per il tuo problema. Inoltre puoi trovare qui alcune formule euristiche per adattarsi al numero di strati / elementi nascosti per la tua ANN. Anche qui troverai una piccola panoramica per questo.

Spero che questa volontà aiuti.


1. Se ti capisco correttamente, non riesco a ritagliare a dimensioni fisse, è l'immagine di un numero a più cifre e tutti i casi sono diversi per dimensioni / luogo ecc. O intendevi qualcosa di diverso? Sì, ho provato diversi metodi di binarizzazione e parametri ottimizzati, se è questo che intendi. 2. In realtà il riconoscimento su MNIST è eccezionale, non si verificano errori di adattamento, l'accuratezza che ho menzionato è l'accuratezza del test. Né la rete né la sua formazione sono il problema. 3. Grazie per tutti i collegamenti, sono abbastanza contento della mia architettura, ovviamente c'è sempre spazio per miglioramenti.
Youngpanda,

Sì, hai capito. Ma hai sempre la possibilità di rendere il tuo set di dati più unificato. Nel tuo caso è meglio ritagliare le immagini delle cifre dai contorni come già fai. Ma dopo sarà meglio espandere le immagini delle cifre in dimensioni unificate in base alle dimensioni massime di un'immagine delle cifre con la scala xey. Potresti preferire il centro della regione del contorno delle cifre per fare quella cosa. Fornirà dati di input più chiari per l'algoritmo di allenamento.
Egor Zamotaev,

Vuoi dire che devo saltare la dilatazione? Alla fine ho già centrato l'immagine, quando applico il bordo (50 px su ciascun lato). Dopodiché ridimensionerò ogni cifra a 28x28, poiché questa è la dimensione di cui abbiamo bisogno per MNIST. Vuoi dire che posso ridimensionare a 28x28 in modo diverso?
Youngpanda,

1
Sì, la dilatazione è indesiderabile. I contorni possono avere rapporti diversi per altezza e larghezza, ecco perché qui è necessario migliorare l'algoritmo. Almeno dovresti creare dimensioni delle immagini con gli stessi rapporti. Dato che hai dimensioni delle immagini in ingresso 28x28, devi preparare le immagini con lo stesso rapporto 1: 1 per le scale x e y. Non dovresti ottenere un bordo di 50 px per ogni lato dell'immagine, ma i bordi X, Y px che soddisfano la condizione: contourSizeX + borderSizeX == contourSizeY + borderSizeY. È tutto.
Egor Zamotaev,

Ho già provato senza dilatazione (ho dimenticato di menzionare nel post). Non ha cambiato alcun risultato ... Il mio numero di confine era sperimentale. Idealmente avrei bisogno che le mie cifre si adattassero alla scatola 20x20 (dimensioni normalizzate come tali nel set di dati) e dopo che lo
spostavo

1

Dopo alcune ricerche ed esperimenti, sono giunto alla conclusione che la preelaborazione dell'immagine stessa non era il problema (ho modificato alcuni parametri suggeriti, come ad esempio dimensioni e forma di dilatazione, ma non erano cruciali per i risultati). Ciò che ha aiutato, tuttavia, sono state le seguenti 2 cose:

  1. Come notato da @ f4f, dovevo raccogliere il mio set di dati con dati reali. Questo ha già aiutato moltissimo.

  2. Ho apportato importanti modifiche alla preelaborazione della segmentazione. Dopo aver ottenuto i contorni individuali, per prima cosa normalizzo le dimensioni per adattarle a una 20x20casella di pixel (così come sono MNIST). Dopodiché centro la scatola nel mezzo 28x28dell'immagine usando il centro di massa (che per le immagini binarie è il valore medio in entrambe le dimensioni).

Naturalmente, ci sono ancora casi di segmentazione difficili, come cifre sovrapposte o connesse, ma le modifiche sopra riportate hanno risposto alla mia domanda iniziale e migliorato le prestazioni della mia classificazione.

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.