OpenCV – #4 – Rozpoznawanie pisma

OpenCV – #4 – Rozpoznawanie pisma

W tej części kursu zajmiemy się rozpoznawaniem cyfr. Opisałem jeden z algorytmów służących do klasyfikacji obiektów K-Nearest Neighborhood.

Zamieszczone tutaj informacje pokazują podstawy dotyczące systemów uczących się i klasyfikacji obiektów.

« Poprzedni artykuł z serii

Napiszemy prosty program, który wczyta dane uczące z dołączonego pliku, następnie zostanie wyświetlone okno w którym myszką będzie można narysować cyfrę po czym program dokona jej klasyfikacji porównując z danymi uczącymi i odpowie nam jaką cyfrę narysowaliśmy.

Algorytm - K-Nearest Neighborhood

Jak każdy inny algorytm systemów uczących się KN składa się z dwóch faz. Pierwsza polega na dostarczeniu wektorów uczących wraz z odpowiednimi etykietami. Chodzi o to, że dostarczamy do naszego algorytmu obrazek prezentujący na przykład liczbę dwa:

2

Dodatkowo dostarczamy także odpowiadającą mu etykietę, która może być zapisana jako liczba float i dla naszej dwójki może wynosić 2.0, co jest najrozsądniejszym wyborem.

W drugiej fazie algorytmu używa się wytrenowanego wcześniej klasyfikatora by rozpoznawać dostarczane wektory testowe. Czyli np. jeśli dostarczymy obrazek, na którym jest dwójka to nasz algorytm zwróci nam na wyjściu, że ten obrazek najbardziej przypomina te sklasyfikowane wcześniej w procesie uczenie z etykietą 2.0. Jako odpowiedź dostaniemy etykietę na podstawie, której będziemy mogli stwierdzić, że nasz obrazek reprezentuje ręcznie zapisaną liczbę 2.

Przypuśćmy więc, że chcemy użyć klasyfikatora KN tak by rozróżniał liczby 0 i 2. Dostarczamy więc w procesie uczenia tablicę obrazków oraz drugą tablicę zawierającą odpowiadające im etykiety, możemy zobaczyć to na poniższej grafice, gdzie pierwszą tablicę stanową obrazki wejściowe, a drugą odpowiadające im etykiety.

cyfry

Choć powyższe grafiki prezentują dane uczące jako obrazki, w celu lepszego zobrazowania problemu, tak naprawdę do algorytmu w procesie uczenie przekazujemy dane w postaci wektorów uczących. Aby móc więc korzystać za algorytmu należy dokonać konwersji naszych obrazków na wektory. Robimy to w ten sposób, tworzymy macierz mającą 1 wiersz oraz liczbę kolumn odpowiadającą całkowitej ilości pikseli obrazka.

Mając więc obrazek o rozmiarach 16x16 powinniśmy utworzyć wektor o rozmiarach 1x256. Mając obrazek 3x3 powinniśmy utworzyć wektor o rozmiarach 1x9 co prezentuje poniższa grafika:

ropoznawanie_pisma

Na grafice pokazano obrazek binarny reprezentujący znak + o rozmiarach 3x3. Obok przedstawiono dwa sposoby na jego przechowywanie. Zmienna img nawiązuje do sposobu przechowywania obrazków w OpenCV, natomiast zmienna vector pokazuje jego reprezentację w postaci wektora.

Po tym jak już nauczymy algorytm, możemy wykorzystać go by klasyfikował dla nas dostarczane wektory testowe jako cyfry. Aby to zrobić należy dostarczyć dwie wartości: obrazek do klasyfikacji oraz pewną liczbę K.

Aby zrozumieć czym jest liczba K spójrzmy na poniższą grafikę.

ropoznawanie_pisma2

Cienkie zielone i czerwone strzałki z przeciętym grotem reprezentują nasze wektory uczące. Zielone są dla dwójek natomiast czerwone dla zer. Grube jednokolorowe strzałki: czerwona i zielona reprezentują obrazki uczące zamienione na wektory. Czarne kółka, są to kółka o średnicy równej naszej liczbie K.

Działanie algorytmu K-Nearest Neighborhood

Kiedy dostarczymy mu wektor testowy, reprezentowany powyżej poprzez grubą strzałkę, sprawdza całe jego sąsiedztwo w obrębie koła o średnicy "K" i znajduje wszystkie wektory uczące należące do tego sąsiedztwa.

Wynikiem będzie etykieta, która posiada najwięcej wektorów uczących we wspomnianym wyżej sąsiedztwie wektora testowego. Na podstawie zwróconej etykiety będziemy mogli stwierdzić jaka liczba została rozpoznana.

Dla zielonego grubego wektora na obrazku, w sąsiedztwie K reprezentowanym przez czarne kółko znajdują się tylko wektory uczące o etykiecie 2.0, a więc obrazek reprezentowany przez ten wektor zostanie sklasyfikowany jako dwójka.

Na koniec należy zaznaczyć, że algorytm KN jest najprostszą metodą klasyfikacji obiektów. Ma więc wiele wad. Aby działał poprawnie należy mu dostarczyć duża ilość danych trenujących, które trzeba trzymać w pamięci przez cały czas działania programu.

Następnie należy porównać każdy wektor testowy z wszystkimi wektorami trenującymi. Algorytm jest więc powolny i pamięciożerny. Jednakowoż niezrażeni tymi faktami zaimplementujemy go i zobaczymy jakie rezultaty możemy osiągnąć przy jego użyciu.

Aby móc uruchomić program należy jeszcze dysponować bazą danych uczących, należy ją pobrać z odpowiedniej strony. Konkretnie chodzi o plik semeion.data. W pliku tym zostały umieszczone 1593 obrazy ręcznie napisanych cyfr od 0 do 9 przez 80 różnych osób.

