Как применить компьютерное зрение для создания собачьего фильтра на основе эмоций в Python 3

_ Автор выбрал Girls Who Code, чтобы получить пожертвование в рамках программы Write for DOnations ._

Вступление

Компьютерное зрение - это область компьютерных наук, целью которой является получение более высокого уровня понимания из изображений и видео. Это поле включает в себя такие задачи, как обнаружение объекта, восстановление изображения (завершение матрицы) и оптический поток. Компьютерное зрение поддерживает такие технологии, как прототипы автомобилей с автоматическим управлением, продуктовые магазины без сотрудников, забавные фильтры Snapchat и средство проверки подлинности лица вашего мобильного устройства.

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

изображение: https: //assets.digitalocean.com/articles/python3_dogfilter/Hf5RDn3.gif [Фильтр рабочей собаки]

Работая с этим учебником, вы будете использовать + OpenCV +, библиотеку компьютерного зрения, + numpy + для утилит линейной алгебры и + matplotlib + для построения графиков. При создании приложения для компьютерного зрения вы также будете применять следующие концепции:

  • Обычные наименьшие квадраты как метод регрессии и классификации.

  • Основы стохастических градиентных нейронных сетей.

Хотя в этом учебнике нет необходимости, вам будет легче понять некоторые из более подробных объяснений, если вы знакомы с этими математическими понятиями:

  • Основные понятия линейной алгебры: скаляры, векторы и матрицы.

  • Основное исчисление: как взять производную.

Вы можете найти полный код этого учебника по адресу https://github.com/do-community/emotion-based-dog-filter.

Давайте начнем.

Предпосылки

Для завершения этого урока вам понадобится следующее:

Шаг 1 - Создание проекта и установка зависимостей

Давайте создадим рабочее пространство для этого проекта и установим зависимости, которые нам понадобятся. Мы назовем наше рабочее пространство + DogFilter +:

mkdir ~/DogFilter

Перейдите в каталог + DogFilter +:

cd ~/DogFilter

Затем создайте новую виртуальную среду Python для проекта:

python3 -m venv

Активируйте свою среду.

source /bin/activate

Приглашение изменится, указывая, что среда активна. Теперь установите PyTorch, среду глубокого изучения Python, которую мы будем использовать в этом руководстве. Процесс установки зависит от того, какую операционную систему вы используете.

В macOS установите Pytorch с помощью следующей команды:

python -m pip install torch== torchvision==

В Linux используйте следующие команды:

pip install http://download.pytorch.org/whl/cpu/torch--cp35-cp35m-.whl
pip install torchvision

А для Windows установите Pytorch с помощью этих команд:

pip install http://download.pytorch.org/whl/cpu/torch--cp35-cp35m-.whl
pip install torchvision

Теперь установите предварительно упакованные двоичные файлы для + OpenCV + и + numpy +, которые являются библиотеками компьютерного зрения и линейной алгебры, соответственно. Первый предлагает такие утилиты, как повороты изображения, а второй предлагает утилиты линейной алгебры, такие как инверсия матриц.

python -m pip install opencv-python== numpy==

Наконец, создайте каталог для наших ресурсов, в котором будут храниться изображения, которые мы будем использовать в этом руководстве:

mkdir assets

Установив зависимости, давайте создадим первую версию нашего фильтра: детектор лиц.

Шаг 2 - Создание детектора лиц

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

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

OpenCV предоставляет параметры модели в своем исходном коде. но нам нужен абсолютный путь к нашему локально установленному OpenCV, чтобы использовать эти параметры. Поскольку этот абсолютный путь может различаться, мы вместо этого загрузим нашу собственную копию и поместим ее в папку + assets +:

wget -O assets/haarcascade_frontalface_default.xml https://github.com/opencv/opencv/raw/master/data/haarcascades/haarcascade_frontalface_default.xml

Параметр + -O + указывает место назначения как + assets / haarcascade_frontalface_default.xml. Второй аргумент - это исходный URL.

Мы обнаружим все лица на следующем изображении с https://pexels.com [Pexels] (CC0, link to original образ).

изображение: https: //assets.digitalocean.com/articles/python3_dogfilter/CfoBWbF.png [изображение детей]

Сначала скачайте изображение. Следующая команда сохраняет загруженный образ как + children.png + в папке + assets:

wget -O assets/children.png https://assets.digitalocean.com/articles/python3_dogfilter/CfoBWbF.png

Чтобы проверить, работает ли алгоритм обнаружения, мы запустим его на отдельном изображении и сохраним полученное аннотированное изображение на диск. Создайте папку + output + для этих аннотированных результатов.

mkdir outputs

Теперь создайте скрипт Python для детектора лица. Создайте файл + step_1_face_detect +, используя + nano + или ваш любимый текстовый редактор:

nano step_2_face_detect.py

Добавьте следующий код в файл. Этот код импортирует OpenCV, который содержит утилиты изображений и классификатор лиц. Остальная часть кода является типичным примером программы Python.

step_2_face_detect.py

"""Test for face detection"""

import cv2


def main():
   pass

if __name__ == '__main__':
   main()

Теперь замените + pass + в функции + main + на этот код, который инициализирует классификатор лица, используя параметры OpenCV, которые вы скачали в папку + assets +:

step_2_face_detect.py

def main():
   # initialize front face classifier
   cascade = cv2.CascadeClassifier("assets/haarcascade_frontalface_default.xml")

Затем добавьте эту строку, чтобы загрузить изображение + children.png +.

step_2_face_detect.py

   frame = cv2.imread('assets/children.png')

Затем добавьте этот код, чтобы преобразовать изображение в черно-белое, так как классификатор обучался на черно-белых изображениях. Для этого мы конвертируем в градации серого и затем дискретизируем гистограмму:

step_2_face_detect.py

   # Convert to black-and-white
   gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
   blackwhite = cv2.equalizeHist(gray)

Затем используйте OpenCV https://docs.opencv.org/2.4/modules/objdetect/doc/cascade_classification.html#cascadeclassifier-detectmultiscale [+ detectMultiScale +] функцию для обнаружения всех лиц на изображении.

step_2_face_detect.py

   rects = cascade.detectMultiScale(
       blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30),
       flags=cv2.CASCADE_SCALE_IMAGE)
  • + scaleFactor + указывает, насколько изображение уменьшается по каждому измерению.

  • + minNeighbors + обозначает количество соседних прямоугольников, которые необходимо сохранить для кандидата-прямоугольника.

  • + minSize + - минимально допустимый размер обнаруженного объекта. Объекты меньшего размера отбрасываются.

Тип возвращаемого значения представляет собой список tuples, где каждый кортеж имеет четыре числа, обозначающие минимальную x, минимальную y, ширину, и высота прямоугольника в этом порядке.

Переберите все обнаруженные объекты и нарисуйте их на изображении зеленым цветом, используя https://docs.opencv.org/2.4/modules/core/doc/drawing_functions.html#rectangle [+ cv2.rectangle +]:

step_2_face_detect.py

   for x, y, w, h in rects:
       cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
  • Второй и третий аргументы являются противоположными углами прямоугольника.

  • Четвертый аргумент - это цвет для использования. + (0, 255, 0) + соответствует зеленому для нашего цветового пространства RGB.

  • Последний аргумент обозначает ширину нашей линии.

Наконец, запишите изображение с ограничивающими прямоугольниками в новый файл по адресу + output / children_detected.png +:

step_2_face_detect.py

   cv2.imwrite('outputs/children_detected.png', frame)

Ваш законченный скрипт должен выглядеть так:

step_2_face_detect.py

"""Tests face detection for a static image."""

import cv2


def main():

   # initialize front face classifier
   cascade = cv2.CascadeClassifier(
       "assets/haarcascade_frontalface_default.xml")

   frame = cv2.imread('assets/children.png')

   # Convert to black-and-white
   gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
   blackwhite = cv2.equalizeHist(gray)

   rects = cascade.detectMultiScale(
       blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30),
   flags=cv2.CASCADE_SCALE_IMAGE)

   for x, y, w, h in rects:
       cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

   cv2.imwrite('outputs/children_detected.png', frame)

if __name__ == '__main__':
   main()

Сохраните файл и выйдите из редактора. Затем запустите скрипт:

python step_2_face_detect.py

Откройте + output / children_detected.png +. Вы увидите следующее изображение, которое показывает грани, обведенные рамками:

изображение: https: //assets.digitalocean.com/articles/python3_dogfilter/x0fUqyk.png [изображение детей с ограничительными рамками]

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

Шаг 3 - Подключение камеры подачи

Следующая цель - подключить камеру компьютера к детектору лица. Вместо того, чтобы обнаруживать лица в статическом изображении, вы обнаружите все лица с камеры вашего компьютера. Вы будете собирать данные с камеры, обнаруживать и аннотировать все лица, а затем отображать аннотированное изображение обратно пользователю. Вы продолжите выполнение сценария на шаге 2, поэтому начните с дублирования этого сценария:

cp step_2_face_detect.py step_3_camera_face_detect.py

Затем откройте новый скрипт в вашем редакторе:

nano step_3_camera_face_detect.py

Вы обновите функцию + main +, используя некоторые элементы из этого testtest скрипт из официальной документации OpenCV. Начните с инициализации объекта + VideoCapture +, который настроен для захвата прямой трансляции с камеры вашего компьютера. Поместите это в начало функции + main +, перед другим кодом в функции:

step_3_camera_face_detect.py

