Wie kann die Ziffernerkennung eines auf MNIST trainierten Modells verbessert werden?

12

Ich arbeite an handprinted mehrstelligen Anerkennung mit Java, mit OpenCVBibliothek zur Vorverarbeitung und Segmentierung und ein KerasModell auf MNIST trainiert (mit einer Genauigkeit von 0,98) für die Anerkennung.

Abgesehen von einer Sache scheint die Erkennung recht gut zu funktionieren. Das Netzwerk erkennt diese häufig nicht (Nummer "Eins"). Ich kann nicht herausfinden, ob dies auf eine Vorverarbeitung / falsche Implementierung der Segmentierung zurückzuführen ist oder ob ein auf Standard-MNIST geschultes Netzwerk die Nummer eins, die wie meine Testfälle aussieht, einfach nicht gesehen hat.

So sehen die problematischen Ziffern nach der Vorverarbeitung und Segmentierung aus:

Geben Sie hier die Bildbeschreibung einwird Geben Sie hier die Bildbeschreibung einund wird klassifiziert als 4.

Geben Sie hier die Bildbeschreibung einwird Geben Sie hier die Bildbeschreibung einund wird klassifiziert als 7.

Geben Sie hier die Bildbeschreibung einwird Geben Sie hier die Bildbeschreibung einund wird klassifiziert als 4. Und so weiter...

Kann dies durch eine Verbesserung des Segmentierungsprozesses behoben werden? Oder eher durch die Verbesserung des Trainingssatzes?

Bearbeiten: Eine Verbesserung des Trainingssatzes (Datenerweiterung) würde definitiv helfen, was ich bereits teste, die Frage der korrekten Vorverarbeitung bleibt weiterhin bestehen.

Meine Vorverarbeitung besteht aus Größenänderung, Konvertierung in Graustufen, Binärisierung, Inversion und Dilatation. Hier ist der Code:

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);

Das vorverarbeitete Bild wird dann wie folgt in einzelne Ziffern segmentiert:

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);
}
Youngpanda
quelle
1
Verwenden Sie die Maske oder die (maskierten?) ursprünglichen Graustufenpixel als Eingabe für Ihre Klassifizierung?
Micka
@ Micka Ich verwende die vorverarbeitete (binärisierte, invertierte, erweiterte) Version. Diejenigen, die zum MNIST-Trainingssatz passen. Es gibt Beispiele für die Nummer "1" nach der Vorverarbeitung in meinem Beitrag.
Youngpanda

Antworten:

5

Ich glaube, dass Ihr Problem der Dilatationsprozess ist. Ich verstehe, dass Sie die Bildgröße normalisieren möchten, aber die Proportionen nicht brechen sollten. Sie sollten die Größe auf das von einer Achse gewünschte Maximum ändern (diejenige, die die größte Neuskalierung ermöglicht, ohne dass eine andere Achsabmessung die maximale Größe überschreitet) und füllen mit Hintergrundfarbe den Rest des Bildes. Es ist nicht so, dass "Standard-MNIST die Nummer eins, die wie Ihre Testfälle aussieht, einfach nicht gesehen hat", Sie lassen Ihre Bilder wie verschiedene trainierte Zahlen aussehen (die erkannt werden).

Überlappung der Quelle und der verarbeiteten Bilder

Wenn Sie das korrekte Seitenverhältnis Ihrer Bilder (Quelle und nachbearbeitet) beibehalten haben, können Sie feststellen, dass Sie die Größe des Bildes nicht nur geändert, sondern auch "verzerrt" haben. Dies kann entweder auf eine inhomogene Dilatation oder auf eine falsche Größenänderung zurückzuführen sein