Zostały one zeskanowane, przeskalowane do rozmiarów 16x16 w odcieniach szarości, następnie wykonano progowanie i każdy piksel został zapisany w formie binarnej jako 0.0 lub 1.0 co odpowiada kolorom czarnemu lub białemu. Poniżej znajduje się kod programu.

Przystąpmy więc do analizy kodu. Na początek wczytujemy bazę danych zawierającą obrazki już w postaci wektorów trenujących oraz odpowiadających im etykiet z pliku semeion.data.

Po otwarciu pliku w edytorze tekstowym zobaczymy coś w rodzaju:

Zawartość pliku tekstowego.

Zawartość pliku tekstowego.

Każda linia zawiera jeden wektor uczący, tzn. 256 następujących po sobie liczb. Obrazki są w formie binarnej dlatego też liczby to 0.0 oznaczają kolor czarny lub 1.0 dla koloru białego. Następnie po 256 liczbach opisujących wektor uczący następuje 10 cyfr 0 lub 1, które opisują etykietę obrazka. Przykładowo następujący ciąg:

1 0 0 0 0 0 0 0 0 0

daje nam etykietę równą 0 zasada jest następująca:

0 1 0 0 0 0 0 0 0 0 // oznacza 1
0 0 0 0 0 1 0 0 0 0 // oznacza 5
0 0 0 0 0 0 0 0 0 1 // oznacza 9

Z racji, że rozpoznajemy cyfry od 0 - 9 potrzebne nam 10 cyfr do opisu wszystkich etykiet przy czym pozycja 1 wśród tych dziesięciu ostatnich cyfr każdej linii determinuje wartość etykiety.

Zapamiętywanie etykiet

Pozostaje jeszcze kwestia zapamiętania naszych etykiet i wektorów uczących w programie. Użyjemy do tego macierzy. Macierze będą początkowo miały wymiary 1x256 dla wektorów uczących oraz 1x1 dla etykiet uczących. Wektory mają reprezentować obrazki 16x16, a etykiety są zapisywane jako pojedyncze liczby float. Tworzymy więc nasze macierze.

Stała CV_32FC1 oznacza, że nasze macierze będą przechowywały informacje będące 32-bitow. liczbami float i będą miały 1 kanał. W naszej pętli wczytującej będziemy przy każdym nowym wczytywanym obrazku zwiększać liczbę wierszy macierzy z wektorami i etykietami o 1, aby pomieścić kolejne wektory uczące, gdyż będą one zapisane w następujących po sobie wierszach macierzy, ta sama sytuacje jest dla etykiet. Opisane operacje realizują metody.

Na koniec wczytywania należy jeszcze usunąć ostatni wiersz obu macierzy, gdyż ze względu na konstrukcję pętli dane w nim znajdujące się są niepoprawne.

Trenowanie klasyfikatora

Trenowanie klasyfikatora sprowadza się do wywołania pojedynczej metody. Parametrami są wektory uczące zapisane wierszami w macierzy oraz odpowiadające i etykiety. Wypisujemy również największą znalezioną przez algorytm wartość K, którą potem wykorzystamy w drugiej fazie, odgadywania liczb.

W pętli głównej tworzymy obraz, na którym będzie można później wyrysować liczbę:

Proces pobierania danych przy rysowaniu następuje w poniższej pętli:

Dalej mamy fragment wykrywający kontur narysowanej liczby. Na podstawie informacji o konturze tworzony jest prostokąt otaczający liczbę. Później następuje wycięcie obszaru z liczbą.

Na koniec skalujemy rozmiary obrazu liczby do rozmiarów obrazów uczących czyli 16x16:

Następnie wykorzystujemy funkcję, która przetwarza obraz na wektor. Jej działanie jest bardzo proste i zostało już wcześniej wytłumaczone. Kolejne wiersze obrazu są zapisywane jeden po drugim w tworzonym wektorze.

Dzielenie przez liczbę 255.0 jest potrzebne po to by w wektorze dane były zapisane w postaci liczb 0.0 lub 1.0 czyli tak jak dla wektorów uczących wczytywanych z bazy danych. Zastosowanie indeksu i * rows + j pozwala zachować odpowiednie przesunięcie danych z obrazka w wektorze.

W programie znajduje się też definicja funkcji, której działanie jest odwrotne do opisanej wyżej, a więc zamienia wektor na obraz. Parametrami są macierz z wektorami ułożonymi według wierszy, obraz wyjściowy, liczba wierszy i kolumn oraz numer wektora, który właściwie oznacza nr wiersza w macierzy z wektorami, który chcemy przetworzyć na obraz.

Funkcję można użyć np. do przetwarzania wektorów uczących na obrazy, aby wyświetlając je sprawdzić czy wszystko się poprawnie wczytuje i jak wyglądają dane uczące.

Następnie mamy metodę, którą dokonujemy sklasyfikowania narysowanej liczby. Podajemy dla niej wektor z danymi do sklasyfikowania, wartość liczby K oraz adres macierzy, do której zostaną zwrócone etykiety.

Możemy naraz przekazać większą liczbę wektorów do sklasyfikowania, umieszczamy je wówczas wierszami w macierzy testvector.

Efekt działania programu widoczny jest na poniższych zrzutach ekranu:

Podsumowanie

To by było na tyle. W załączniku zamieszczam jeszcze wykonalny program, aby Ci którzy nie mają zainstalowanego OpenCV mogli sprawdzić działanie omawianego algorytmu. Nie jest on idealny, ale działa całkiem dobrze. Aby móc uruchomić program należy mieć zainstalowany pakiet MinGW.

« Poprzedni artykuł z serii

Załączniki

OpenCV, pismo, programowanie, rozpoznawanie

Trwa ładowanie komentarzy...