def main():

   ...

Начиная со строки, определяющей + frame, отступим весь существующий код, поместив весь код в цикл` + while`.

step_3_camera_face_detect.py

       frame = cv2.imread('assets/children.png')
       ...
       for x, y, w, h in rects:
           cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

       cv2.imwrite('outputs/children_detected.png', frame)

Замените строку, определяющую + frame в начале цикла` + while`. Вместо того, чтобы читать с изображения на диске, теперь вы читаете с камеры:

step_3_camera_face_detect.py

Замените строку + cv2.imwrite (…​) + в конце цикла + while +. Вместо того, чтобы записывать изображение на диск, вы будете отображать аннотированное изображение обратно на экран пользователя:

step_3_camera_face_detect.py

Кроме того, добавьте код для отслеживания ввода с клавиатуры, чтобы вы могли остановить программу. Проверьте, нажимает ли пользователь символ + q +, и, если это так, выйдите из приложения. Сразу после + cv2.imshow (…​) + добавьте следующее:

step_3_camera_face_detect.py

...
       cv2.imshow('frame', frame)


...

Строка + cv2.waitkey (1) + останавливает программу на 1 миллисекунду, чтобы захваченное изображение могло быть отображено обратно пользователю.

Наконец, отпустите захват и закройте все окна. Поместите это вне цикла + while, чтобы завершить функцию` + main`.

step_3_camera_face_detect.py

...

   while True:
   ...

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

step_3_camera_face_detect.py

"""Test for face detection on video camera.

Move your face around and a green box will identify your face.
With the test frame in focus, hit `q` to exit.
Note that typing `q` into your terminal will do nothing.
"""

import cv2


def main():
   cap = cv2.VideoCapture(0)

   # initialize front face classifier
   cascade = cv2.CascadeClassifier(
       "assets/haarcascade_frontalface_default.xml")

   while True:
       # Capture frame-by-frame
       ret, frame = cap.read()

       # Convert to black-and-white
       gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
       blackwhite = cv2.equalizeHist(gray)

       # Detect faces
       rects = cascade.detectMultiScale(
           blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30),
           flags=cv2.CASCADE_SCALE_IMAGE)

       # Add all bounding boxes to the image
       for x, y, w, h in rects:
           cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

       # Display the resulting frame
       cv2.imshow('frame', frame)
       if cv2.waitKey(1) & 0xFF == ord('q'):
           break

   # When everything done, release the capture
   cap.release()
   cv2.destroyAllWindows()


if __name__ == '__main__':
   main()

Сохраните файл и выйдите из редактора.

Теперь запустите тестовый скрипт.

python step_3_camera_face_detect.py

Это активирует вашу камеру и открывает окно, отображающее канал вашей камеры. Ваше лицо будет окружено зеленым квадратом в реальном времени:

изображение: https: //assets.digitalocean.com/articles/python3_dogfilter/a7lyf7q.gif [Рабочий детектор лица]

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

Шаг 4 - Создание Собачьего фильтра

Прежде чем мы создадим сам фильтр, давайте рассмотрим, как изображения представлены численно. Это даст вам фон, необходимый для изменения изображений и в конечном итоге применить фильтр собаки.

Давайте посмотрим на пример. Мы можем построить черно-белое изображение, используя числа, где + 0 + соответствует черному, а + 1 + соответствует белому.

Сосредоточьтесь на разделительной линии между 1 и 0. Какую форму вы видите?

0 0 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0
0 0 0 1 1 1 0 0 0
0 0 1 1 1 1 1 0 0
0 0 0 1 1 1 0 0 0
0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 0 0 0

Изображение алмаз. Если сохранить это matrix значений в виде изображения. Это дает нам следующую картину:

изображение: https: //assets.digitalocean.com/articles/python3_dogfilter/QPontyM.png [ромб в виде картинки]

Мы можем использовать любое значение от 0 до 1, например, 0,1, 0,26 или 0,74391. Числа ближе к 0 темнее, а цифры ближе к 1 светлее. Это позволяет нам представлять белый, черный и любой оттенок серого. Это хорошая новость для нас, потому что теперь мы можем построить любое изображение в градациях серого, используя 0, 1 и любое значение между ними. Рассмотрим, например, следующее. Можете ли вы сказать, что это? Опять же, каждое число соответствует цвету пикселя.

1  1  1  1  1  1  1  1  1  1  1  1
1  1  1  1  0  0  0  0  1  1  1  1
1  1  0  0 .4 .4 .4 .4  0  0  1  1
1  0 .4 .4 .5 .4 .4 .4 .4 .4  0  1
1  0 .4 .5 .5 .5 .4 .4 .4 .4  0  1
0 .4 .4 .4 .5 .4 .4 .4 .4 .4 .4  0
0 .4 .4 .4 .4  0  0 .4 .4 .4 .4  0
0  0 .4 .4  0  1 .7  0 .4 .4  0  0
0  1  0  0  0 .7 .7  0  0  0  1  0
1  0  1  1  1  0  0 .7 .7 .4  0  1
1  0 .7  1  1  1 .7 .7 .7 .7  0  1
1  1  0  0 .7 .7 .7 .7  0  0  1  1
1  1  1  1  0  0  0  0  1  1  1  1
1  1  1  1  1  1  1  1  1  1  1  1

Перерисованный как изображение, теперь вы можете сказать, что это, по сути, Poké Ball:

изображение: https: //assets.digitalocean.com/articles/python3_dogfilter/RwAXIGE.png [Pokeball as picture]

Теперь вы видели, как черно-белые изображения и изображения в градациях серого представлены численно. Чтобы ввести цвет, нам нужен способ кодирования дополнительной информации. У изображения есть его высота и ширина, выраженная как + h x w +.

Изображение: https: //assets.digitalocean.com/articles/python3_dogfilter/sctg0YN.png [Image]

В текущем представлении в оттенках серого каждый пиксель имеет одно значение от 0 до 1. Мы можем эквивалентно сказать, что наше изображение имеет размеры + h x w x 1 +. Другими словами, каждая позиция + (x, y) + в нашем изображении имеет только одно значение.

изображение: https: //assets.digitalocean.com/articles/python3_dogfilter/58GGRPe.png [изображение в градациях серого]

Для представления цвета мы представляем цвет каждого пикселя, используя три значения от 0 до 1. Одно число соответствует «степени красного», одно - «степени зеленого», а последнее - «степени синего». Мы называем это RGB цветовое пространство. Это означает, что для каждой позиции + (x, y) + в нашем изображении у нас есть три значения + (r, g, b) +. В результате наше изображение теперь + h x w x 3 +:

изображение: https: //assets.digitalocean.com/articles/python3_dogfilter/kXL8Mms.png [Цветное изображение]

Здесь каждое число колеблется от 0 до 255 вместо 0 до 1, но идея та же. Разные комбинации чисел соответствуют разным цветам, таким как темно-фиолетовый + (102, 0, 204) + или ярко-оранжевый + (255, 153, 51) +. Выводы следующие:

  1. Каждое изображение будет представлено в виде блока чисел, который имеет три измерения: высоту, ширину и цветовые каналы. Манипулирование этим полем чисел напрямую эквивалентно манипулированию изображением.

  2. Мы также можем выровнять это поле, чтобы оно стало просто списком чисел. Таким образом, наше изображение становится vector. Позже мы будем называть изображения векторами.

Теперь, когда вы понимаете, как изображения представлены в числовом виде, вы хорошо подготовлены к тому, чтобы начать наносить собачьи маски на лица. Чтобы применить маску собаки, вы замените значения в дочернем изображении пикселями небелой маски собаки. Для начала вы будете работать с одним изображением. Загрузите этот фрагмент лица из изображения, которое вы использовали в Шаге 2.

wget -O assets/child.png https://assets.digitalocean.com/articles/python3_dogfilter/alXjNK1.png

изображение: https: //assets.digitalocean.com/articles/python3_dogfilter/alXjNK1.png [Обрезанное лицо]

Дополнительно скачайте следующую маску собаки. Маски для собак, использованные в этом уроке, являются моими собственными рисунками, теперь опубликованными в открытом доступе под лицензией CC0.

изображение: https: //assets.digitalocean.com/articles/python3_dogfilter/ED32BCs.png [маска для собаки]

Загрузите это с помощью + wget +:

wget -O assets/dog.png https://assets.digitalocean.com/articles/python3_dogfilter/ED32BCs.png

Создайте новый файл с именем + step_4_dog_mask_simple.py +, который будет содержать код для сценария, который применяет маску собаки к лицам:

nano step_4_dog_mask_simple.py

Добавьте следующий шаблон для скрипта Python и импортируйте библиотеки OpenCV и + numpy +:

step_4_dog_mask_simple.py

"""Test for adding dog mask"""

import cv2
import numpy as np


def main():
   pass

if __name__ == '__main__':
   main()

Замените + pass + в функции + main + на эти две строки, которые загружают исходное изображение и маску собаки в память.

step_4_dog_mask_simple.py

...
def main():
   face = cv2.imread('assets/child.png')
   mask = cv2.imread('assets/dog.png')

Далее наденьте собачью маску на ребенка. Логика более сложная, чем то, что мы делали ранее, поэтому мы создадим новую функцию с именем + apply_mask + для модульной реализации нашего кода. Сразу после двух строк, которые загружают изображения, добавьте эту строку, которая вызывает функцию + apply_mask +:

step_4_dog_mask_simple.py