Herr
quelle
Ich glaube, dass @SiR etwas Gewicht hat. Versuchen Sie, das Seitenverhältnis der numerischen Literale nicht zu ändern.
ZdaR
Entschuldigung, ich folge nicht ganz. Denken Sie, dass mein Dilatations- oder Größenänderungsprozess das Problem ist? Ich verändere das Bild nur am Anfang mit dieser Zeile Imgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC);. Hier bleibt die Aspektration gleich, wo breche ich die Proportionen?
Youngpanda
@SiR als Antwort auf Ihre obigen Änderungen: Ja, ich verändere das Bild nicht nur in der Größe, ich wende verschiedene Operationen an. Eine davon ist die Dilatation, eine morphologische, die eine leichte "Verzerrung" verursacht, da sie helle Bereiche innerhalb eines Bildes verursacht Bild zu "wachsen". Oder meinst du die Größe am Ende zu ändern, wo ich die Bilder 28x28 machen?
Youngpanda
@youngpanda, vielleicht finden Sie die Diskussion hier stackoverflow.com/questions/28525436/… interessant. Es könnte Ihnen einen Hinweis geben, warum Ihr Ansatz keine guten Ergebnisse bringt
SiR
@ SiR danke für den Link, ich bin mit LeNet vertraut, aber es ist schön, wieder zu lesen
Youngpanda
5

Es sind bereits einige Antworten veröffentlicht, aber keine beantwortet Ihre eigentliche Frage zur Bildvorverarbeitung .

Ich sehe wiederum keine wesentlichen Probleme mit Ihrer Implementierung, solange es sich um ein gut gemachtes Studienprojekt handelt.

Aber eine Sache zu beachten, die Sie möglicherweise verpassen. In der mathematischen Morphologie gibt es grundlegende Operationen: Erosion und Dilatation (von Ihnen verwendet). Und es gibt komplexe Operationen: verschiedene Kombinationen von Grundoperationen (z. B. Öffnen und Schließen). Der Wikipedia-Link ist nicht die beste Lebenslaufreferenz, aber Sie können damit beginnen, um sich ein Bild zu machen.

Normalerweise ist es besser, das Öffnen anstelle der Erosion und das Schließen anstelle der Erweiterung zu verwenden, da sich in diesem Fall das ursprüngliche Binärbild viel weniger ändert (aber der gewünschte Effekt des Reinigens scharfer Kanten oder des Füllens von Lücken wird erreicht). In Ihrem Fall sollten Sie also das Schließen überprüfen (Bilddilatation, gefolgt von Erosion mit demselben Kernel). Für den Fall, dass das extra kleine Bild 8 * 8 stark modifiziert wird, wenn Sie es sogar mit einem 1 * 1-Kernel erweitern (1 Pixel entspricht mehr als 16% des Bildes), was bei größeren Bildern weniger ist).

Um die Idee zu visualisieren, sehen Sie sich die folgenden Bilder an (aus OpenCV-Tutorials: 1 , 2 ):

Erweiterung: ursprüngliches Symbol und erweitertes

Schließen: ursprüngliches Symbol und geschlossenes

Ich hoffe es hilft.

f4f
quelle
Vielen Dank für die Eingabe! Eigentlich ist es kein Studienprojekt, also was wäre dann das Problem? .. Mein Bild ist ziemlich groß, wenn ich die Erweiterung anwende, 8x8 ist nicht die Größe des Bildes, es ist der Größenänderungsfaktor für Höhe und Breite. Es kann jedoch eine Verbesserungsoption sein, verschiedene mathematische Operationen auszuprobieren. Ich wusste nichts über das Öffnen und Schließen, ich werde es ausprobieren! Vielen Dank.
Youngpanda
Mein Fehler, falsch interpretierte Größenänderung Anruf wie es mit 8 * 8 als neue Größe war. Wenn Sie OCR in der realen Welt verwenden möchten, sollten Sie eine Option in Betracht ziehen, Ihr ursprüngliches Netz anhand von Daten zu übertragen, die für Ihren Anwendungsbereich typisch sind. Überprüfen Sie zumindest, ob es die Genauigkeit verbessert, im Allgemeinen sollte es tun.
f4f
Eine andere zu überprüfende Sache ist die Vorverarbeitungsreihenfolge: Graustufen-> Binär-> Invers-> Größenänderung. Das Ändern der Größe ist ein kostspieliger Vorgang, und ich sehe keine Notwendigkeit, ihn auf ein Farbbild anzuwenden. Die Segmentierung von Symbolen kann ohne Konturerkennung (mit etwas weniger Kosten) erfolgen, wenn Sie über ein bestimmtes Eingabeformat verfügen, die Implementierung jedoch schwierig sein kann.
f4f
Wenn ich außer MNIST noch einen anderen Datensatz hätte, könnte ich versuchen, das Lernen zu übertragen :) Ich werde versuchen, die Vorverarbeitungsreihenfolge zu ändern und mich bei Ihnen zu melden. Vielen Dank! Ich habe noch keine einfachere Option als die Konturerkennung für mein Problem gefunden ...
youngpanda
1
OK. Sie können den Datensatz selbst aus den Bildern erfassen, für die Sie OCR verwenden. Dies ist eine gängige Praxis.
f4f
4

