In diesem Artikel möchte ich zeigen, wie man Zahlen mit Python erkennen kann. Das Touchpad vom Laptop benutze ich, um Zahlen zu zeichnen. Dafür verwende ich keras und Tensorflow und einen eigenen Datensatz zum Trainieren und Validieren.
Datensatz erstellen
Für den Datensatz verwende ich folgenden Python Code. Die Ordnerstruktur muss vorher angelegt sein:
import cv2 as cv import pyautogui import numpy as np import keyboard import string import random from time import sleep def find_minmax(pic): # find the min,max of x,y colored = np.where(pic != [0,0,0]) freespace = 10 min_y = np.amin(colored[0]) - freespace max_y = np.amax(colored[0]) + freespace min_x = np.amin(colored[1]) - freespace max_x = np.amax(colored[1]) + freespace return min_y,max_y,min_x,max_x def main(): screenWidth, screenHeight = pyautogui.size() prevmouseX = prevmouseY = 0 dim=(28,28) for n in range(0,10,1): for d in range(0,1,1): print(f'Zahl: {n} \nAnzahl: {d}') # create np array with a size of max windows resolution y = np.zeros([screenHeight,screenWidth,3],dtype=np.uint8) # make array image black pic = np.full_like(y,[0,0,0]) while not keyboard.is_pressed('esc'): # get mouse position mouseX, mouseY = pyautogui.position() #print(mouseX,mouseY) # make the drawing lines bigger for i in range(-5,5): pic[mouseY ,mouseX + i] = [255,255,255] pic[mouseY + i, mouseX] = [255,255,255] sleep(0.1) # find min and max of each axis(x,y) min_y,max_y,min_x,max_x = find_minmax(pic) picresized = pic[min_y:max_y,min_x:max_x] picresized = cv.resize(picresized,dim) randomstring = ''.join(random.choices(string.ascii_lowercase,k=8)) #print(f"create file {n}/{d}{randomstring}.png") #cv.imwrite(f'./{n}/{d}{randomstring}.png',picresized) print(f"create file Test/{d}{randomstring}.png") cv.imwrite(f'./Test/{d}{randomstring}.png',picresized) if __name__ == '__main__': main()
Für jede einzelne Ziffer gibt es mindestens 50 Datensätze, insgesamt habe ich 554 Bilder erstellt. Dabei könnte man die Prozedur in 3 Teile gliedern. Die erste Prozedur besteht darin die Zahl zu zeichnen. Dabei entsteht ein Bild von der Größe des Displays, bei mir 1920×1080. Dieses Bild wird dann reduziert auf den Zahlenbereich, bevor es dann auf eine Größe von 28×28 reduziert wird. Damit hätten wir einen vollständigen Datensatz, mit dem wir unser Model trainieren können
Das Model erstellen und trainieren
Für die Erstellung des Models verwende ich Tensorflow und Keras.Sequential. Dabei bin ich nach dem Tutorial von tensorflow.org vorgegangen.
import numpy as np import matplotlib.pyplot as plt import PIL import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers from tensorflow.keras.models import Sequential import pathlib def visualize_training_results(history,epochs): acc = history.history['accuracy'] val_acc = history.history['val_accuracy'] loss = history.history['loss'] val_loss = history.history['val_loss'] epochs_range = range(epochs) plt.figure(figsize=(8, 8)) plt.subplot(1, 2, 1) plt.plot(epochs_range, acc, label='Training Accuracy') plt.plot(epochs_range, val_acc, label='Validation Accuracy') plt.legend(loc='lower right') plt.title('Training and Validation Accuracy') plt.subplot(1, 2, 2) plt.plot(epochs_range, loss, label='Training Loss') plt.plot(epochs_range, val_loss, label='Validation Loss') plt.legend(loc='upper right') plt.title('Training and Validation Loss') plt.show() data_dir = pathlib.Path('C:/Users/Willkommen/Desktop/handdigits/Modelcreation') image_count = len(list(data_dir.glob('*/*.png'))) print(image_count) batch_size = 32 img_height = 28 img_width = 28 # create dataset train_ds = tf.keras.utils.image_dataset_from_directory( data_dir, validation_split=0.2, subset='training', seed=123, image_size=(img_height, img_width), batch_size=batch_size ) val_ds = tf.keras.utils.image_dataset_from_directory( data_dir, validation_split=0.2, subset='validation', seed=123, image_size=(img_height, img_width), batch_size=batch_size ) class_names = train_ds.class_names print(class_names) # Configure dataset for performance AUTOTUNE = tf.data.AUTOTUNE train_ds = train_ds.cache().shuffle(20).prefetch(buffer_size=AUTOTUNE) val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE) # Standardize the data normalization_layer = layers.Rescaling(1./255) normalized_ds = train_ds.map(lambda x, y: (normalization_layer(x), y)) image_batch, labels_batch = next(iter(normalized_ds)) first_image = image_batch[0] print(np.min(first_image), np.max(first_image)) num_classes = len(class_names) # Create the model model = Sequential([ layers.Rescaling(1./255, input_shape=(img_height, img_width, 3)), layers.Conv2D(16,3, padding='same', activation='relu'), layers.MaxPooling2D(), layers.Conv2D(32,3, padding='same', activation='relu'), layers.MaxPooling2D(), layers.Conv2D(64,3, padding='same', activation='relu'), layers.MaxPooling2D(), layers.Flatten(), layers.Dense(128, activation='relu'), layers.Dense(num_classes) ]) # compile model model.compile( optimizer='adam', loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=['accuracy'] ) model.summary() ## Train the model epochs = 10 history = model.fit( train_ds, validation_data = val_ds, epochs=epochs ) ## Visualize Training results visualize_training_results(history, epochs) #Predict on new data data_dir = pathlib.Path('C:/Users/Willkommen/Desktop/handdigits/Test/0nzzlfidh.png') img = tf.keras.utils.load_img( data_dir, target_size=(img_height, img_width) ) img_array = tf.keras.utils.img_to_array(img), img_array = tf.expand_dims(img_array[0], 0) # Create a batch predictions = model.predict(img_array) score = tf.nn.softmax(predictions[0]) print( "This number most likely belongs to {} with a {:.2f} percent confidence." .format(class_names[np.argmax(score)], 100 * np.max(score)) ) # save model model.save('C:/Users/Willkommen/Desktop/handdigits/ownmodel.h5')
Wir laden die Daten und splitten die Datensätze im Verhältnis 80% zu 20% in Trainings- und Validierungsdaten. Der Klassenname bezieht sich auf den Ordnername, also die Zahlen von 0 – 9. Die Datensätze ziehen wir uns von der Festplatte in den Memory Cache. Dies hat den Vorteil das etwaige I/O auf der Platte nicht zu einem Flaschenhals führen, wenn wir das Model trainieren.
Da der RGB Channel Werte zwischen 0 – 255 hat, normalisieren wir diese noch, sodass ein Wert zwischen 0 und 1 besteht.
Nach der Normalisierung erstellen wir unser Keras Sequential Model mit drei convolution Blöcken. Jeder hat zusätzlich ein max pooling layer.
Zum Kompilieren benutzen wir den Adamoptimizer und die SparseCategoricalCrossentropy loss Funktion.
In insgesamt 10 Epochen trainieren wir unser Model. Zur Visualisierung des Trainingsergebnisses mittels matplotlib gibt es die Methode visualize_training_results.
Um unser gerade neu erstelltes Model zu testen, benutzen wir das Model auf neu erstellte Testdaten.
Nachfolgend könnt ihr die Validierung vom Training sehen. Das Model scheint dabei gut zu performen.
Mit Touchpad zeichnen
Das Model für die Zahlenidentifizierung haben wir nun und wir könnten die Zahlen mit Python erkennen. Fehlt lediglich das Zeichnen mit dem Touchpad. Dafür verwende ich die Bibliothek „keyboard“. Diese ermittelt die aktuelle Mausposition und liefert X,Y Koordinaten. Jene X,Y Koordinaten ändern die Werte im Numpy Array und haben statt dem Wert [0,0,0] den Wert [255,255,255]. Wie auch beim Trainieren des Models, entspricht das erste Bild der Gesamtgröße des Bildschirms. Das zweite ist nur der Zahlenbereich und das dritte und finale Bild, der Zahlenbereich auf die Größe 28,28 reduziert.
Nachdem wir nun das 28×28 Bild-Array haben, rufen wir die Methode predict_num(img) auf. In der Methode wird das Bild noch einmal auf die korrekte Größe gebracht, ehe es dann durch das Model bewertet wird.
import cv2 as cv import keyboard import pyautogui import numpy as np import tensorflow as tf from keras.models import load_model model = load_model('ownmodel.h5') def predict_num(img): # reshape for model compatibility img = img.reshape(1,28,28,3) #predicting the class img = tf.expand_dims(img[0], 0) # Create a batch predictions = model.predict(img) score = tf.nn.softmax(predictions[0]) print( "You draw number {} with a {:.2f} percent confidence." .format(np.argmax(score), 100 * np.max(score)) ) def find_minmax(pic): # find the min,max of x,y colored = np.where(pic != [0,0,0]) freespace = 10 min_y = np.amin(colored[0]) - freespace max_y = np.amax(colored[0]) + freespace min_x = np.amin(colored[1]) - freespace max_x = np.amax(colored[1]) + freespace return min_y,max_y,min_x,max_x def main(): screenWidth, screenHeight = pyautogui.size() prevmouseX = prevmouseY = 0 firstrun = True dim=(28,28) # create np array with a size of max windows resolution y = np.zeros([screenHeight,screenWidth,3],dtype=np.uint8) # make array image black pic = np.full_like(y,[0,0,0]) while not keyboard.is_pressed('esc'): if firstrun: print("Draw your number now and confirm with ESC") firstrun=False # get mouse position mouseX, mouseY = pyautogui.position() # make the drawing lines bigger for i in range(-5,5): pic[mouseY ,mouseX + i] = [255,255,255] pic[mouseY + i, mouseX] = [255,255,255] cv.imwrite('./original.png',pic) # find number and reduce picturesize to a minimum min_y,max_y,min_x,max_x = find_minmax(pic) picresized = pic[min_y:max_y,min_x:max_x] cv.imwrite('./originalcut.png',picresized) # resize image to (28,28) picresized = cv.resize(picresized,dim) cv.imwrite('./resized.png',picresized) #predict predict_num(picresized) if __name__ == '__main__': main()
Testbeispiel
Hier findet ihr ein Beispiel, bei dem ich die 3 über das Touchpad gezeichnet habe. Folgend findet ihr das original Bild, nur den Zahlenbereich und den Zahlenbereich auf 28×28 herunterskaliert. Das Model hat dabei folgendes ausgegeben:
Draw your number now and confirm with ESC
1/1 [==============================] - 0s 417ms/step
You draw number 3 with a 99.95 percent confidence.
Und hier jetzt einmal die Bilder
Den ganzen vollständigen Code findet ihr bei mir auf github: https://github.com/stevieWoW/neuronalnetworkmousepadnumber