...
   face_with_mask = apply_mask(face, mask)

Создайте новую функцию с именем + apply_mask + и поместите ее над функцией + main +:

step_4_dog_mask_simple.py

...
def apply_mask(face: np.array, mask: np.array) -> np.array:
   """Add the mask to the provided face, and return the face with mask."""
   pass

def main():
...

На этом этапе ваш файл должен выглядеть так:

step_4_dog_mask_simple.py

"""Test for adding dog mask"""

import cv2
import numpy as np


def apply_mask(face: np.array, mask: np.array) -> np.array:
   """Add the mask to the provided face, and return the face with mask."""
   pass


def main():
   face = cv2.imread('assets/child.png')
   mask = cv2.imread('assets/dog.png')
   face_with_mask = apply_mask(face, mask)

if __name__ == '__main__':
   main()

Давайте создадим функцию + apply_mask +. Наша цель - нанести маску на лицо ребенка. Тем не менее, мы должны поддерживать соотношение сторон для нашей маски собаки. Для этого нам нужно явно вычислить окончательные размеры нашей маски собаки. Внутри функции + apply_mask + замените + pass + на эти две строки, которые извлекают высоту и ширину обоих изображений:

step_4_dog_mask_simple.py

...
   mask_h, mask_w, _ = mask.shape
   face_h, face_w, _ = face.shape

Затем определите, какое измерение нужно «сжать больше». Чтобы быть точным, нам нужно ужесточить два ограничения. Добавьте эту строку в функцию + apply_mask +:

step_4_dog_mask_simple.py

...

   # Resize the mask to fit on face
   factor = min(face_h / mask_h, face_w / mask_w)

Затем вычислите новую форму, добавив этот код в функцию:

step_4_dog_mask_simple.py

...
   new_mask_w = int(factor * mask_w)
   new_mask_h = int(factor * mask_h)
   new_mask_shape = (new_mask_w, new_mask_h)

Здесь мы приводим числа к целым числам, так как функция + resize + требует целочисленных измерений.

Теперь добавьте этот код, чтобы изменить размер маски собаки в новую форму:

step_4_dog_mask_simple.py

...

   # Add mask to face - ensure mask is centered
   resized_mask = cv2.resize(mask, new_mask_shape)

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

step_4_dog_mask_simple.py

   cv2.imwrite('outputs/resized_dog.png', resized_mask)

Завершенный скрипт должен выглядеть так:

step_4_dog_mask_simple.py

"""Test for adding dog mask"""
import cv2
import numpy as np

def apply_mask(face: np.array, mask: np.array) -> np.array:
   """Add the mask to the provided face, and return the face with mask."""
   mask_h, mask_w, _ = mask.shape
   face_h, face_w, _ = face.shape

   # Resize the mask to fit on face
   factor = min(face_h / mask_h, face_w / mask_w)
   new_mask_w = int(factor * mask_w)
   new_mask_h = int(factor * mask_h)
   new_mask_shape = (new_mask_w, new_mask_h)

   # Add mask to face - ensure mask is centered
   resized_mask = cv2.resize(mask, new_mask_shape)
   cv2.imwrite('outputs/resized_dog.png', resized_mask)


def main():
   face = cv2.imread('assets/child.png')
   mask = cv2.imread('assets/dog.png')
   face_with_mask = apply_mask(face, mask)

if __name__ == '__main__':
   main()

Сохраните файл и выйдите из редактора. Запустите новый скрипт:

python step_4_dog_mask_simple.py

Откройте изображение в + output / resized_dog.png +, чтобы дважды проверить правильность изменения размера маски. Он будет соответствовать маске собаки, показанной ранее в этом разделе.

Теперь добавьте маску собаки ребенку. Снова откройте файл + step_4_dog_mask_simple.py + и вернитесь к функции + apply_mask +:

nano step_4_dog_mask_simple.py

Сначала удалите строку кода, которая записывает маску с измененным размером из функции + apply_mask +, поскольку она вам больше не нужна:

   ...

Вместо этого примените свои знания о представлении изображения с самого начала этого раздела, чтобы изменить изображение. Начните с создания копии дочернего изображения. Добавьте эту строку в функцию + apply_mask +:

step_4_dog_mask_simple.py

...
   face_with_mask = face.copy()

Затем найдите все позиции, где маска собаки не белая или почти белая. Для этого убедитесь, что значение пикселя не превышает 250 во всех цветовых каналах, так как мы ожидаем, что почти белый пиксель будет около + [255, 255, 255] +. Добавьте этот код:

step_4_dog_mask_simple.py

...
   non_white_pixels = (resized_mask < 250).all(axis=2)

На этом этапе изображение собаки, самое большее, такое же, как изображение ребенка. Мы хотим центрировать изображение собаки на лице, поэтому вычислите смещение, необходимое для центрирования изображения собаки, добавив этот код в + apply_mask +:

step_4_dog_mask_simple.py

...
   off_h = int((face_h - new_mask_h) / 2)
   off_w = int((face_w - new_mask_w) / 2)

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

step_4_dog_mask_simple.py

   face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \
           resized_mask[non_white_pixels]

Затем верните результат:

step_4_dog_mask_simple.py

   return face_with_mask

В функции + main + добавьте этот код, чтобы записать результат функции + apply_mask + в выходное изображение, чтобы вы могли вручную дважды проверить результат:

step_4_dog_mask_simple.py

...
   face_with_mask = apply_mask(face, mask)

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

step_4_dog_mask_simple.py

"""Test for adding dog mask"""

import cv2
import numpy as np


def apply_mask(face: np.array, mask: np.array) -> np.array:
   """Add the mask to the provided face, and return the face with mask."""
   mask_h, mask_w, _ = mask.shape
   face_h, face_w, _ = face.shape

   # Resize the mask to fit on face
   factor = min(face_h / mask_h, face_w / mask_w)
   new_mask_w = int(factor * mask_w)
   new_mask_h = int(factor * mask_h)
   new_mask_shape = (new_mask_w, new_mask_h)
   resized_mask = cv2.resize(mask, new_mask_shape)

   # Add mask to face - ensure mask is centered
   face_with_mask = face.copy()
   non_white_pixels = (resized_mask < 250).all(axis=2)
   off_h = int((face_h - new_mask_h) / 2)
   off_w = int((face_w - new_mask_w) / 2)
   face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \
        resized_mask[non_white_pixels]

   return face_with_mask

def main():
   face = cv2.imread('assets/child.png')
   mask = cv2.imread('assets/dog.png')
   face_with_mask = apply_mask(face, mask)
   cv2.imwrite('outputs/child_with_dog_mask.png', face_with_mask)

if __name__ == '__main__':
   main()

Сохраните скрипт и запустите его:

python step_4_dog_mask_simple.py

В + output / child_with_dog_mask.png + вы увидите следующую картинку ребенка с маской собаки:

изображение: https: //assets.digitalocean.com/articles/python3_dogfilter/ZEn0RsJ.png [изображение ребенка с маской собаки]

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

Мы заберем с того места, где остановились на шаге 3. Скопируйте + step_3_camera_face_detect.py + в + step_4_dog_mask.py +.

cp step_3_camera_face_detect.py step_4_dog_mask.py

Откройте свой новый скрипт.

nano step_4_dog_mask.py

Сначала импортируйте библиотеку NumPy вверху скрипта:

step_4_dog_mask.py

import numpy as np
...

Затем добавьте функцию + apply_mask + из вашей предыдущей работы в этот новый файл над функцией + main +:

step_4_dog_mask.py

def apply_mask(face: np.array, mask: np.array) -> np.array:
   """Add the mask to the provided face, and return the face with mask."""
   mask_h, mask_w, _ = mask.shape
   face_h, face_w, _ = face.shape

   # Resize the mask to fit on face
   factor = min(face_h / mask_h, face_w / mask_w)
   new_mask_w = int(factor * mask_w)
   new_mask_h = int(factor * mask_h)
   new_mask_shape = (new_mask_w, new_mask_h)
   resized_mask = cv2.resize(mask, new_mask_shape)

   # Add mask to face - ensure mask is centered
   face_with_mask = face.copy()
   non_white_pixels = (resized_mask < 250).all(axis=2)
   off_h = int((face_h - new_mask_h) / 2)
   off_w = int((face_w - new_mask_w) / 2)
   face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \
        resized_mask[non_white_pixels]

   return face_with_mask
...

Во-вторых, найдите эту строку в функции + main +:

step_4_dog_mask.py

   cap = cv2.VideoCapture(0)

Добавьте этот код после этой строки, чтобы загрузить маску собаки:

step_4_dog_mask.py

   cap = cv2.VideoCapture(0)



   ...

Далее в цикле + while + найдите эту строку:

step_4_dog_mask.py

       ret, frame = cap.read()

Добавьте эту строку после нее, чтобы извлечь высоту и ширину изображения:

step_4_dog_mask.py

       ret, frame = cap.read()

       ...

Затем удалите строку в + main +, которая рисует ограничивающие рамки. Вы найдете эту строку в цикле + for +, который перебирает обнаруженные лица:

step_4_dog_mask.py

       for x, y, w, h in rects:
       ...

       ...

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

step_4_dog_mask.py

       for x, y, w, h in rects:
           # crop a frame slightly larger than the face
           y0, y1 = int(y - 0.25*h), int(y + 0.75*h)
           x0, x1 = x, x + w

Введите проверку, если обнаруженное лицо находится слишком близко к краю.

