Jak poprawić cyfrowe rozpoznawanie modelu przeszkolonego na MNIST?

12

Pracuję nad odręcznym rozpoznawaniem wielocyfrowym przy Javaużyciu OpenCVbiblioteki do wstępnego przetwarzania i segmentacji oraz Kerasmodelu 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:

wprowadź opis zdjęcia tutajstaje się wprowadź opis zdjęcia tutaji jest klasyfikowany jako 4.

wprowadź opis zdjęcia tutajstaje się wprowadź opis zdjęcia tutaji jest klasyfikowany jako 7.

wprowadź opis zdjęcia tutajstaje się wprowadź opis zdjęcia tutaji 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);
}
Youngpanda
źródło
1
czy używasz maski lub (zamaskowanych?) oryginalnych pikseli w skali szarości jako danych wejściowych do swojej klasyfikacji?
Micka
@Micka Używam wersji wstępnie przetworzonej (binarnej, odwróconej, rozszerzonej). Te, które pasują do zestawu treningowego MNIST. Istnieją przykłady liczby „1” po wstępnym przetwarzaniu w moim poście.
youngpanda

Odpowiedzi:

5

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)

Nakładanie się obrazów źródłowych i przetworzonych

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

Pan
źródło
Uważam, że @SiR ma pewną wagę, spróbuj nie zmieniać proporcji literałów numerycznych.
ZdaR
Przepraszam, nie do końca podążam. Czy uważasz, że problem stanowi mój proces rozszerzenia lub zmiany rozmiaru? Zmieniam rozmiar obrazu tylko na początku w tej linii Imgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC);. Tutaj proporcje pozostają takie same, gdzie mam przełamać proporcje?
youngpanda
@SiR w odpowiedzi na powyższe zmiany: tak, nie zmieniam rozmiaru obrazu, stosuję różne operacje, jedną z nich jest dylatacja, która jest morfologiczna, co powoduje lekkie „zniekształcenie”, ponieważ powoduje jasne obszary w obrębie obraz „rośnie”. A może chodzi ci o zmianę rozmiaru na samym końcu, gdzie robię zdjęcia 28 x 28?
Youngpanda
@Youngpanda, dyskusja może być dla Ciebie tutaj stackoverflow.com/questions/28525436/... interesująca. To może dać ci wskazówkę, dlaczego twoje podejście nie przynosi dobrych rezultatów
SiR
@SiR dziękuję za link, znam LeNet, ale miło jest przeczytać ponownie
youngpanda
5

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ę: oryginalny i rozszerzony symbol

zamknięcie: oryginalny symbol i zamknięty

Mam nadzieję, że to pomoże.

f4f
źródło
Dziękuję za wkład! Właściwie to nie jest projekt badawczy, więc jaki byłby wtedy problem? .. Mój obraz jest dość duży, kiedy stosuję dylatację, 8x8 nie jest rozmiarem obrazu, jest to współczynnik zmiany wysokości dla wysokości i szerokości. Ale nadal może być opcją ulepszenia, aby wypróbować różne operacje matematyczne. Nie wiedziałem o otwieraniu i zamykaniu, wypróbuję to! Dziękuję Ci.
youngpanda
Moja wina, źle odczytałem rozmowę rozmiaru, jak to było z 8 * 8 jako nowy rozmiar. Jeśli chcesz korzystać z OCR w świecie rzeczywistym, zastanów się nad opcją transferu, ucząc się oryginalnej sieci na danych typowych dla Twojego obszaru użytkowania. Przynajmniej sprawdź, czy poprawia to dokładność, ogólnie powinno.
f4f
Kolejną rzeczą do sprawdzenia jest kolejność wstępnego przetwarzania: skala szarości-> binarne-> odwrotne-> zmiana rozmiaru. Zmiana rozmiaru jest kosztowną operacją i nie widzę potrzeby stosowania jej do kolorowego obrazu. A segmentacja symboli może odbywać się bez wykrywania konturu (z czymś mniej kosztownym), jeśli masz jakiś określony format wejściowy, ale może być trudny do wdrożenia.
f4f
Gdybym miał inny zestaw danych oprócz MNIST, mógłbym spróbować przenieść naukę :) Spróbuję zmienić kolejność przetwarzania wstępnego i skontaktuję się z Tobą. Dziękuję Ci! Nie znalazłem jeszcze łatwiejszej opcji niż wykrycie konturu dla mojego problemu ...
youngpanda
1
Ok. Możesz sam zbierać zestaw danych z obrazów, których będziesz używać OCR, ponieważ jest to powszechna praktyka.
f4f
4