Sie benötigen also einen komplexen Ansatz, da jeder Schritt Ihrer Computerkaskade auf den vorherigen Ergebnissen basiert. In Ihrem Algorithmus haben Sie die nächsten Funktionen:

  1. Bildvorverarbeitung

Wie bereits erwähnt, verlieren Sie beim Anwenden der Größenänderung Informationen über die Seitenverhältnisse des Bildes. Sie müssen die gleiche Verarbeitung von Ziffernbildern durchführen, um die gleichen Ergebnisse zu erzielen, die im Trainingsprozess impliziert wurden.

Besser, wenn Sie das Bild nur mit Bildern fester Größe zuschneiden. In dieser Variante müssen Sie in Konturen vor dem Trainingsprozess kein Ziffernbild finden und dessen Größe ändern. Dann können Sie Ihren Zuschneidealgorithmus zur besseren Erkennung ein wenig ändern: Finden Sie einfach die Kontur und platzieren Sie Ihre Ziffer, ohne die Größe für die Erkennung in der Mitte des relevanten Bildrahmens zu ändern.

Außerdem sollten Sie dem Binärisierungsalgorithmus mehr Aufmerksamkeit schenken. Ich habe Erfahrung mit der Untersuchung der Auswirkung von Binärisierungsschwellenwerten auf Lernfehler: Ich kann sagen, dass dies ein sehr wichtiger Faktor ist. Sie können andere Binarisierungsalgorithmen ausprobieren, um diese Idee zu überprüfen. Sie können diese Bibliothek beispielsweise zum Testen alternativer Binärisierungsalgorithmen verwenden.

  1. Lernalgorithmus

Um die Qualität der Erkennung zu verbessern, verwenden Sie im Schulungsprozess eine Kreuzvalidierung . Dies hilft Ihnen, das Problem der Überanpassung Ihrer Trainingsdaten zu vermeiden . Sie können beispielsweise diesen Artikel lesen , in dem die Verwendung mit Keras erläutert wird.

Manchmal sagen höhere Genauigkeitsraten nichts über die tatsächliche Erkennungsqualität aus, da trainierte ANN das Muster nicht in den Trainingsdaten gefunden haben. Es kann mit dem Trainingsprozess oder dem Eingabedatensatz verbunden sein, wie oben erläutert, oder es kann durch die ANN-Architektur zur Auswahl führen.

  1. ANN-Architektur

Das ist ein großes Problem. Wie definiere ich die bessere ANN-Architektur, um die Aufgabe zu lösen? Es gibt keine üblichen Wege, dies zu tun. Es gibt jedoch einige Möglichkeiten, dem Ideal näher zu kommen. Zum Beispiel könnten Sie dieses Buch lesen . Es hilft Ihnen, eine bessere Vision für Ihr Problem zu entwickeln. Auch können Sie finden hier einige Heuristiken Formeln die Anzahl der verborgenen Schichten / Elemente für Ihren ANN passen. Auch hier finden Sie hierzu einen kleinen Überblick.

Ich hoffe das wird helfen.