step_4_dog_mask.py

           # give up if the cropped frame would be out-of-bounds
           if x0 < 0 or y0 < 0 or x1 > frame_w or y1 > frame_h:
               continue

Наконец, вставьте лицо с маской в ​​изображение.

step_4_dog_mask.py

           # apply mask
           frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask)

Убедитесь, что ваш скрипт выглядит так:

step_4_dog_mask.py

"""Real-time dog filter

Move your face around and a dog filter will be applied to your face if it is not out-of-bounds. With the test frame in focus, hit `q` to exit. Note that typing `q` into your terminal will do nothing.
"""

import numpy as np
import cv2


def apply_mask(face: np.array, mask: np.array) -> np.array:
   """Add the mask to the provided face, and return the face with mask."""
   mask_h, mask_w, _ = mask.shape
   face_h, face_w, _ = face.shape

   # Resize the mask to fit on face
   factor = min(face_h / mask_h, face_w / mask_w)
   new_mask_w = int(factor * mask_w)
   new_mask_h = int(factor * mask_h)
   new_mask_shape = (new_mask_w, new_mask_h)
   resized_mask = cv2.resize(mask, new_mask_shape)

   # Add mask to face - ensure mask is centered
   face_with_mask = face.copy()
   non_white_pixels = (resized_mask < 250).all(axis=2)
   off_h = int((face_h - new_mask_h) / 2)
   off_w = int((face_w - new_mask_w) / 2)
   face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \
        resized_mask[non_white_pixels]

   return face_with_mask

def main():
   cap = cv2.VideoCapture(0)

   # load mask
   mask = cv2.imread('assets/dog.png')

   # initialize front face classifier
   cascade = cv2.CascadeClassifier("assets/haarcascade_frontalface_default.xml")

   while(True):
       # Capture frame-by-frame
       ret, frame = cap.read()
       frame_h, frame_w, _ = frame.shape

       # Convert to black-and-white
       gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
       blackwhite = cv2.equalizeHist(gray)

       # Detect faces
       rects = cascade.detectMultiScale(
           blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30),
           flags=cv2.CASCADE_SCALE_IMAGE)

       # Add mask to faces
       for x, y, w, h in rects:
           # crop a frame slightly larger than the face
           y0, y1 = int(y - 0.25*h), int(y + 0.75*h)
           x0, x1 = x, x + w

           # give up if the cropped frame would be out-of-bounds
           if x0 < 0 or y0 < 0 or x1 > frame_w or y1 > frame_h:
               continue

           # apply mask
           frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask)

       # Display the resulting frame
       cv2.imshow('frame', frame)
       if cv2.waitKey(1) & 0xFF == ord('q'):
           break

   # When everything done, release the capture
   cap.release()
   cv2.destroyAllWindows()


if __name__ == '__main__':
   main()

Сохраните файл и выйдите из редактора. Затем запустите скрипт.

python step_4_dog_mask.py

Теперь у вас работает собачий фильтр в реальном времени. Скрипт также будет работать с несколькими лицами на картинке, так что вы можете собрать своих друзей вместе для некоторой автоматической идентификации собак.

изображение: https: //assets.digitalocean.com/articles/python3_dogfilter/g9CiUD1.gif [GIF для фильтра рабочей собаки]

На этом мы завершаем нашу первую основную задачу в этом уроке, которая заключается в создании фильтра для собак в стиле Snapchat. Теперь давайте используем выражение лица, чтобы определить маску собаки, примененную к лицу.

Шаг 5 - Создайте базовый классификатор эмоций лица, используя наименьшие квадраты

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

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

Нам нужно задать два вопроса для каждой модели, которую мы рассматриваем. На данный момент этих двух вопросов будет достаточно, чтобы провести различие между моделями:

  1. Входные данные: какую информацию дает модель?

  2. Вывод: что модель пытается предсказать?

На высоком уровне цель состоит в том, чтобы разработать модель классификации эмоций. Модель является:

  1. Вход: даны изображения лиц.

  2. Вывод: предсказывает соответствующую эмоцию.

model: face -> emotion

Подход, который мы будем использовать, это least squares; мы берем набор точек и находим линию наилучшего соответствия. Линия наилучшего соответствия, показанная на следующем изображении, является нашей моделью.

изображение: https: //assets.digitalocean.com/articles/python3_dogfilter/lYQDlWs.png [Наименьшие квадраты]

Рассмотрим ввод и вывод для нашей строки:

  1. Ввод: данные + x + координаты.

  2. Вывод: прогнозирует соответствующую $ y $ координату.

least squares line: x -> y

Наш ввод + x + должен представлять лица, а наш вывод + y + должен представлять эмоцию, чтобы мы могли использовать метод наименьших квадратов для классификации эмоций:

  • + x → face +: вместо того, чтобы использовать one число для + x +, мы будем использовать vector значений для + x +. Таким образом, + x + может представлять изображения лиц. В статье Ordinary Least Squares объясняется, почему вы можете использовать вектор значений для + x +.

  • + y → emotion +: каждая эмоция будет соответствовать числу. Например, «angry» равно 0, «sad» равно 1, а «happy» равно 2. Таким образом, + y + может представлять эмоции. Однако наша строка не ограничена для вывода значений + y + 0, 1 и 2. У него бесконечное число возможных значений y - это может быть 1,2, 3,5 или 10003,42. Как мы переводим эти значения + y + в целые числа, соответствующие классам? См. Статью One-Hot Encoding для более подробной информации и объяснения.

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

  1. Предварительная обработка данных: как объяснено в начале этого раздела, наши образцы являются векторами, где каждый вектор кодирует изображение лица. Наши метки являются целыми числами, соответствующими эмоциям, и мы будем применять однократное кодирование к этим меткам.

  2. Укажите и обучите модель: используйте решение наименьших квадратов в замкнутой форме, + w ^ * +.

  3. Запустите прогноз, используя модель: возьмите argmax + Xw ^ * +, чтобы получить предсказанные эмоции.

Давайте начнем.

Сначала создайте каталог для хранения данных:

mkdir data

Затем загрузите данные, подготовленные Пьером-Люком Керриером и Аароном Курвиллем, из Классификации эмоций лица 2013 года compiteition на Kaggle.

wget -O data/fer2013.tar https://bitbucket.org/alvinwan/adversarial-examples-in-computer-vision-building-then-fooling/raw/babfe4651f89a398c4b3fdbdd6d7a697c5104cff/fer2013.tar

Перейдите в каталог + data + и распакуйте данные.

cd data
tar -xzf fer2013.tar

Теперь мы создадим скрипт для запуска модели наименьших квадратов. Перейдите в корень вашего проекта:

cd ~/DogFilter

Создайте новый файл для скрипта:

nano step_5_ls_simple.py

Добавьте шаблон Python и импортируйте необходимые вам пакеты:

step_5_ls_simple.py

"""Train emotion classifier using least squares."""

import numpy as np

def main():
   pass

if __name__ == '__main__':
   main()

Затем загрузите данные в память. Замените + pass + в вашей функции + main + следующим кодом:

step_5_ls_simple.py

   # load data
   with np.load('data/fer2013_train.npz') as data:
       X_train, Y_train = data['X'], data['Y']

   with np.load('data/fer2013_test.npz') as data:
       X_test, Y_test = data['X'], data['Y']

Теперь горячо закодируйте ярлыки. Для этого создайте единичную матрицу с помощью + numpy +, а затем внесите в нее индекс, используя наш список меток:

step_5_ls_simple.py

   # one-hot labels
   I = np.eye(6)
   Y_oh_train, Y_oh_test = I[Y_train], I[Y_test]

Здесь мы используем тот факт, что вся строка + i + в единичной матрице равна нулю, за исключением записи + i +. Таким образом, i-я строка представляет собой горячее кодирование для метки класса + i +. Кроме того, мы используем расширенную индексацию + numpy +, где + [a, b, c, d] [[1, 3]] = [b, d] +.

Вычисление + (X ^ TX) ^ {- 1} + займет слишком много времени на стандартном оборудовании, так как + X ^ TX + является матрицей + 2304x2304 + с более чем четырьмя миллионами значений, поэтому мы сократим это время выбрав только первые 100 функций. Добавьте этот код:

step_5_ls_simple.py

...
   # select first 100 dimensions
   A_train, A_test = X_train[:, :100], X_test[:, :100]

Затем добавьте этот код для оценки решения наименьших квадратов в замкнутой форме:

step_5_ls_simple.py

...
   # train model
   w = np.linalg.inv(A_train.T.dot(A_train)).dot(A_train.T.dot(Y_oh_train))

Затем определите функцию оценки для наборов обучения и проверки. Поместите это перед вашей функцией + main +:

step_5_ls_simple.py

def evaluate(A, Y, w):
   Yhat = np.argmax(A.dot(w), axis=1)
   return np.sum(Yhat == Y) / Y.shape[0]

Для оценки меток мы берем внутренний продукт с каждой выборкой и получаем индексы максимальных значений, используя + np.argmax +. Затем мы вычисляем среднее количество правильных классификаций. Это последнее число ваша точность.

Наконец, добавьте этот код в конец функции + main +, чтобы вычислить точность обучения и проверки с использованием только что написанной функции +valu +:

step_5_ls_simple.py

   # evaluate model
   ols_train_accuracy = evaluate(A_train, Y_train, w)
   print('(ols) Train Accuracy:', ols_train_accuracy)
   ols_test_accuracy = evaluate(A_test, Y_test, w)
   print('(ols) Test Accuracy:', ols_test_accuracy)

