Машинное обучение распознавать поворот лица (Machine learning to recognize face turn)

Автор: | 02.11.2019

Введение
Постановка задачи
Оценка возможностей распознавать поворот лица через машинное обучение
Программный код (вариант 1)
Программный код (вариант 2)
Адекватность и оптимальность нейронной сети для распознавания поворота головы
Полезные ссылки

Введение

Как не зная алгоритм, а просто имея данные и ответ, получить из них алгоритм?  Альтернатива традиционному алгоритмическому подходу – нейронная сеть, которую можно обучать предсказывать результат по выборке для обучения.

На рисунке демонстрируются 2 подхода к решению задачи пересчета температуры из Цельсия в Фаренгейт.

При создании среды для обучения мы всегда должны помнить, что наличие хорошего показателя с набором данных для обучения не обязательно означает, что модель хорошо адаптируется к новым данным. Конечная цель любой модели машинного обучения — делать надежные прогнозы для новых, неизвестных данных. 

Весь смысл машинного обучения состоит в том, чтобы предсказать вещи, основанные на образцах и других факторах, с которыми он обучался.

Машинное обучение проще, чем традиционное программирование. В процессе программирования вам необходимо учитывать каждый случай в исходных данных, адаптировать под него логику программы и правила. В то же самое время, используя машинное обучение вы, по сути, тренируете компьютер  самому находить правила на основе примеров.

Постановка задачи

На рисунке ниже отображен результат запуска программы, описанной в статье Обнаружение лица и выделение характерных точек.

При повороте головы  изменяются размеры сторон треугольника, вершины которого соединяют ключевые точки лица. Эти данные можно использовать для распознавания поворота головы — вправо (Right) и влево (Left).

При алгоритмическом подходе к решению такой задачи достаточно выполнять проверку, какая из сторон больше. Если стороны равны, то положение промежуточное (Center).

Попробуем решить эту задачу через обучение нейронной сети (персептрона).

На вход сети подается пара сигналов (признаков лица), которые представляют нормализованные расстояния от точек глаз до точки носа. Под нормализацией здесь понимается то, что признаки-расстояния отнесены к расстоянию между глазами. Нормализация обеспечивает независимость признаков от удаленности камеры.

Каждая пара сигналов (X) берется с кадров (frames) от веб-камеры. При обучении НС сигнал на  выходе (Y) принимается:

  • 1 — при положении головы Right;
  • 0  — при положении головы Left.

При тестировании уже обученной сети сигнал на выходе должен быть между 1 и 0:

  • Y> 0.9 — если положение головы Right;
  •  Y< 0.1 — если положение головы Left;
  • 0.1 < Y <0.9 —  если положение головы Center

Оценка возможностей распознавать поворот лица через машинное обучение

Для оценки возможности распознавать поворот лица через машинное обучение выполним следующее:

  1. Получим входные данные (пара нормализованных расстояний от точки носа до точек глаз) для нескольких кадров (Frames) с поворотом головы вправо и влево, используя программу из статьи  Обнаружение лица и выделение характерных точек в реальном времени
  2. Обучим и протестируем нейронную сеть на основе этих данных, используя программу из статьи Простейшие нейронные сети на Python в Visual Studio.

Входные данные для обучения:

  • для поворота головы направо

В левом углу отображаются текущие данные (размеры сторон треугольника), которые могут подаваться на вход нейронной сети. Эти же данные для каждого фрейма отображаются в окне консоли Из них выбираем для обучения НС наиболее разнообразные.

  • для поворота головы налево

Входные данные для тестирования:

  • для поворота головы направо

  • для поворота головы налево

Ниже представлены отобранные данные для обучения и тестирования НС

В первой строке указаны входные данные, ниже — ожидаемые выходные сигналы.

Программный код для обучения и тестирования приводится ниже.

import numpy as np
# обучающая выборка входных и выходных данных
X = np.array([[0.838,0.661],[1.095,0.679],[0.958,0.701],
              [0.604,1.060],[0.695,1.082],[0.532,1.115]])
