DSZI_2020_Projekt/rozpoznawanie_banknotów_Kinga_Molik.md
2020-05-18 15:27:23 +02:00

9.2 KiB

Raport przygotowała: Kinga Molik
Raportowany okres: 10 maja - 17 maja 2020
Niniejszy raport poświęcony jest przekazaniu informacji na temat stanu mini-projektu indywidualnego w ramach projektu grupowego realizowanego na przedmiot Sztuczna Inteligencja w roku akademickim 2019/2020.

Tematem realizowanego projektu indywidualnego jest rozpoznawanie nominałów banknotów oraz kart płatniczych. Do rozwiązania problemu zostały wykorzystane sieci neuronowe, biblioteki:

  • Keras (backend: Tensorflow),
  • Pillow

Uczenie modelu

Dane wejściowe:

Do utworzenia modelu przygotowane zostały zdjęcia przedstawiające polskie banknoty oraz różne karty płatnicze. W folderach train i examine znajdują się foldery:

  • 10,
  • 20,
  • 50,
  • 100,
  • 200,
  • 500

Odpowiadające nominałom polskich banknotów (zdjęć jest odpowiednio: po 35 i po 10) oraz:

  • cards,

w których umieszczone zostały zdjęcia kart bankowych (80 oraz 20).

Wszystkie zdjęcia są jednego typu (rozszerzenie .png), wszystkie są nasycone kolorami (nie ma zdjęć czarno-białych) oraz moją różne wielkości, co będzie normalizowane w kodzie.

Proces uczenia:

Inicjalizacja sieci neuronowej:

model = Sequential()
model.add(Conv2D(32, (2, 2), input_shape=input_shape))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(32, (2, 2)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(64, (2, 2)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Flatten())
model.add(Dense(64))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(7))
model.add(Activation('sigmoid'))
  • Conv2D - warstwa do konwulsji obrazu w wiele obrazów - poprzez mnożenie ich przez macierz (2,2) .
  • Activation('relu') - funkcja aktywacji (negatywne wyniki są zerowane).
  • MaxPooling2D - funkcja zmiany rozdzielczości zdjęcia (raz dodana używana jest również przez kolejne warstwy).
  • Flatten - spłaszcza macierze do wektorów.\
  • Dense(64) - używana do łącznia całego modelu (jest ukrytą warstwą).\
  • Dropout - używana, by uniknąć nadmiernego dopasowania bazy danych.\
  • Dense(7) - warstwa wyjściowa zawierająca 7 neuronów (bo tyle mamy różnych kategorii) - decyduje o przynależności danego zdjęcia do kategorii.
  • Activation('sigmoid')- normalizuje wartości do przedziału [0,1] (ponieważ mamy tu do czynienia z prawdopodobieństwem).

Kompilacja modelu :

model.compile(loss='categorical_crossentropy',  
            optimizer='rmsprop',  
            metrics=['accuracy'])

Mamy 7 kategorii, które chcielibyśmy rozróżniać, dlatego zastosujemy funkcję straty categorical_crossentropy, do optymalizacji wykorzystamy rmsprop, a parametr metrics = ['accuracy'] pomaga nam sprawdzać dokładność nauki.

Generatory danych:

train_datagen = ImageDataGenerator(        
    rotation_range=45,        
    width_shift_range=0.3,        
    height_shift_range=0.3,        
    rescale=1./256,        
    shear_range=0.25,        
    zoom_range=0.1,        
    horizontal_flip=True)

examine_datagen = ImageDataGenerator(rescale=1. / 256)

train_generator = train_datagen.flow_from_directory(  
                  train_data_dir,  
                target_size=(img_width, img_height),  
                  batch_size=batch_size,  
                class_mode='categorical')  

examine_generator = examine_datagen.flow_from_directory(  
                    examine_data_dir,  
                    target_size=(img_width, img_height),  
                    batch_size=batch_size,  
                    class_mode='categorical')

train_datagen - generator obrazów z bazy danych uczących. examine_datagen - generator obrazów z bazy danych sprawdzających. Generujemy obrazy, które są niewielką modyfikacją oryginalnych obrazów z folderów train i examine w celu zwiększenia ich ilości. Czym większa baza wiedzy, tymlepsze wyniki podczas sprawdzania. train_generator oraz examine_genertor zaczytują nam pliki z danych folderów (train, examine), normalizuje wielkość zdjęć (img_width, img_height = 256, 256), przypisujemy batch_size = 16 oraz class_mode='categorical', ponieważ mamy 7 różnych klas.