Дважды проверьте, что ваш скрипт соответствует следующему:

step_5_ls_simple.py

"""Train emotion classifier using least squares."""

import numpy as np


def evaluate(A, Y, w):
   Yhat = np.argmax(A.dot(w), axis=1)
   return np.sum(Yhat == Y) / Y.shape[0]

def main():

   # load data
   with np.load('data/fer2013_train.npz') as data:
       X_train, Y_train = data['X'], data['Y']

   with np.load('data/fer2013_test.npz') as data:
       X_test, Y_test = data['X'], data['Y']

   # one-hot labels
   I = np.eye(6)
   Y_oh_train, Y_oh_test = I[Y_train], I[Y_test]

   # select first 100 dimensions
   A_train, A_test = X_train[:, :100], X_test[:, :100]

   # train model
   w = np.linalg.inv(A_train.T.dot(A_train)).dot(A_train.T.dot(Y_oh_train))

   # evaluate model
   ols_train_accuracy = evaluate(A_train, Y_train, w)
   print('(ols) Train Accuracy:', ols_train_accuracy)
   ols_test_accuracy = evaluate(A_test, Y_test, w)
   print('(ols) Test Accuracy:', ols_test_accuracy)


if __name__ == '__main__':
   main()

Сохраните файл, выйдите из редактора и запустите скрипт Python.

python step_5_ls_simple.py

Вы увидите следующий вывод:

Output(ols) Train Accuracy: 0.4748918316507146
(ols) Test Accuracy: 0.45280545359202934

Наша модель дает 47,5% точности поезда. Мы повторяем это на проверочном наборе, чтобы получить точность 45,3%. Для задачи трехсторонней классификации 45,3% разумно выше угадывания, что составляет 33%. Это наш начальный классификатор для обнаружения эмоций, и на следующем шаге вы создадите модель наименьших квадратов для повышения точности. Чем выше точность, тем надежнее ваш собачий фильтр на основе эмоций может найти подходящий собачий фильтр для каждой обнаруженной эмоции.

Шаг 6 - Повышение точности за счет выделения входов

Мы можем использовать более выразительную модель для повышения точности. Для этого мы featurize наши входы.

Исходное изображение говорит нам, что позиция (+0, 0 +) - красная, (+1, 0 +) - коричневая, и так далее. Изображение со стеснением может сказать нам, что в левом верхнем углу изображения есть собака, посередине - человек и т. Д. Создание функций - это мощное средство, но его точное определение выходит за рамки данного руководства.

Мы будем использовать approximation для ядра радиальной базисной функции (RBF), используя случайную гауссову матрицу. Мы не будем вдаваться в подробности в этом уроке. Вместо этого мы будем рассматривать это как черный ящик, который вычисляет функции более высокого порядка для нас.

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

cp step_5_ls_simple.py step_6_ls_simple.py

Откройте новый файл в вашем редакторе:

nano step_6_ls_simple.py

Начнем с создания потрясающей случайной матрицы. Опять же, мы будем использовать только 100 функций в нашем новом пространстве функций.

Найдите следующую строку, определяя + A_train + и + A_test +:

step_6_ls_simple.py

   # select first 100 dimensions
   A_train, A_test = X_train[:, :100], X_test[:, :100]

Непосредственно над этим определением для + A_train + и + A_test + добавьте матрицу случайных объектов:

step_6_ls_simple.py

   # select first 100 dimensions
   A_train, A_test = X_train[:, :100], X_test[:, :100]  ...

Затем замените определения для + A_train + и + A_test +. Мы переопределяем наши матрицы, называемые матрицами design, используя эту случайную настройку.

step_6_ls_simple.py

   A_train, A_test = X_train.dot(W), X_test.dot(W)

Сохраните ваш файл и запустите скрипт.

python step_6_ls_simple.py

Вы увидите следующий вывод:

Output(ols) Train Accuracy: 0.584174642717
(ols) Test Accuracy: 0.584425799685

Эта функция теперь обеспечивает точность поезда 58,4% и точность проверки 58,4%, что улучшает результаты проверки на 13,1%. Мы урезали X-матрицу до «100 x 100 +», но выбор 100 был произвольным. Мы также можем обрезать матрицу ` X ` так, чтобы она составляла `+1000 x 1000 +` или `+50 x 50 +`. Скажем, что размерность ` x ` равна ` d x d `. Мы можем проверить больше значений ` d `, повторно обрезав X до ` d x d +` и повторно вычислив новую модель.

Пытаясь увеличить значения + d +, мы находим дополнительное улучшение точности теста на 4,3% до 61,7%. На следующем рисунке мы рассматриваем производительность нашего нового классификатора при изменении + d +. Интуитивно понятно, что по мере увеличения + d + точность также должна увеличиваться, поскольку мы используем все больше и больше наших исходных данных. Однако вместо того, чтобы рисовать радужную картину, график демонстрирует отрицательную тенденцию:

изображение: https: //assets.digitalocean.com/articles/python3_dogfilter/cfKxdJ9.png [Производительность простых наименьших квадратов]

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

Мы исправляем нашу обычную целевую функцию наименьших квадратов термином регуляризации, давая нам новую цель. Наша новая целевая функция называется ridge regression и выглядит так:

min_w |Aw- y|^2 + lambda |w|^2

В этом уравнении + lambda + является перестраиваемым гиперпараметром. Вставьте + lambda = 0 + в уравнение, и регрессия гребня станет методом наименьших квадратов. Вставьте + lambda = infinity + в уравнение, и вы обнаружите, что лучший + w + теперь должен быть нулевым, так как любой ненулевой + w + несет бесконечные потери. Как оказалось, эта цель также дает решение в замкнутой форме:

w^* = (A^TA + lambda I)^{-1}A^Ty

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

Снова откройте + step_6_ls_simple.py + в вашем редакторе:

nano step_6_ls_simple.py

На этот раз увеличьте размерность нового пространства признаков до + d = 1000 +. Измените значение + d + с + 100 + на + 1000 +, как показано в следующем блоке кода:

step_6_ls_simple.py

...
   d =
   W = np.random.normal(size=(X_train.shape[1], d))
...

Затем примените регрессию гребня, используя регуляризацию + lambda = 10 ^ {10} +. Замените строку, определяющую + w +, следующими двумя строками:

step_6_ls_simple.py

...
   # train model

   1e10 * I

Затем найдите этот блок:

step_6_ls_simple.py

...
 ols_train_accuracy = evaluate(A_train, Y_train, w)
 print('(ols) Train Accuracy:', ols_train_accuracy)
 ols_test_accuracy = evaluate(A_test, Y_test, w)
 print('(ols) Test Accuracy:', ols_test_accuracy)

Замените его следующим:

step_6_ls_simple.py

...

 print('(ridge) Train Accuracy:', evaluate(A_train, Y_train, w))
 print('(ridge) Test Accuracy:', evaluate(A_test, Y_test, w))

Завершенный скрипт должен выглядеть так:

step_6_ls_simple.py

"""Train emotion classifier using least squares."""

import numpy as np

def evaluate(A, Y, w):
   Yhat = np.argmax(A.dot(w), axis=1)
   return np.sum(Yhat == Y) / Y.shape[0]

def main():
   # load data
   with np.load('data/fer2013_train.npz') as data:
       X_train, Y_train = data['X'], data['Y']

   with np.load('data/fer2013_test.npz') as data:
       X_test, Y_test = data['X'], data['Y']

   # one-hot labels
   I = np.eye(6)
   Y_oh_train, Y_oh_test = I[Y_train], I[Y_test]
   d = 1000
   W = np.random.normal(size=(X_train.shape[1], d))
   # select first 100 dimensions
   A_train, A_test = X_train.dot(W), X_test.dot(W)

   # train model
   I = np.eye(A_train.shape[1])
   w = np.linalg.inv(A_train.T.dot(A_train) + 1e10 * I).dot(A_train.T.dot(Y_oh_train))

   # evaluate model
   print('(ridge) Train Accuracy:', evaluate(A_train, Y_train, w))
   print('(ridge) Test Accuracy:', evaluate(A_test, Y_test, w))

if __name__ == '__main__':
   main()

Сохраните файл, выйдите из редактора и запустите скрипт:

python step_6_ls_simple.py

Вы увидите следующий вывод:

Output(ridge) Train Accuracy: 0.651173462698
(ridge) Test Accuracy: 0.622181436812

Точность валидации увеличилась на 0,4% до 62,2%, а точность поезда снизилась до 65,1%. Еще раз переоценивая ряд различных + d +, мы видим меньший разрыв между точностью обучения и валидации для регрессии гребня. Другими словами, регрессия гребня была менее подвержена переоснащению.

изображение: https: //assets.digitalocean.com/articles/python3_dogfilter/gzGBSGo.png [Производительность с использованием марихуаны и регрессии гребня]

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

Шаг 7 - Создание классификатора эмоций лица с использованием сверточной нейронной сети в PyTorch

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

Краткую визуализацию и введение в нейронную сеть см. В статье Understanding Neural Networks. Здесь мы будем использовать глубокую библиотеку PyTorch. Существует множество широко используемых библиотек, каждая из которых имеет свои плюсы и минусы. PyTorch является особенно хорошим местом для начала. Чтобы реализовать этот классификатор нейронной сети, мы снова предпринимаем три шага, как мы это сделали с классификатором наименьших квадратов:

  1. Предварительная обработка данных: примените горячее кодирование, а затем примените абстракции PyTorch.

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

  3. Выполните прогноз с помощью модели: Оцените нейронную сеть.