y = np.array([[0,0,0,1,1,1]]).T # T-транспонирование матрицы
# инициализация случайных весов (от -1 до +1) синапсов
np.random.seed(1)
synapses_hidden = 2 * np.random.random((2,3)) - 1 # 2*3 - hidden слой 
synapses_output = 2 * np.random.random((3,1)) - 1 # 3*1 - output слой 
# обучение сети - цикл из 10000 повторений
for j in range(10000):
    # Входной слой ( 2 входа )
    l0 = X
    # Скрытый слой ( 3 скрытых нейрона )
    l1 = 1 / (1 + np.exp(-(l0.dot(synapses_hidden))))
    # Выходной слой ( 1 выходной нейрон )
    l2 = 1 / (1 + np.exp(-(l1.dot(synapses_output))))
    # вычисляем ошибку (используем дельта-правило)
    l2_delta = (y - l2) * (l2 * (1 - l2))
    # получаем ошибку на скрытом слое (используем дельта-правило)
    l1_delta = l2_delta.dot(synapses_output.T) * (l1 * (1 - l1))
    # корректируем веса от скрытых нейронов к выходу
    synapses_output += l1.T.dot(l2_delta)
    # корректируем веса от входов к скрытым нейронам
    synapses_hidden += l0.T.dot(l1_delta)
# Печать сигналов на выходе после последнего цикла обучения
print('Результаты обучения')
print(l2)

# Тестирование с новыми данными на входе
X = np.array([[0.982,0.680],[1.030,0.700],[0.885,0.690],
              [0.685,1.064],[0.751,0.937],[0.797,0.833]])
# Входной слой ( 2 входа )
l0 = X
# Скрытый слой ( 3 скрытых нейрона ) 
l1 = 1 / (1 + np.exp(-(l0.dot(synapses_hidden))))
# Выходной слой ( 1 выходной нейрон )
l2 = 1 / (1 + np.exp(-(l1.dot(synapses_output))))
print('Результаты тестирования')
print(l2)

Результаты запусков программы с различным количеством циклов обучения (10000 и 100000):

Длительность обучения 1 сек и 5 сек.

Выделенные красным цветом числа не соответствует ожидаемому результату (далеки от единицы). Однако, это можно объяснить тем, что этот результат получен для входных сигналов  [0.797,0.833] , которые определяют положение головы, близкое к центральному (Center).

Для обучения использовалось данные всего лишь с  трех кадров. Хотя результаты тестирования достаточно хорошие, однако нет гарантии, что модель будет адаптироваться  ко всему многообразию данных в пределах поставленной задачи, поскольку для тестирования использовалось мало кадров.

Вводить данные вручную и тестировать лишь по цифрам — неэффективно. Возникает необходимость разработать программу, в которой реализуется автоматическое считывание данных с фреймов от веб-камеры. Интерфейс такой программы описывается ниже.

Программный код (вариант 1)

При запуске программы появляется диалоговое окно. При нажатии на кнопку Start  запускается процесс обучения — если включена опция Training или процесс распознавания —  если включена опция Recognition. Перед началом обучения (еще до включения веб-камеры) появляется msgbox с вопросом: «Right side?». При положительном ответе на выход нейронной сети будет подаваться значение сигнала 1, в противном случае — 0.

Далее включается веб-камера и начинается сбор данных для обучение НС на основе признаков лица (нормализованных расстояний от точки носа до точек глаз). Лицо необходимо поворачивать перед камерой, чтобы получать на вход НС разнообразные данные. Камера автоматически выключается после снятия данных с 20 кадров.

Процесс обучения запускается кнопкой Train. Данные перед этим группируются должным образом.

Желательно, чтобы чередовались данные фреймов с сигналом на выходе 1 и  сигналом на выходе 0 — [1 0 1 0 1 0 …]. Иначе может произойти не обучение НС, а ее переучивание с одного состояния в другое .

