Pracuję nad odręcznym rozpoznawaniem wielocyfrowym przy Java
użyciu OpenCV
biblioteki do wstępnego przetwarzania i segmentacji oraz Keras
modelu wyszkolonego w zakresie MNIST (z dokładnością 0,98) do rozpoznawania.
Rozpoznawanie wydaje się działać całkiem dobrze, pomijając jedną rzecz. Sieć dość często nie rozpoznaje tych (numer „jeden”). Nie mogę ustalić, czy dzieje się tak z powodu wstępnego przetwarzania / nieprawidłowej implementacji segmentacji, czy też sieć wyszkolona na standardowym MNIST po prostu nie widziała numeru jeden, który wygląda jak moje przypadki testowe.
Oto jak wyglądają problematyczne cyfry po przetwarzaniu wstępnym i segmentacji:
staje się i jest klasyfikowany jako 4
.
staje się i jest klasyfikowany jako 7
.
staje się i jest klasyfikowany jako 4
. I tak dalej...
Czy można to naprawić, poprawiając proces segmentacji? Czy raczej poprzez ulepszenie zestawu treningowego?
Edycja: Ulepszenie zestawu treningowego (augmentacja danych) zdecydowanie by pomogło, co już testuję, wciąż pozostaje kwestia poprawnego przetwarzania wstępnego.
Moje wstępne przetwarzanie obejmuje zmianę rozmiaru, konwersję do skali szarości, binaryzację, inwersję i rozszerzenie. Oto kod:
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);
Wstępnie przetworzony obraz jest następnie dzielony na poszczególne cyfry w następujący sposób:
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);
}
źródło
Odpowiedzi:
Uważam, że twoim problemem jest proces dylatacji. Rozumiem, że chcesz znormalizować rozmiary obrazu, ale nie powinieneś łamać proporcji, powinieneś zmienić rozmiar na maksymalny pożądany przez jedną oś (tę, która pozwala na największą zmianę skali bez pozwalania, aby inny wymiar osi przekraczał maksymalny rozmiar) i wypełnienie z kolorem tła reszta obrazu. To nie jest tak, że „standardowy MNIST po prostu nie widział numeru jeden, który wygląda jak twoje przypadki testowe”, sprawiasz, że twoje zdjęcia wyglądają jak różne wyszkolone liczby (te, które są rozpoznawane)
Jeśli zachowałeś właściwe proporcje obrazów (źródłowe i przetworzone), możesz zobaczyć, że nie tylko zmieniłeś rozmiar obrazu, ale go „zniekształcałeś”. Może to wynikać z niejednorodnego rozszerzenia lub nieprawidłowej zmiany rozmiaru
źródło
Imgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC);
. Tutaj proporcje pozostają takie same, gdzie mam przełamać proporcje?Istnieje już kilka odpowiedzi, ale żadna z nich nie odpowiada na twoje pytanie dotyczące wstępnego przetwarzania obrazu .
Z kolei nie widzę żadnych poważnych problemów z implementacją, o ile jest to projekt studyjny, dobrze zrobiony.
Ale jedną rzeczą, którą możesz zauważyć, że możesz przegapić. Istnieją podstawowe operacje w morfologii matematycznej: erozja i dylatacja (używane przez ciebie). I są złożone operacje: różne kombinacje podstawowych (np. Otwieranie i zamykanie). Link do Wikipedii nie jest najlepszym odniesieniem do CV, ale możesz zacząć od niego, aby uzyskać pomysł.
Zwykle lepiej jest używać otwierania zamiast erozji i zamykania zamiast dylatacji, ponieważ w tym przypadku oryginalny obraz binarny zmienia się znacznie mniej (ale osiągany jest pożądany efekt czyszczenia ostrych krawędzi lub wypełniania luk). Więc w twoim przypadku powinieneś sprawdzić zamknięcie (dylatacja obrazu, a następnie erozja z tym samym jądrem). W przypadku, gdy bardzo mały obraz 8 * 8 jest znacznie zmodyfikowany, gdy rozszerzysz go nawet z jądrem 1 * 1 (1 piksel to więcej niż 16% obrazu), co jest mniejsze na większych obrazach).
Aby zwizualizować pomysł, zobacz następujące zdjęcia (z samouczków OpenCV: 1 , 2 ):
rozszerzanie się:
zamknięcie:
Mam nadzieję, że to pomoże.
źródło
Potrzebujesz kompleksowego podejścia, ponieważ każdy krok kaskady obliczeniowej oparty jest na poprzednich wynikach. W twoim algorytmie masz następujące funkcje:
Jak wspomniano wcześniej, zastosowanie zmiany rozmiaru powoduje utratę informacji o proporcjach obrazu. Musisz wykonać to samo przetwarzanie obrazów cyfr, aby uzyskać te same wyniki, które sugerowano w procesie szkolenia.
Lepszy sposób, jeśli po prostu przycinasz zdjęcie za pomocą zdjęć o stałym rozmiarze. W tym wariancie nie będzie potrzeby znajdowania konturów i zmiany rozmiaru obrazu cyfrowego przed procesem treningowym. Następnie możesz wprowadzić niewielką zmianę w algorytmie przycinania w celu lepszego rozpoznania: po prostu znajdź kontur i umieść cyfrę bez zmiany rozmiaru w środku odpowiedniej ramki obrazu w celu rozpoznania.
Powinieneś także zwrócić większą uwagę na algorytm binaryzacji. Miałem doświadczenie w badaniu wpływu wartości progowych binaryzacji na błąd uczenia się: mogę powiedzieć, że jest to bardzo istotny czynnik. Możesz wypróbować inne algorytmy binaryzacji, aby sprawdzić ten pomysł. Na przykład możesz użyć tej biblioteki do testowania alternatywnych algorytmów binaryzacji.
Aby poprawić jakość uznawania, w procesie szkolenia stosuje się walidację krzyżową . Pomaga to uniknąć problemu nadmiernego dopasowania danych treningowych. Na przykład możesz przeczytać ten artykuł, w którym wyjaśniono, jak używać go z Keras.
Czasami wyższe wskaźniki dokładności nie mówią nic o prawdziwej jakości rozpoznawania, ponieważ przeszkolony ANN nie znalazł wzoru w danych treningowych. Może to być związane z procesem szkoleniowym lub zestawem danych wejściowych, jak wyjaśniono powyżej, lub może to wynikać z wyboru architektury ANN.
To duży problem. Jak zdefiniować lepszą architekturę ANN, aby rozwiązać zadanie? Nie ma wspólnych sposobów na zrobienie tego. Ale istnieje kilka sposobów na zbliżenie się do ideału. Na przykład możesz przeczytać tę książkę . Pomaga w lepszej wizji problemu. Możesz również znaleźć tutaj niektóre formuły heurystyczne, które pasują do liczby ukrytych warstw / elementów dla Twojego ANN. Również tutaj znajdziesz krótki przegląd tego.
Mam nadzieję, że to pomoże.
źródło
Po kilku badaniach i eksperymentach doszedłem do wniosku, że samo przetwarzanie obrazu nie stanowiło problemu (zmieniłem niektóre sugerowane parametry, takie jak np. Rozmiar i kształt dylatacji, ale nie były one kluczowe dla wyników). Pomogły jednak 2 następujące rzeczy:
Jak zauważył @ f4f, musiałem zebrać własny zestaw danych z danymi ze świata rzeczywistego. To już ogromnie pomogło.
Wprowadziłem ważne zmiany w moim wstępnym przetwarzaniu segmentacji. Po uzyskaniu indywidualnych konturów najpierw normalizuję rozmiar obrazów, aby dopasować je do
20x20
ramki pikselowej (gdy są w środkuMNIST
). Następnie wyśrodkowuję pole na środku28x28
obrazu za pomocą środka masy (który dla obrazów binarnych jest wartością średnią dla obu wymiarów).Oczywiście nadal występują trudne przypadki segmentacji, takie jak nakładanie się lub łączenie cyfr, ale powyższe zmiany odpowiedziały na moje początkowe pytanie i poprawiły moją klasyfikację.
źródło