Создайте новый файл с именем + step_7_fer_simple.py +

nano step_7_fer_simple.py

Импортируйте необходимые утилиты и создайте Python class, в котором будут храниться ваши данные. Для обработки данных здесь вы создадите наборы данных поезд и тест. Для этого реализуйте интерфейс PyTorch + Dataset +, который позволяет загружать и использовать встроенный конвейер данных PyTorch для набора данных распознавания эмоций лица:

step_7_fer_simple.py

from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torch
import cv2
import argparse


class Fer2013Dataset(Dataset):
   """Face Emotion Recognition dataset.

   Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier
   and Aaron Courville in 2013.

   Each sample is 1 x 1 x 48 x 48, and each label is a scalar.
   """
   pass

Удалите заполнитель + pass в классе` + Fer2013 Dataset`. Вместо него добавьте функцию, которая будет инициализировать наш держатель данных:

step_7_fer_simple.py

   def __init__(self, path: str):
       """
       Args:
           path: Path to `.np` file containing sample nxd and label nx1
       """
       with np.load(path) as data:
           self._samples = data['X']
           self._labels = data['Y']
       self._samples = self._samples.reshape((-1, 1, 48, 48))

       self.X = Variable(torch.from_numpy(self._samples)).float()
       self.Y = Variable(torch.from_numpy(self._labels)).float()
...

Эта функция начинается с загрузки образцов и меток. Затем он оборачивает данные в структуры данных PyTorch.

Сразу после функции + init + добавьте функцию + len +, поскольку это необходимо для реализации интерфейса + Dataset +, ожидаемого PyTorch:

step_7_fer_simple.py

...
   def __len__(self):
       return len(self._labels)

Наконец, добавьте метод + getitem +, который возвращает dictionary, содержащий образец и метку:

step_7_fer_simple.py

   def __getitem__(self, idx):
       return {'image': self._samples[idx], 'label': self._labels[idx]}

Дважды проверьте, что ваш файл выглядит следующим образом:

step_7_fer_simple.py

from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torch
import cv2
import argparse


class Fer2013Dataset(Dataset):
   """Face Emotion Recognition dataset.
   Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier
   and Aaron Courville in 2013.
   Each sample is 1 x 1 x 48 x 48, and each label is a scalar.
   """

   def __init__(self, path: str):
       """
       Args:
           path: Path to `.np` file containing sample nxd and label nx1
       """
       with np.load(path) as data:
           self._samples = data['X']
           self._labels = data['Y']
       self._samples = self._samples.reshape((-1, 1, 48, 48))

       self.X = Variable(torch.from_numpy(self._samples)).float()
       self.Y = Variable(torch.from_numpy(self._labels)).float()

   def __len__(self):
       return len(self._labels)

   def __getitem__(self, idx):
       return {'image': self._samples[idx], 'label': self._labels[idx]}

Затем загрузите набор данных + Fer2013Dataset +. Добавьте следующий код в конец вашего файла после класса + Fer2013Dataset +:

step_7_fer_simple.py

trainset = Fer2013Dataset('data/fer2013_train.npz')
trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=True)

testset = Fer2013Dataset('data/fer2013_test.npz')
testloader = torch.utils.data.DataLoader(testset, batch_size=32, shuffle=False)

Этот код инициализирует набор данных, используя созданный класс + Fer2013Dataset +. Затем для набора поездов и проверки он оборачивает набор данных в + DataLoader +. Это переводит набор данных в итерацию для последующего использования.

В качестве проверки работоспособности убедитесь, что утилиты набора данных работают. Создайте пример загрузчика набора данных с помощью + DataLoader + и напечатайте первый элемент этого загрузчика. Добавьте следующее в конец вашего файла:

step_7_fer_simple.py

if __name__ == '__main__':
   loader = torch.utils.data.DataLoader(trainset, batch_size=2, shuffle=False)
   print(next(iter(loader)))

Убедитесь, что ваш законченный скрипт выглядит так:

step_7_fer_simple.py

from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torch
import cv2
import argparse


class Fer2013Dataset(Dataset):
   """Face Emotion Recognition dataset.
   Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier
   and Aaron Courville in 2013.
   Each sample is 1 x 1 x 48 x 48, and each label is a scalar.
   """

   def __init__(self, path: str):
       """
       Args:
           path: Path to `.np` file containing sample nxd and label nx1
       """
       with np.load(path) as data:
           self._samples = data['X']
           self._labels = data['Y']
       self._samples = self._samples.reshape((-1, 1, 48, 48))

       self.X = Variable(torch.from_numpy(self._samples)).float()
       self.Y = Variable(torch.from_numpy(self._labels)).float()

   def __len__(self):
       return len(self._labels)

   def __getitem__(self, idx):
       return {'image': self._samples[idx], 'label': self._labels[idx]}

trainset = Fer2013Dataset('data/fer2013_train.npz')
trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=True)

testset = Fer2013Dataset('data/fer2013_test.npz')
testloader = torch.utils.data.DataLoader(testset, batch_size=32, shuffle=False)

if __name__ == '__main__':
   loader = torch.utils.data.DataLoader(trainset, batch_size=2, shuffle=False)
   print(next(iter(loader)))

Выйдите из редактора и запустите скрипт.

python step_7_fer_simple.py

В результате вы получите следующую пару tensors. Наш конвейер данных выводит две выборки и две метки. Это указывает на то, что наш конвейер данных запущен и готов к работе:

Output{'image':
(0 ,0 ,.,.) =
  24   32   36  ...   173  172  173
  25   34   29  ...   173  172  173
  26   29   25  ...   172  172  174
     ...         ⋱        ...
 159  185  157  ...   157  156  153
 136  157  187  ...   152  152  150
 145  130  161  ...   142  143  142
    ⋮

(1 ,0 ,.,.) =
  20   17   19  ...   187  176  162
  22   17   17  ...   195  180  171
  17   17   18  ...   203  193  175
     ...         ⋱        ...
   1    1    1  ...   106  115  119
   2    2    1  ...   103  111  119
   2    2    2  ...    99  107  118
[torch.LongTensor of size 2x1x48x48]
, 'label':
1
1
[torch.LongTensor of size 2]
}

Теперь, когда вы убедились, что конвейер данных работает, вернитесь к + step_7_fer_simple.py +, чтобы добавить нейронную сеть и оптимизатор. Откройте + step_7_fer_simple.py +.

nano step_7_fer_simple.py

Сначала удалите последние три строки, которые вы добавили в предыдущей итерации:

step_7_fer_simple.py

# Delete all three lines

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

step_7_fer_simple.py

class Net(nn.Module):
   def __init__(self):
       super(Net, self).__init__()
       self.conv1 = nn.Conv2d(1, 6, 5)
       self.pool = nn.MaxPool2d(2, 2)
       self.conv2 = nn.Conv2d(6, 6, 3)
       self.conv3 = nn.Conv2d(6, 16, 3)
       self.fc1 = nn.Linear(16 * 4 * 4, 120)
       self.fc2 = nn.Linear(120, 48)
       self.fc3 = nn.Linear(48, 3)

   def forward(self, x):
       x = self.pool(F.relu(self.conv1(x)))
       x = self.pool(F.relu(self.conv2(x)))
       x = self.pool(F.relu(self.conv3(x)))
       x = x.view(-1, 16 * 4 * 4)
       x = F.relu(self.fc1(x))
       x = F.relu(self.fc2(x))
       x = self.fc3(x)
       return x

Теперь инициализируйте нейронную сеть, определите функцию потерь и определите гиперпараметры оптимизации, добавив следующий код в конец скрипта:

step_7_fer_simple.py

net = Net().float()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

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

Сначала извлеките + image и` + label` из загрузчика набора данных, а затем оберните каждый в PyTorch + Variable. Во-вторых, выполните прямой проход, а затем выполните обратное распространение через потери и нейронную сеть. Для этого добавьте следующий код в конец скрипта:

step_7_fer_simple.py

for epoch in range(2):  # loop over the dataset multiple times

   running_loss = 0.0
   for i, data in enumerate(trainloader, 0):
       inputs = Variable(data['image'].float())
       labels = Variable(data['label'].long())
       optimizer.zero_grad()

       # forward + backward + optimize
       outputs = net(inputs)
       loss = criterion(outputs, labels)
       loss.backward()
       optimizer.step()

       # print statistics
       running_loss += loss.data[0]
       if i % 100 == 0:
           print('[%d, %5d] loss: %.3f' % (epoch, i, running_loss / (i + 1)))

Теперь ваш скрипт должен выглядеть так:

step_7_fer_simple.py

from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torch
import cv2
import argparse


class Fer2013Dataset(Dataset):
   """Face Emotion Recognition dataset.

   Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier
   and Aaron Courville in 2013.

   Each sample is 1 x 1 x 48 x 48, and each label is a scalar.
   """
   def __init__(self, path: str):
       """
       Args:
           path: Path to `.np` file containing sample nxd and label nx1
       """
       with np.load(path) as data:
           self._samples = data['X']
           self._labels = data['Y']
       self._samples = self._samples.reshape((-1, 1, 48, 48))

       self.X = Variable(torch.from_numpy(self._samples)).float()
       self.Y = Variable(torch.from_numpy(self._labels)).float()

   def __len__(self):
       return len(self._labels)


   def __getitem__(self, idx):
       return {'image': self._samples[idx], 'label': self._labels[idx]}