Для тестирования корректности обучения включается опция Recognition и нажимается кнопка Start. Тест считается успешным, если при повороте головы появляется соответствующее сообщение.

Программный код приводится ниже:

from tkinter import *
from tkinter import filedialog
from tkinter import messagebox as mb
import numpy as np
import cv2
from mtcnn.mtcnn import MTCNN

def Video(train,y):
 global n, nk, side
 detector = MTCNN()
 video_capture = cv2.VideoCapture(0)
 while True:
    n = n + 1 #счетчик кадров
    # Capture frame-by-frame
    ret, frame = video_capture.read()
    image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    result = detector.detect_faces(image)

    # Result is an array with all the bounding boxes detected.
    bounding_box = result[0]['box']
    keypoints = result[0]['keypoints']

    cv2.rectangle(frame,
      (bounding_box[0], bounding_box[1]),
      (bounding_box[0]+bounding_box[2], bounding_box[1] + bounding_box[3]),
      (0,155,255),
      2)
    cv2.circle(frame,(keypoints['left_eye']), 3, (0,155,255), 2)
    cv2.circle(frame,(keypoints['right_eye']), 2, (0,155,255), 2)
    cv2.circle(frame,(keypoints['nose']), 3, (0,155,255), 2)
    cv2.circle(frame,(keypoints['mouth_left']), 3, (0,155,255), 2)
    cv2.circle(frame,(keypoints['mouth_right']), 3, (0,155,255), 2)

    cv2.line(frame,(keypoints['left_eye']),keypoints['right_eye'], (0,0,255), 1)
    cv2.line(frame,(keypoints['left_eye']),keypoints['nose'], (0,255,0), 2)
    cv2.line(frame,(keypoints['right_eye']),keypoints['nose'], (255,0,0), 2)
 
    dX = keypoints['right_eye'][0] - keypoints['left_eye'][0]
    dY = keypoints['right_eye'][1] - keypoints['left_eye'][1]
    dist_norm = np.sqrt((dX ** 2) + (dY ** 2))
 
    dX = keypoints['left_eye'][0] - keypoints['nose'][0]
    dY = keypoints['left_eye'][1] - keypoints['nose'][1]
    dist_left = np.sqrt((dX ** 2) + (dY ** 2))

    dX = keypoints['right_eye'][0] - keypoints['nose'][0]
    dY = keypoints['right_eye'][1] - keypoints['nose'][1]
    dist_right = np.sqrt((dX ** 2) + (dY ** 2))

    # Normalized Features-distances
    input_left = dist_left/dist_norm
    input_right = dist_right/dist_norm

    X = np.array([[input_left,input_right]])
    if train == True: 
       if n <= nk:
           Training_data(X,y,side)
           print(n)
       else:
           print('Считывание входных данных завершилось')
           side = False
           break
    else:
      Recognition(X,frame,bounding_box) 
 
    #print(result)
    # Display the resulting frame
    cv2.imshow('Video', frame)

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
 # When everything is done, release the capture
 video_capture.release()
 cv2.destroyAllWindows()

def Training_data(X,y,side):
   global n
   global X_data
   global y_data
   global X_data_new
   global y_data_new
   if side == True:
      # Формирование данных для первой стороны лица [1,1,1,1...]
      X_data = np.concatenate((X_data, X))
      y_data = np.concatenate((y_data, y))
      #print(X_data)
      #print(y_data)
   else:
       # Добавление данных для другой стороны лица c их перемешиванием [1,0,1,0...]
      X_data_new = np.concatenate((X_data_new, X_data[[n - 1]], X))
      y_data_new = np.concatenate((y_data_new, y_data[[n - 1]], y))
      #print(X_data_new)
      #print(y_data_new)