Potrzebujesz kompleksowego podejścia, ponieważ każdy krok kaskady obliczeniowej oparty jest na poprzednich wynikach. W twoim algorytmie masz następujące funkcje:

  1. Wstępne przetwarzanie obrazu

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.

  1. Algorytm uczenia się

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.

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

Egor Zamotaev
źródło
1. Jeśli dobrze cię rozumiem, nie mogę przyciąć do ustalonego rozmiaru, jest to obraz liczby wielocyfrowej, a wszystkie przypadki różnią się rozmiarem / miejscem itp. Czy masz na myśli coś innego? Tak, próbowałem różnych metod binaryzacji i poprawionych parametrów, jeśli o to ci chodzi. 2. W rzeczywistości rozpoznanie na MNIST jest świetne, nie dzieje się tak, że nie można go przerobić, dokładność, o której wspomniałem, to dokładność testu. Problemem nie jest ani sieć, ani jej szkolenie. 3. Dzięki za wszystkie linki, jestem jednak całkiem zadowolony z mojej architektury, oczywiście zawsze jest miejsce na ulepszenia.
youngpanda
Tak, rozumiesz Ale zawsze masz możliwość ujednolicenia zestawu danych. W twoim przypadku lepiej przyciąć obrazy cyfr według konturów, tak jak już to robisz. Ale po tym lepiej będzie po prostu rozszerzyć obrazy cyfr do ujednoliconego rozmiaru zgodnie z maksymalnym rozmiarem obrazu cyfrowego w skali xiy. Możesz wybrać środek tego obszaru cyfr konturowych. Zapewni to bardziej czyste dane wejściowe dla algorytmu treningowego.
Egor Zamotaev
Czy masz na myśli, że muszę pominąć dylatację? Na koniec już wyśrodkowuję obraz, kiedy stosuję ramkę (50 pikseli z każdej strony). Następnie zmieniam rozmiar każdej cyfry na 28 x 28, ponieważ jest to rozmiar, którego potrzebujemy dla MNIST. Czy masz na myśli, że mogę zmienić rozmiar do 28 x 28 inaczej?
youngpanda,
1
Tak, rozszerzenie jest niepożądane. Twoje kontury mogą mieć różne proporcje w zależności od wysokości i szerokości, dlatego tutaj potrzebujesz ulepszenia swojego algorytmu. Przynajmniej powinieneś tworzyć obrazy o tych samych proporcjach. Ponieważ masz wejściowe rozmiary obrazu 28 x 28, musisz przygotować obrazy o tym samym stosunku 1: 1 w skali xiy. Nie powinieneś otrzymać granicy 50 pikseli dla każdej strony obrazu, ale ramki X, Y px, które spełniają warunek: contourSizeX + borderSizeX == contourSizeY + borderSizeY. To wszystko.
Egor Zamotaev
Próbowałem już bez dylatacji (zapomniałem wspomnieć w poście). Nie zmieniło to żadnych wyników ... Mój numer granicy był eksperymentalny. Idealnie chciałbym, aby moje cyfry pasowały do ​​pudełka 20x20 (znormalizowanego jako takie w zestawie danych), a po tym
przesunęłyby
1

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:

  1. Jak zauważył @ f4f, musiałem zebrać własny zestaw danych z danymi ze świata rzeczywistego. To już ogromnie pomogło.

  2. 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 20x20ramki pikselowej (gdy są w środku MNIST). Następnie wyśrodkowuję pole na środku 28x28obrazu 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ę.

Youngpanda
źródło