Egor Zamotaev
quelle
1. Wenn ich Sie richtig verstehe, kann ich nicht auf eine feste Größe zuschneiden, es ist ein Bild mit einer mehrstelligen Zahl und alle Fälle unterscheiden sich in Größe / Ort usw. Oder haben Sie etwas anderes gemeint? Ja, ich habe verschiedene Binärisierungsmethoden ausprobiert und Parameter optimiert, wenn Sie das meinen. 2. Tatsächlich ist die Erkennung auf MNIST großartig, es findet keine Überanpassung statt, die Genauigkeit, die ich erwähnt habe, ist Testgenauigkeit. Weder das Netzwerk noch seine Schulung sind das Problem. 3. Danke für all die Links, ich bin sehr zufrieden mit meiner Architektur, natürlich gibt es immer Raum für Verbesserungen.
Youngpanda
Ja, du verstehst es. Sie haben jedoch immer die Möglichkeit, Ihren Datensatz einheitlicher zu gestalten. In Ihrem Fall ist es einfach besser, Bilder von Ziffern anhand der Konturen zuzuschneiden, als Sie es bereits getan haben. Danach ist es jedoch besser, die Bilder Ihrer Ziffern auf eine einheitliche Größe zu erweitern, die der maximalen Größe eines Ziffernbildes im x- und y-Maßstab entspricht. Sie könnten es vorziehen, die Mitte des Ziffernkonturbereichs zu verwenden, um dies zu tun. Sie erhalten sauberere Eingabedaten für Ihren Trainingsalgorithmus.
Egor Zamotaev
Meinst du, dass ich die Dilatation überspringen muss? Am Ende zentriere ich das Bild bereits, wenn ich den Rand anbringe (50 px auf jeder Seite). Danach verändere ich jede Ziffer auf 28x28, da dies die Größe ist, die wir für MNIST benötigen. Meinst du, ich kann die Größe auf 28x28 anders ändern?
Youngpanda
1
Ja, eine Erweiterung ist unerwünscht. Ihre Konturen können je nach Höhe und Breite unterschiedliche Verhältnisse haben. Deshalb müssen Sie hier Ihren Algorithmus verbessern. Zumindest sollten Sie Bildgrößen mit den gleichen Verhältnissen erstellen. Da Sie 28x28 Eingangsbildgrößen haben, müssen Sie Bilder mit dem gleichen Verhältnis von 1: 1 im x- und y-Maßstab vorbereiten. Sie sollten nicht für jede Bildseite einen 50-Pixel-Rand erhalten, sondern X-, Y-Pixel-Ränder, die die Bedingung erfüllen: contourSizeX + borderSizeX == contourSizeY + borderSizeY. Das ist alles.
Egor Zamotaev
Ich habe es bereits ohne Erweiterung versucht (vergessen, in der Post zu erwähnen). Es hat keine Ergebnisse geändert ... Meine Grenznummer war experimentell. Idealerweise würde ich meine Ziffern benötigen, um in eine 20x20-Box zu passen (größennormalisiert als solche im Datensatz) und sie danach unter Verwendung des Massenschwerpunkts verschieben ...
youngpanda
1

Nach einigen Recherchen und Experimenten kam ich zu dem Schluss, dass die Bildvorverarbeitung selbst nicht das Problem war (ich habe einige vorgeschlagene Parameter geändert, wie z. B. Dilatationsgröße und -form, aber sie waren für die Ergebnisse nicht entscheidend). Was jedoch geholfen hat, waren zwei Dinge:

  1. Wie @ f4f bemerkte, musste ich meinen eigenen Datensatz mit realen Daten sammeln. Das hat schon enorm geholfen.

  2. Ich habe wichtige Änderungen an meiner Segmentierungsvorverarbeitung vorgenommen. Nachdem ich einzelne Konturen erhalten habe, normalisiere ich die Bilder zunächst so, dass sie in eine 20x20Pixelbox passen (so wie sie sind MNIST). Danach zentriere ich die Box in der 28x28Bildmitte unter Verwendung des Massenschwerpunkts (der für Binärbilder der Mittelwert über beide Dimensionen ist).

Natürlich gibt es immer noch schwierige Segmentierungsfälle wie überlappende oder verbundene Ziffern, aber die obigen Änderungen beantworteten meine ursprüngliche Frage und verbesserten meine Klassifizierungsleistung.

Youngpanda
quelle