def Training(ev):
 global synapses_hidden
 global synapses_output
 global X_data
 global y_data
 global X_data_new
 global y_data_new
 X_data = X_data_new
 y_data = y_data_new
 print('Выборка для обучения') 
 X = X_data
 y = y_data
 print(X)
 print(y)
 print('Обучение началось')

 # обучение сети - цикл из 10000 повторений
 for j in range(10000):
   # Входной слой ( 2 входа )
   l0 = X
   # Скрытый слой ( 3 скрытых нейрона )
   l1 = 1 / (1 + np.exp(-(l0.dot(synapses_hidden))))
   # Выходной слой ( 1 выходной нейрон )
   l2 = 1 / (1 + np.exp(-(l1.dot(synapses_output))))
   # вычисляем ошибку (используем дельта-правило)
   l2_delta = (y - l2) * (l2 * (1 - l2))
   # получаем ошибку на скрытом слое (используем дельта-правило)
   l1_delta = l2_delta.dot(synapses_output.T) * (l1 * (1 - l1))
   # корректируем веса от скрытых нейронов к выходу
   synapses_output += l1.T.dot(l2_delta)
   # корректируем веса от входов к скрытым нейронам
   synapses_hidden += l0.T.dot(l1_delta)
 print('Обучение закончилось')

def Recognition(X,frame,bounding_box):
    global synapses_hidden
    global synapses_output
    global n
 
    # Входной слой ( 2 входа )
    l0 = X
    # Скрытый слой ( 3 скрытых нейрона )
    l1 = 1 / (1 + np.exp(-(l0.dot(synapses_hidden))))
    # Выходной слой ( 1 выходной нейрон )
    l2 = 1 / (1 + np.exp(-(l1.dot(synapses_output))))
    print(n)
    print('Результат распознавания')
    print(l2)
    if l2 >= 0.9:   # if l2[0] >= 0.9:
           # YES RIGHT
           cv2.putText(frame, str('RIGHT'), (bounding_box[0], bounding_box[1]), 
                   cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 0), 2, lineType=cv2.LINE_AA)
    else: 
         if l2 >= 0.1: 
            # NO LEFT
           cv2.putText(frame, str('CENTER'), (bounding_box[0], bounding_box[1]), 
                   cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 2, lineType=cv2.LINE_AA)
         else:
           cv2.putText(frame, str('LEFT'), (bounding_box[0] + bounding_box[2], bounding_box[1]), 
                   cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 0, 0), 2, lineType=cv2.LINE_AA)

def Quit(ev):
 global root
 root.destroy()
 
def Start(ev):
 global n
 n = 0
 value = option.get()
 y = np.array([[0]])
 if value == 1:
      train = True 
      answer = mb.askyesno(title="Question", message="Right side?") 
      if answer == True:
        y = np.array([[1]])
 else:
      # Recognition
      train = False
 Video(train,y)

# ОСНОВНАЯ ПРОГРАММА
# инициализация глобальных переменных
 # инициализация случайных весов (от -1 до +1) синапсов
np.random.seed(1)
synapses_hidden = 2 * np.random.random((2,3)) - 1 # 2*3 - hidden слой 
synapses_output = 2 * np.random.random((3,1)) - 1 # 3*1 - output слой
side = True # True - левая сторона, False - правая сторона
n = 0 # текущий номер фрейма
nk = 20 # Число фреймов для обучения за одно включение камеры
X_data = np.array([[0.0,0.0]])
y_data = np.array([[0.0]])
X_data_new = np.array([[0.0,0.0]])
y_data_new = np.array([[0.0]])
# Удаляются первые элементы из массива
X_data = np.delete(X_data,0,axis=0)
y_data = np.delete(y_data,0,axis=0)
X_data_new = np.delete(X_data_new,0,axis=0)
y_data_new = np.delete(y_data_new,0,axis=0)

# Interface
root = Tk()

panelFrame = Frame(root, height = 60, bg = 'gray')
textFrame = Frame(root, height = 40, width = 400)

panelFrame.pack(side = 'top', fill = 'x')
textFrame.pack(side = 'bottom', fill = 'both', expand = 1)

option = IntVar()
R1 = Radiobutton(panelFrame, text="Training      ", value = 1, var = option)
R1.pack()
R2 = Radiobutton(panelFrame, text="Recognition", value = 2, var = option)
R2.pack()
value = option.set(1)

