Ich arbeite an handprinted mehrstelligen Anerkennung mit Java
, mit OpenCV
Bibliothek zur Vorverarbeitung und Segmentierung und ein Keras
Modell 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:
wird und wird klassifiziert als 4
.
wird und wird klassifiziert als 7
.
wird und 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);
}
quelle
Antworten:
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).
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
quelle
Imgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC);
. Hier bleibt die Aspektration gleich, wo breche ich die Proportionen?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:
Schließen:
Ich hoffe es hilft.
quelle
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:
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.
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.
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.
quelle
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:
Wie @ f4f bemerkte, musste ich meinen eigenen Datensatz mit realen Daten sammeln. Das hat schon enorm geholfen.
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
20x20
Pixelbox passen (so wie sie sindMNIST
). Danach zentriere ich die Box in der28x28
Bildmitte 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.
quelle