trainset = Fer2013Dataset('data/fer2013_train.npz')
trainloader = torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=True)

testset = Fer2013Dataset('data/fer2013_test.npz')
testloader = torch.utils.data.DataLoader(testset, batch_size=32, shuffle=False)


class Net(nn.Module):
   def __init__(self):
       super(Net, self).__init__()
       self.conv1 = nn.Conv2d(1, 6, 5)
       self.pool = nn.MaxPool2d(2, 2)
       self.conv2 = nn.Conv2d(6, 6, 3)
       self.conv3 = nn.Conv2d(6, 16, 3)
       self.fc1 = nn.Linear(16 * 4 * 4, 120)
       self.fc2 = nn.Linear(120, 48)
       self.fc3 = nn.Linear(48, 3)

   def forward(self, x):
       x = self.pool(F.relu(self.conv1(x)))
       x = self.pool(F.relu(self.conv2(x)))
       x = self.pool(F.relu(self.conv3(x)))
       x = x.view(-1, 16 * 4 * 4)
       x = F.relu(self.fc1(x))
       x = F.relu(self.fc2(x))
       x = self.fc3(x)
       return x

net = Net().float()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)


for epoch in range(2):  # loop over the dataset multiple times

   running_loss = 0.0
   for i, data in enumerate(trainloader, 0):
       inputs = Variable(data['image'].float())
       labels = Variable(data['label'].long())
       optimizer.zero_grad()

       # forward + backward + optimize
       outputs = net(inputs)
       loss = criterion(outputs, labels)
       loss.backward()
       optimizer.step()

       # print statistics
       running_loss += loss.data[0]
       if i % 100 == 0:
           print('[%d, %5d] loss: %.3f' % (epoch, i, running_loss / (i + 1)))

Сохраните файл и выйдите из редактора после проверки кода. Затем запустите обучение для проверки концепции:

python step_7_fer_simple.py

Вы увидите вывод, похожий на следующий, как в нейронной сети:

Output[0,     0] loss: 1.094
[0,   100] loss: 1.049
[0,   200] loss: 1.009
[0,   300] loss: 0.963
[0,   400] loss: 0.935
[1,     0] loss: 0.760
[1,   100] loss: 0.768
[1,   200] loss: 0.775
[1,   300] loss: 0.776
[1,   400] loss: 0.767

Затем вы можете дополнить этот сценарий с помощью ряда других утилит PyTorch для сохранения и загрузки моделей, вывода результатов обучения и проверки достоверности, точной настройки расписания скорости обучения и т. Д. После обучения в течение 20 эпох с темпом обучения 0,01 и импульсом 0,9 наша нейронная сеть достигает точности поезда 87,9% и точности проверки 75,5%, что на 6,8% больше по сравнению с наиболее успешным подходом наименьших квадратов на данный момент - 66,6%. , Мы добавим эти дополнительные навороты в новый сценарий.

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

nano step_7_fer.py

Начните со следующего импорта. Это соответствует нашему предыдущему файлу, но дополнительно включает OpenCV как + import cv2. +

step_7_fer.py

from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torch
import cv2
import argparse

Непосредственно под этими импортами повторно используйте ваш код из + step_7_fer_simple.py +, чтобы определить нейронную сеть:

step_7_fer.py

class Net(nn.Module):
   def __init__(self):
       super(Net, self).__init__()
       self.conv1 = nn.Conv2d(1, 6, 5)
       self.pool = nn.MaxPool2d(2, 2)
       self.conv2 = nn.Conv2d(6, 6, 3)
       self.conv3 = nn.Conv2d(6, 16, 3)
       self.fc1 = nn.Linear(16 * 4 * 4, 120)
       self.fc2 = nn.Linear(120, 48)
       self.fc3 = nn.Linear(48, 3)

   def forward(self, x):
       x = self.pool(F.relu(self.conv1(x)))
       x = self.pool(F.relu(self.conv2(x)))
       x = self.pool(F.relu(self.conv3(x)))
       x = x.view(-1, 16 * 4 * 4)
       x = F.relu(self.fc1(x))
       x = F.relu(self.fc2(x))
       x = self.fc3(x)
       return x

Снова, повторно используйте код для набора данных Распознавания эмоций лица из + step_7_fer_simple.py + и добавьте его в этот файл:

step_7_fer.py

class Fer2013Dataset(Dataset):
   """Face Emotion Recognition dataset.
   Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier
   and Aaron Courville in 2013.
   Each sample is 1 x 1 x 48 x 48, and each label is a scalar.
   """

   def __init__(self, path: str):
       """
       Args:
           path: Path to `.np` file containing sample nxd and label nx1
       """
       with np.load(path) as data:
           self._samples = data['X']
           self._labels = data['Y']
       self._samples = self._samples.reshape((-1, 1, 48, 48))

       self.X = Variable(torch.from_numpy(self._samples)).float()
       self.Y = Variable(torch.from_numpy(self._labels)).float()

   def __len__(self):
       return len(self._labels)

   def __getitem__(self, idx):
       return {'image': self._samples[idx], 'label': self._labels[idx]}

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

step_7_fer.py

def evaluate(outputs: Variable, labels: Variable, normalized: bool=True) -> float:
   """Evaluate neural network outputs against non-one-hotted labels."""
   Y = labels.data.numpy()
   Yhat = np.argmax(outputs.data.numpy(), axis=1)
   denom = Y.shape[0] if normalized else 1
   return float(np.sum(Yhat == Y) / denom)

Затем добавьте функцию с именем + batch_evaluate +, которая применяет первую функцию ко всем изображениям:

step_7_fer.py

def batch_evaluate(net: Net, dataset: Dataset, batch_size: int=500) -> float:
   """Evaluate neural network in batches, if dataset is too large."""
   score = 0.0
   n = dataset.X.shape[0]
   for i in range(0, n, batch_size):
       x = dataset.X[i: i + batch_size]
       y = dataset.Y[i: i + batch_size]
       score += evaluate(net(x), y, False)
   return score / n

Теперь определите функцию с именем + get_image_to_emotion_predictor +, которая принимает изображение и выводит предсказанную эмоцию, используя предварительно обученную модель:

step_7_fer.py

def get_image_to_emotion_predictor(model_path='assets/model_best.pth'):
   """Returns predictor, from image to emotion index."""
   net = Net().float()
   pretrained_model = torch.load(model_path)
   net.load_state_dict(pretrained_model['state_dict'])

   def predictor(image: np.array):
       """Translates images into emotion indices."""
       if image.shape[2] > 1:
           image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
       frame = cv2.resize(image, (48, 48)).reshape((1, 1, 48, 48))
       X = Variable(torch.from_numpy(frame)).float()
       return np.argmax(net(X).data.numpy(), axis=1)[0]
   return predictor

Наконец, добавьте следующий код, чтобы определить функцию + main + для использования других утилит:

step_7_fer.py

def main():
   trainset = Fer2013Dataset('data/fer2013_train.npz')
   testset = Fer2013Dataset('data/fer2013_test.npz')
   net = Net().float()

   pretrained_model = torch.load("assets/model_best.pth")
   net.load_state_dict(pretrained_model['state_dict'])

   train_acc = batch_evaluate(net, trainset, batch_size=500)
   print('Training accuracy: %.3f' % train_acc)
   test_acc = batch_evaluate(net, testset, batch_size=500)
   print('Validation accuracy: %.3f' % test_acc)


if __name__ == '__main__':
   main()

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

Дважды проверьте, что ваш файл соответствует следующему:

step_7_fer.py

from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torch
import cv2
import argparse

class Net(nn.Module):
   def __init__(self):
       super(Net, self).__init__()
       self.conv1 = nn.Conv2d(1, 6, 5)
       self.pool = nn.MaxPool2d(2, 2)
       self.conv2 = nn.Conv2d(6, 6, 3)
       self.conv3 = nn.Conv2d(6, 16, 3)
       self.fc1 = nn.Linear(16 * 4 * 4, 120)
       self.fc2 = nn.Linear(120, 48)
       self.fc3 = nn.Linear(48, 3)

   def forward(self, x):
       x = self.pool(F.relu(self.conv1(x)))
       x = self.pool(F.relu(self.conv2(x)))
       x = self.pool(F.relu(self.conv3(x)))
       x = x.view(-1, 16 * 4 * 4)
       x = F.relu(self.fc1(x))
       x = F.relu(self.fc2(x))
       x = self.fc3(x)
       return x


class Fer2013Dataset(Dataset):
   """Face Emotion Recognition dataset.
   Utility for loading FER into PyTorch. Dataset curated by Pierre-Luc Carrier
   and Aaron Courville in 2013.
   Each sample is 1 x 1 x 48 x 48, and each label is a scalar.
   """

   def __init__(self, path: str):
       """
       Args:
           path: Path to `.np` file containing sample nxd and label nx1
       """
       with np.load(path) as data:
           self._samples = data['X']
           self._labels = data['Y']
       self._samples = self._samples.reshape((-1, 1, 48, 48))

       self.X = Variable(torch.from_numpy(self._samples)).float()
       self.Y = Variable(torch.from_numpy(self._labels)).float()

   def __len__(self):
       return len(self._labels)

   def __getitem__(self, idx):
       return {'image': self._samples[idx], 'label': self._labels[idx]}