startBtn = Button(panelFrame, text = 'Start')
quitBtn = Button(panelFrame, text = 'Quit')
trainBtn = Button(panelFrame, text = 'Train')

startBtn.bind("<Button-1>", Start)
trainBtn.bind("<Button-1>", Training)
quitBtn.bind("<Button-1>", Quit)

startBtn.place(x = 50, y = 10, width = 40, height = 40)
trainBtn.place(x = 270, y = 10, width = 40, height = 40)
quitBtn.place(x = 340, y = 10, width = 40, height = 40)

root.mainloop()

Программный код (вариант 2)

Эта  программа позволяет сохранять для обучения данные с выбранных кадров. С помощью нее проводились исследования на Адекватность и оптимальность нейронной сети.

При запуске программы, она сразу же находится в режиме тестирования, результат которого отображается в правом верхнем углу. Однако, до обучения результат некорректный. В левом углу отображаются данные (размеры сторон треугольника), которые подаются на вход нейронной сети.

Входные данные  вводятся в выборку для обучения при нажатии кнопки Save.  При этом сигнал на выходе (1 или 0) задается выбором соответствующей Radio-button   опции  (Right или Left). После набора в списках достаточного количества данных запускается процедура обучения кнопкой Train. После завершения обучения результат тестирования отображается в правом верхнем углу уже корректно.

Диалог отслеживается в окне консоли:

Программный код приводится ниже:

import time
import sys
from tkinter import *
from tkinter import messagebox as mb
import tkinter as tk
import numpy as np
import cv2
from PIL import Image, ImageTk
from mtcnn.mtcnn import MTCNN
from numpy import exp, array, random, dot

def opencvToTk(frame):
    #Convert an opencv image to a tkinter image, to display in canvas
    rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    pil_img = Image.fromarray(rgb_image)
    tk_image = ImageTk.PhotoImage(image=pil_img)
    return tk_image

def mousePressed(event, data):
    pass

def keyPressed(event, data):
    if event.keysym == "q":
        data.root.destroy()
    if event.keysym == "w":
        Save()
    pass

def timerFired(data):
    pass

def Save(event):
 global option
 global X
 global y
 global input_left
 global input_right
 l = input_left
 r = input_right
 X = np.concatenate((X, [[l,r]]))
 print ("Сигналы на входе")
 print (X)
 print ("Сигналы на выходе")
 value = option.get()
 if value == 0:
   y = np.concatenate((y, [[1]]))
   print (y)
 else:
   y = np.concatenate((y, [[0]]))
   print (y)
 print ("Следующий кадр или переходите к обучению (Train)")

def Teach(event): 
    global synapses_hidden
    global synapses_output
    global X
    global y
    print ( 'Обучение началось') 
    # обучение сети - цикл из 10000 повторений
    for j in range(10000):
        # Входной слой ( 2 входа )
        l0 = X
        # Скрытый слой ( 3 скрытых нейрона )
        l1 = 1 / (1 + np.exp(-(l0.dot(synapses_hidden))))
        # Выходной слой ( 1 выходной нейрон )
        l2 = 1 / (1 + np.exp(-(l1.dot(synapses_output))))
        # вычисляем ошибку (используем дельта-правило)
        l2_delta = (y - l2) * (l2 * (1 - l2))
        # получаем ошибку на скрытом слое (используем дельта-правило)
        l1_delta = l2_delta.dot(synapses_output.T) * (l1 * (1 - l1))
        # корректируем веса от скрытых нейронов к выходу
        synapses_output += l1.T.dot(l2_delta)
        # корректируем веса от входов к скрытым нейронам
        synapses_hidden += l0.T.dot(l1_delta)
    print ( 'Обучение закончилось')
    # Печать сигналов на выходе после последнего цикла обучения
    print ( 'Сигналы на выходе после последнего цикла обучения')
    print(l2)