model.fit_generator(  
       train_generator,  
steps_per_epoch=nb_train_samples // batch_size,  
epochs=epochs,  
validation_data=examine_generator,  
validation_steps=nb_examine_samples // batch_size)

model.save_weights('model_payment.h5')

model.fit_generator - generujemy model danych z danymi: - train_generator - genetarator danych do nauki - steps_per_epoch - liczba kroków w danej epoce (obliczana przez podzielenie ilości danych uczących (290) przez batch_size (16)) - validation_data - generator danych do sprawdzania - validation_steps - liczna kroków podczas sprawdzania (ilość danych sprawdzających (80) przez batch_size(16))

' model_payment.h5 ' - plik, do którego zapisywane są wagi modelu.

Integracja z projektem

Na początku zostaje stworzony model sekwencyjny, na którym zostaje wywołana funkcja recognizeCash() oraz załadowany zostaje wcześniej stworzony model z wagami.

pln_classify = Sequential()
recognizeCash(pln_classify)
pln_classify.load_weights('Kinga/model_payment.h5')

Funkcja do rozpoznawania banknotów uruchomiona zostaje po wybraniu na ekranie startowym przycisku Rozpoznawanie banknotów. W funkcji payment() mamy podany przykładowy stan początkowy restauracji, czyli dane dotyczące klientów, tj. numer stolika, zamówione dania. Zakładamy, że każdy z klientów zakończył już konsumpcje oraz chciałby zapłacić. Każdy klient ma podany budżet (zakładamy tu, że budget >0 oznacza ilość posiadanej gotówki, a budget=0 oznacza chęć zapłaty kartą.) Agent powinien rozpoznać jakim banknotem zapłacił klient oraz wydać mu resztę. W przypadku zapłaty kartą, agent oznajmia, że restauracja nie przyjmuje płatności kartą. By to uschematyzować, została dodana funkcja w klasie Client:

def pay(self, image):  
   load = Image.open(image)  
   load.show()

Po podejściu do stolika, agent odczytuje cenę zamówionej przez klienta potrawy, zostaje wywołana funkcja decide() (z atrybutami client - dany klient, image - zdjęcie z bazy ćwiczeniowej, dish - potrawa klienta), w której zostaje wyświetlone zdjęcie z płatnością klienta oraz sprawdzamy w niej, do której klasy należy ów zdjęcie. Nasze sprawdzenie wykonujemy poprzez przesłanie znormalizowanego zdjęcia do modelu, który wcześniej zainicjowaliśmy.

def decide(client, image, dish):  
   img_width, img_height = 256, 256  
 
   client.pay(image)  
 
   test_image = load_img(image, target_size=(img_width, img_height))  
   test_image = img_to_array(test_image)  
   test_image = test_image.reshape((1,) + test_image.shape)  
 
   result = pln_model.predict(test_image)  
   print(result)  
 
   if result.tolist() == [[1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]:  
       messagebox.showinfo("Rachunek", "Dziękuję, należy się reszta: " + str(10 - dish.price) + " zł.")  
       return 0  
   elif result.tolist() == [[0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]]:  
       messagebox.showinfo("Rachunek", "Dziękuję, należy się reszta: " + str(100 - dish.price) + " zł.")  
       return 0  
   elif result.tolist() == [[0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]]:  
       messagebox.showinfo("Rachunek", "Dziękuję, należy się reszta: " + str(20 - dish.price) + " zł.")  
       return 0  
   elif result.tolist() == [[0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]]:  
       messagebox.showinfo("Rachunek", "Dziękuję, należy się reszta: " + str(200 - dish.price) + " zł.")  
       return 0  
   elif result.tolist() == [[0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]]:  
       messagebox.showinfo("Rachunek", "Dziękuję, należy się reszta: " + str(50 - dish.price) + " zł.")  
       return 0  
   elif result.tolist() == [[0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0]]:  
       messagebox.showinfo("Rachunek", "Dziękuję, należy się reszta: " + str(500 - dish.price) + " zł.")  
       return 0  
   else:  
       messagebox.showinfo("Uwaga", "Niestety, nie przyjmujemy zapłaty kartą.")  
       return 1

Zmienna result ma typ numpy.ndarray, dlatego przy użyciu jej w funkcji warunkowej if konwertujemy ją do listy. Nasze zdjęcie może przynależeć do jednej z 7 klas, gdzie 1.0 oznacza właśnie tą przynależność. Klasy zaczytywane są w kolejności alfabetycznej, czyli: 10, 100, 20, 200, 50, 500, cards. A więc lista: 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0 oznacza, że na danym zdjęciu mamy banknot 20 złotowy. W zależności od zdjęcia, mamy komunikat o reszcie lub o braku możliwości płacenia kartą. Po przejściu przykładowo wyznaczonej trasy, aplikacja kończy swoje działanie.