def evaluate(outputs: Variable, labels: Variable, normalized: bool=True) -> float:
   """Evaluate neural network outputs against non-one-hotted labels."""
   Y = labels.data.numpy()
   Yhat = np.argmax(outputs.data.numpy(), axis=1)
   denom = Y.shape[0] if normalized else 1
   return float(np.sum(Yhat == Y) / denom)


def batch_evaluate(net: Net, dataset: Dataset, batch_size: int=500) -> float:
   """Evaluate neural network in batches, if dataset is too large."""
   score = 0.0
   n = dataset.X.shape[0]
   for i in range(0, n, batch_size):
       x = dataset.X[i: i + batch_size]
       y = dataset.Y[i: i + batch_size]
       score += evaluate(net(x), y, False)
   return score / n


def get_image_to_emotion_predictor(model_path='assets/model_best.pth'):
   """Returns predictor, from image to emotion index."""
   net = Net().float()
   pretrained_model = torch.load(model_path)
   net.load_state_dict(pretrained_model['state_dict'])

   def predictor(image: np.array):
       """Translates images into emotion indices."""
       if image.shape[2] > 1:
           image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
       frame = cv2.resize(image, (48, 48)).reshape((1, 1, 48, 48))
       X = Variable(torch.from_numpy(frame)).float()
       return np.argmax(net(X).data.numpy(), axis=1)[0]
   return predictor


def main():
   trainset = Fer2013Dataset('data/fer2013_train.npz')
   testset = Fer2013Dataset('data/fer2013_test.npz')
   net = Net().float()

   pretrained_model = torch.load("assets/model_best.pth")
   net.load_state_dict(pretrained_model['state_dict'])

   train_acc = batch_evaluate(net, trainset, batch_size=500)
   print('Training accuracy: %.3f' % train_acc)
   test_acc = batch_evaluate(net, testset, batch_size=500)
   print('Validation accuracy: %.3f' % test_acc)


if __name__ == '__main__':
   main(

Сохраните файл и выйдите из редактора.

Как и раньше, с помощью детектора лица загрузите предварительно обученные параметры модели и сохраните их в папке + assets + с помощью следующей команды:

wget -O assets/model_best.pth https://github.com/alvinwan/emotion-based-dog-filter/raw/master/src/assets/model_best.pth

Запустите скрипт для использования и оценки предварительно обученной модели:

python step_7_fer.py

Это выведет следующее:

OutputTraining accuracy: 0.879
Validation accuracy: 0.755

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

Шаг 8 - Завершение собачьего фильтра на основе эмоций

Прежде чем интегрировать наш новый классификатор эмоций лица, нам понадобятся маски для животных. Мы будем использовать маску Dalmation и маску Sheepdog:

image: https: //assets.digitalocean.com/articles/python3_dogfilter/HveFdkg.png [Маска далмации] + image: https: //assets.digitalocean.com/articles/python3_dogfilter/E9ax7PI.png [Маска овчарки]

Выполните эти команды, чтобы загрузить обе маски в папку + assets:

wget -O assets/dalmation.png https://assets.digitalocean.com/articles/python3_dogfilter/E9ax7PI.png  # dalmation
wget -O assets/sheepdog.png https://assets.digitalocean.com/articles/python3_dogfilter/HveFdkg.png  # sheepdog

Теперь давайте использовать маски в нашем фильтре. Начните с дублирования файла + step_4_dog_mask.py +:

cp step_4_dog_mask.py step_8_dog_emotion_mask.py

Откройте новый скрипт Python.

nano step_8_dog_emotion_mask.py

Вставьте новую строку в верхней части скрипта, чтобы импортировать предиктор эмоций:

step_8_dog_emotion_mask.py

from step_7_fer import get_image_to_emotion_predictor
...

Затем в функции + main () + найдите эту строку:

step_8_dog_emotion_mask.py

   mask = cv2.imread('assets/dog.png')

Замените его следующим, чтобы загрузить новые маски и объединить все маски в кортеж:

step_8_dog_emotion_mask.py

   mask0 = cv2.imread('assets/dog.png')
   mask1 = cv2.imread('assets/dalmation.png')
   mask2 = cv2.imread('assets/sheepdog.png')
   masks = (mask0, mask1, mask2)

Добавьте разрыв строки, а затем добавьте этот код, чтобы создать предиктор эмоций.

step_8_dog_emotion_mask.py

   # get emotion predictor
   predictor = get_image_to_emotion_predictor()

Ваша функция + main + теперь должна соответствовать следующему:

step_8_dog_emotion_mask.py

def main():
   cap = cv2.VideoCapture(0)

   # load mask
   mask0 = cv2.imread('assets/dog.png')
   mask1 = cv2.imread('assets/dalmation.png')
   mask2 = cv2.imread('assets/sheepdog.png')
   masks = (mask0, mask1, mask2)

   # get emotion predictor
   predictor = get_image_to_emotion_predictor()

   # initialize front face classifier
   ...

Далее найдите эти строки:

step_8_dog_emotion_mask.py

           # apply mask
           frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask)

Вставьте следующую строку ниже строки + # apply mask +, чтобы выбрать подходящую маску с помощью предиктора:

step_8_dog_emotion_mask.py

           # apply mask

           frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask)

Готовый файл должен выглядеть так:

step_8_dog_emotion_mask.py

"""Test for face detection"""

from step_7_fer import get_image_to_emotion_predictor
import numpy as np
import cv2

def apply_mask(face: np.array, mask: np.array) -> np.array:
   """Add the mask to the provided face, and return the face with mask."""
   mask_h, mask_w, _ = mask.shape
   face_h, face_w, _ = face.shape

   # Resize the mask to fit on face
   factor = min(face_h / mask_h, face_w / mask_w)
   new_mask_w = int(factor * mask_w)
   new_mask_h = int(factor * mask_h)
   new_mask_shape = (new_mask_w, new_mask_h)
   resized_mask = cv2.resize(mask, new_mask_shape)

   # Add mask to face - ensure mask is centered
   face_with_mask = face.copy()
   non_white_pixels = (resized_mask < 250).all(axis=2)
   off_h = int((face_h - new_mask_h) / 2)
   off_w = int((face_w - new_mask_w) / 2)
   face_with_mask[off_h: off_h+new_mask_h, off_w: off_w+new_mask_w][non_white_pixels] = \
        resized_mask[non_white_pixels]

   return face_with_mask

def main():

   cap = cv2.VideoCapture(0)
   # load mask
   mask0 = cv2.imread('assets/dog.png')
   mask1 = cv2.imread('assets/dalmation.png')
   mask2 = cv2.imread('assets/sheepdog.png')
   masks = (mask0, mask1, mask2)

   # get emotion predictor
   predictor = get_image_to_emotion_predictor()

   # initialize front face classifier
   cascade = cv2.CascadeClassifier("assets/haarcascade_frontalface_default.xml")

   while True:
       # Capture frame-by-frame
       ret, frame = cap.read()
       frame_h, frame_w, _ = frame.shape

       # Convert to black-and-white
       gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
       blackwhite = cv2.equalizeHist(gray)

       rects = cascade.detectMultiScale(
           blackwhite, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30),
           flags=cv2.CASCADE_SCALE_IMAGE)

       for x, y, w, h in rects:
           # crop a frame slightly larger than the face
           y0, y1 = int(y - 0.25*h), int(y + 0.75*h)
           x0, x1 = x, x + w
           # give up if the cropped frame would be out-of-bounds
           if x0 < 0 or y0 < 0 or x1 > frame_w or y1 > frame_h:
               continue
           # apply mask
           mask = masks[predictor(frame[y:y+h, x: x+w])]
           frame[y0: y1, x0: x1] = apply_mask(frame[y0: y1, x0: x1], mask)

       # Display the resulting frame
       cv2.imshow('frame', frame)
       if cv2.waitKey(1) & 0xFF == ord('q'):
           break

   cap.release()
   cv2.destroyAllWindows()

if __name__ == '__main__':
   main()

Сохраните и выйдите из редактора. Теперь запустите скрипт:

python step_8_dog_emotion_mask.py

Теперь попробуйте! Улыбающийся зарегистрируется как «счастливый» и покажет оригинальную собаку. Нейтральное лицо или хмурый взгляд будут зарегистрированы как «грустные» и приведут к далматину. Лицо «удивления» с большой каплей челюсти даст овчарку.

изображение: https: //assets.digitalocean.com/articles/python3_dogfilter/JPavHJl.gif [GIF для собачьего фильтра на основе эмоций]

Это завершает наш собачий фильтр на основе эмоций и вылазку в компьютерное зрение.

Заключение

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

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

Например, представьте себе систему поиска работы, где модели обучались с данными о кандидатах. такие как раса, пол, возраст, культура, родной язык или другие факторы. И, возможно, разработчики обучили модели, которая обеспечивает разреженность, которая в итоге сводит пространство функций к подпространству, где пол объясняет большую часть различий. В результате модель влияет на поиск работы кандидата и даже на процессы выбора компании, основанные в основном на гендерной проблематике. Теперь рассмотрим более сложные ситуации, когда модель менее интерпретируема, и вы не знаете, что соответствует определенной функции. Вы можете узнать больше об этом в Equality of Opportunity в машинном обучении профессора Moritz Hardt из Калифорнийского университета в Беркли.

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

Related