def Test(data,l,r):
    global synapses_hidden
    global synapses_output
    l0 = np.array([l,r])
    # Скрытый слой ( 3 скрытых нейрона )
    l1 = 1 / (1 + np.exp(-(l0.dot(synapses_hidden))))
    # Выходной слой ( 1 выходной нейрон )
    l2 = 1 / (1 + np.exp(-(l1.dot(synapses_output))))
    #print('Распознавание')
    #print(l)
    #print(r)
    if l2 > 0.9:
            cv2.putText(data.frame, 'RIGHT', (500,50), 
                    cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 0), 2,lineType=cv2.LINE_AA) 
    else: 
         if l2 >= 0.1:
            cv2.putText(data.frame, 'CENTER', (500,50), 
                     cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 2, lineType=cv2.LINE_AA) 
         else:
           cv2.putText(data.frame, 'LEFT', (500,50), 
                    cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 0, 0), 2, lineType=cv2.LINE_AA) 
 
def cameraFired(data):
    global input_left
    global input_right
 
    data.frame = cv2.GaussianBlur(data.frame, (11, 11), 0)
    image = cv2.cvtColor(data.frame, cv2.COLOR_BGR2RGB)
    result = detector.detect_faces(image)
    try:
    # Capture frame-by-frame
    # Result is an array with all the bounding boxes detected.
        bounding_box = result[0]['box']
        keypoints = result[0]['keypoints']
        cv2.rectangle(data.frame,
          (bounding_box[0], bounding_box[1]),
          (bounding_box[0]+bounding_box[2], bounding_box[1] + bounding_box[3]),
          (0,155,255),
          2)
        cv2.circle(data.frame,(keypoints['left_eye']), 3, (0,155,255), 2)
        cv2.circle(data.frame,(keypoints['right_eye']), 2, (0,155,255), 2)
        cv2.circle(data.frame,(keypoints['nose']), 3, (0,155,255), 2)
        cv2.circle(data.frame,(keypoints['mouth_left']), 3, (0,155,255), 2)
        cv2.circle(data.frame,(keypoints['mouth_right']), 3, (0,155,255), 2)

        cv2.line(data.frame,(keypoints['left_eye']),keypoints['right_eye'], (0,0,255), 1)
        cv2.line(data.frame,(keypoints['left_eye']),keypoints['nose'], (0,255,0), 2)
        cv2.line(data.frame,(keypoints['right_eye']),keypoints['nose'], (255,0,0), 2)
 
        dX = keypoints['right_eye'][0] - keypoints['left_eye'][0]
        dY = keypoints['right_eye'][1] - keypoints['left_eye'][1]
        dist_norm = np.sqrt((dX ** 2) + (dY ** 2))
 
        dX = keypoints['left_eye'][0] - keypoints['nose'][0]
        dY = keypoints['left_eye'][1] - keypoints['nose'][1]
        dist_left = np.sqrt((dX ** 2) + (dY ** 2))

        dX = keypoints['right_eye'][0] - keypoints['nose'][0]
        dY = keypoints['right_eye'][1] - keypoints['nose'][1]
        dist_right = np.sqrt((dX ** 2) + (dY ** 2))
        # Normalized Features-distances
        input_left = dist_left/dist_norm
        input_right = dist_right/dist_norm
        # Постоянно идет тестирование (даже еще до обучения)
        Test(data, input_left,input_right)
 
        cv2.putText(data.frame, str(input_left), (50,50), 
            cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 0), lineType=cv2.LINE_AA) 
        cv2.putText(data.frame, str(input_right), (50,100), 
            cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 0, 0), lineType=cv2.LINE_AA)
 
    except IndexError:
     gotdata = 'null'

def drawCamera(canvas, data):
    data.tk_image = opencvToTk(data.frame)
    canvas.create_image(data.width / 2, data.height / 2, image=data.tk_image)

def redrawAll(canvas, data):
    drawCamera(canvas, data)


def run(width=300, height=300):
    global option
    #class Struct(object): pass
    #data = Struct()
    data.width = width
    data.height = height
    data.camera_index = 0

    data.timer_delay = 100 # ms
    data.redraw_delay = 50 # ms
 
    # Initialize the webcams
    camera = cv2.VideoCapture(data.camera_index)
    data.camera = camera
 
    # Make tkinter window and canvas
    data.root = Tk()
 
    canvas = Canvas(data.root, width=data.width, height=data.height)
    canvas.pack()

    option = BooleanVar()
    R1 = Radiobutton(data.root, text="Right", value = 0, var = option)
    #R1.pack()
    R1.place(x = 50, y = 10, width = 50, height = 20)
    R2 = Radiobutton(data.root, text="Left  ", value = 1, var = option)
    #R2.pack()
    R2.place(x = 50, y = 30, width = 50, height = 20)
    option.set(0)

    #print (value)
    btn = Button(data.root, #родительское окно
             text="Save face", #надпись на кнопке
             width=30,height=5, #ширина и высота
             bg="white",fg="black")
    btn.bind("<Button-1>", Save)
    btn1 = Button(data.root, #родительское окно
             text="Train", #надпись на кнопке
             width=30,height=5, #ширина и высота
             bg="white",fg="black")
    btn1.bind("<Button-1>", Teach)
    btn.place(x = 110, y = 10, width = 70, height = 40)
    btn1.place(x = 200, y = 10, width = 70, height = 40)
    # Basic bindings. Note that only timer events will redraw.
    data.root.bind("<Button-1>", lambda event: mousePressed(event, data))
    data.root.bind("<Key>", lambda event: keyPressed(event, data))

    # Timer fired needs a wrapper. This is for periodic events.
    def timerFiredWrapper(data):
        # Ensuring that the code runs at roughly the right periodicity
        start = time.time()
        timerFired(data)
        end = time.time()
        diff_ms = (end - start) * 1000
        delay = int(max(data.timer_delay - diff_ms, 0))
        data.root.after(delay, lambda: timerFiredWrapper(data))

    # Wait a timer delay before beginning, to allow everything else to
    # initialize first.
    data.root.after(data.timer_delay, 
        lambda: timerFiredWrapper(data)) 

    def redrawAllWrapper(canvas, data):
        start = time.time()

        # Get the camera frame and get it processed.
        _, data.frame = data.camera.read()
        cameraFired(data)

        # Redrawing code
        canvas.delete(ALL)
        redrawAll(canvas, data)

        # Calculate delay accordingly
        end = time.time()
        diff_ms = (end - start) * 1000

        # Have at least a 5ms delay between redraw. Ideally higher is better.
        delay = int(max(data.redraw_delay - diff_ms, 5))

        data.root.after(delay, lambda: redrawAllWrapper(canvas, data))

    # Start drawing immediately
    data.root.after(0, lambda: redrawAllWrapper(canvas, data))

    # Loop tkinter
    data.root.mainloop()

    # Once the loop is done, release the camera.
    print("Releasing camera!")
    data.camera.release()

# ОСНОВНАЯ ПРОГРАММА
detector = MTCNN()
class Struct(object): pass
data = Struct()
var_1 = 0
X = np.array([[0.0,0.0]])
y = np.array([[0.0]])
# Удаляются первые элементы из массива
X = np.delete(X,0,axis=0)
y = np.delete(y,0,axis=0)

#y = np.array([])
input_left = 0.6
input_right = 1.0
# инициализация случайных весов (от -1 до +1) синапсов
np.random.seed(1)
synapses_hidden = 2 * np.random.random((2,3)) - 1 # 2*3 - hidden слой 
synapses_output = 2 * np.random.random((3,1)) - 1 # 3*1 - output слой
print("Подготовьте данные для обучения!")

if __name__ == "__main__":
    run(700, 600)

Далее см. статью  Адекватность и оптимальность нейронной сети для распознавания поворота головы

 

Полезные ссылки:

 

Автор: Николай Свирневский