Распознавание маски на лице с помощью YOLOv3
- вторник, 20 апреля 2021 г. в 00:36:00
YOLO или You Only Look Once — это архитектура свёрточных нейронных сетей, которая используется для распознавания множественных объектов на изображении. В 2020, на фоне пандемии, задача детектирования объектов (object detection) на изображении стала как никогда актуальной. Эта статья даёт полное пошаговое руководство для тех, кто хочет научиться распознавать объекты с помощью YOLO на разных данных. Предполагается, что вы уже знаете, как делать распознавание объектов с помощью методов глубокого обучения и, в частности, вы знаете основы YOLO, так что давайте погрузимся в нашу задачу.
Я собираюсь работать с YOLOv3 — одной из самых ходовых версий YOLO, которая включает в себя современную, удивительно точную и быструю систему обнаружения объектов в реальном времени. Новые версии, такие как YOLOv4, YOLOv5, могут достичь даже лучших результатов. Этот проект вы найдёте в моём репозитории на Github.
Чтобы реализовать проект, я использовал Google Colab. Первые эксперименты с предварительной обработкой не были вычислительно дорогими, поэтому выполнялись на моём ноутбуке, но модель обучалась на GPU Google Colab.
Для начала, чтобы сделать детектор маски, нужны соответствующие данные. Кроме того, из-за природы YOLO нужны аннотированные данные с ограничивающими прямоугольниками. Один из вариантов — создать собственный набор данных, либо собирая изображения из Интернета, либо фотографируя друзей, знакомых и аннотируя фотографии вручную с помощью определённых программ, таких как LabelImg. Однако оба варианта утомительные и трудоёмкие, особенно последний. Есть другой вариант, самый жизнеспособный для моей цели, — работать с публичным набором данных.
Я выбрал Набор обнаружения маски на лице от Kaggle и загрузил его прямо в мой Google Drive. Посмотрите здесь, как это можно сделать. Скачанный набор данных — это две папки:
images, содержит 853 .png файла;
annotations, содержит 853 соответствующих аннотации в формате XML.
После загрузки набора данных, чтобы тренировать нашу модель, нам нужно преобразовать файлы .xml в .txt, а если точнее, в формат YOLO. Пример:
<annotation>
<folder>images</folder>
<filename>maksssksksss0.png</filename>
<size>
<width>512</width>
<height>366</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>without_mask</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<occluded>0</occluded>
<difficult>0</difficult>
<bndbox>
<xmin>79</xmin>
<ymin>105</ymin>
<xmax>109</xmax>
<ymax>142</ymax>
</bndbox>
</object>
<object>
<name>with_mask</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<occluded>0</occluded>
<difficult>0</difficult>
<bndbox>
<xmin>185</xmin>
<ymin>100</ymin>
<xmax>226</xmax>
<ymax>144</ymax>
</bndbox>
</object>
<object>
<name>without_mask</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<occluded>0</occluded>
<difficult>0</difficult>
<bndbox>
<xmin>325</xmin>
<ymin>90</ymin>
<xmax>360</xmax>
<ymax>141</ymax>
</bndbox>
</object>
</annotation>
Предположим, что это аннотация изображения, содержащего только 3 ограничительных прямоугольника, это видно по количеству <object> ... </object> в XML.
Чтобы создать подходящий текстовый файл, нам нужно 5 типов данных из каждого XML-файла. Для каждого <object> ... </object> в XML-файле извлеките класс, то есть поле <name>...</name> и координаты ограничивающего прямоугольника — 4 атрибута в <bndbox>...</bndbox>. Подходящий формат выглядит так:
<class_name> <x_center> <y_center> <width> <height>
Я написал скрипт, который извлекает 5 атрибутов каждого объекта в каждом XML-файле и создаёт соответствующие файлы TXT. Подробные комментарии о подходе к преобразованию вы найдёте в моём скрипте. К примеру, у Image1.jpg должен быть соответствующий Image1.txt, вот такой:
1 0.18359375 0.337431693989071 0.05859375 0.10109289617486339
0 0.4013671875 0.3333333333333333 0.080078125 0.12021857923497267
1 0.6689453125 0.3155737704918033 0.068359375 0.13934426229508196
Это точное преобразование вышеупомянутого файла .xml в подходящий текст.
Примечание: очень важно сгруппировать изображения и соответствующие TXT в одну папку.
Конечно, прежде чем приступить к обучению модели, мы должны быть абсолютно уверены, что преобразование было правильным, потому что хотим подать модели достоверные данные. Чтобы гарантировать соответствие, я написал скрипт, который берёт изображение и соответствующую ему текстовую аннотацию из заданной папки и отображает взятое изображение с ограничивающими прямоугольниками. Вот что получилось:
Пока всё хорошо, давайте продолжим.
Чтобы обучить нашу модель и проверить её на этапе обучения, мы должны разделить данные на два набора — набор обучения и набор тестирования. Пропорция составила 90 и 10 % соответственно. Поэтому я создал две новые папки и поместил 86 изображений с аннотациями в test_folder, а остальные 767 изображений — в train_folder.
Следующий шаг — клонировать репозиторий darknet с помощью команды:
!git clone https://github.com/AlexeyAB/darknet
После этого нам нужно загрузить веса предварительно обученной модели, то есть применить трансферное обучение, а не обучать модель с нуля.
!wget https://pjreddie.com/media/files/darknet53.conv.74
darknet53.conv.74 — основа сети YOLOv3, которая вначале обучается классификации на наборе данных ImageNet и играет роль экстрактора. Чтобы использовать её для распознавания, дополнительные веса сети YOLOv3 перед обучением инициализируются случайным образом. Но, конечно, на этапе обучения сеть получит надлежащие веса.
Чтобы завершить подготовку и начать обучение модели, нужно создать пять файлов.
face_mask.names: создайте содержащий классы задачи файл _.names.
В нашем случае исходный набор данных Kaggle имеет 3 категории: with_mask, without_mask и mask_weared_incorrect [с маской, без маски, маска надета неправильно].
Чтобы немного упростить задачу, я объединил две последние категории в одну. Итак, есть две категории, Good и Bad, на основании того, правильно ли кто-то носит свою маску:
1. Good.
2. Bad.
face_mask.data: создайте файл _.data, который содержит релевантную информацию о нашей задаче, с ним будет работать программа:
classes = 2
train = data/train.txt
valid = data/test.txt
names = data/face_mask.names
backup = backup/
Примечание: если папки резервного копирования нет, создайте её, потому что там будут сохраняться веса за каждую тысячу итераций. На самом деле это будут ваши контрольные точки на случай, если обучение неожиданно прервётся; если что, вы сможете продолжить тренировать модель.
3. face_mask.cfg: Этот конфигурационный файл должен быть адаптирован к нашей задаче, а именно нам нужно скопировать yolov3.cfg переименовать его в _.cfg и изменить код, как описано ниже:
строку batch на batch=64;
строку subdivisions на subdivisions=16. В случае проблемы с памятью увеличьте это значение до 32 или 64;
входные размеры на стандартные: width=416, height=416;
строку max_batches на (#classes * 2000), это даст 4000 итераций для нашей задачи.
Я начал с разрешения 416x416 и обучил свою модель на 4000 итераций, но, чтобы достичь большей точности, увеличил разрешение и продлил обучение ещё на 3000 итераций. Если у вас есть только одна категория, вы не должны тренировать свою модель только до 2000 итераций. Предполагается, что 4000 итераций — это минимум.
измените строку steps на 80% и 90% max_batches. В нашем случае 80/100 * 4000 = 3200, 90 / 100 * 4000 = 3600;
нажмите Ctrl+F и найдите слово "yolo". Поиск приведёт прямо к yolo_layers, где вы измените количество классов (в нашем случае classes=2) и количество фильтров. Переменная filters — это вторая переменная выше строки [yolo].
Строка должна стать такой: filters=(classes + 5) * 3, в нашем случае это filters = (2 + 5) * 3 = 21. В файле .cfg есть 3 слоя yolo_layers, поэтому упомянутые выше изменения нужно выполнить трижды.
4. Файлы train.txt и test.txt: Эти два файла были включены в файл face_mask.data и указывают абсолютный путь каждого изображения к модели. Например, фрагмент моего файла train.txt выглядит так:
/content/gdrive/MyDrive/face_mask_detection/mask_yolo_train/maksssksksss734.png
/content/gdrive/MyDrive/face_mask_detection/mask_yolo_train/maksssksksss735.png
/content/gdrive/MyDrive/face_mask_detection/mask_yolo_train/maksssksksss736.png
/content/gdrive/MyDrive/face_mask_detection/mask_yolo_train/maksssksksss737.png
/content/gdrive/MyDrive/face_mask_detection/mask_yolo_train/maksssksksss738.png
...
Как я уже говорил, файлы .png должны располагаться в одной папке с соответствующими текстовыми аннотациями.
Это означает, что проект структурирован так:
MyDrive
├──darknet
├──...
├──backup
├──...
├──cfg
├──face_mask.cfg
├──...
├──data
├──face_mask.data
├──face_mask.names
├──train.txt
├──test.txt
├──face_mask_detection
├──annotations (contains original .xml files)
├──images (contains the original .png images)
├──mask_yolo_test (contains .png % .txt files for testing)
├──mask_yolo_train (contains .png % .txt files for training)
├── show_bb.py
└── xml_to_yolo.py
После компиляции модели нужно изменить права на папку darknet, вот так:
!chmod +x ./darknet
И начинаем тренировать модель, запустив такую команду:
!./darknet detector train data/face_mask.data cfg/face_mask.cfg backup/face_mask_last.weights -dont_show -i 0 -map
Прописываем флаг -map, чтобы в консоль выводились важные показатели, такие как average Loss, Precision, Recall, AveragePrecision (AP), meanAveragePrecsion (mAP) и т. д.
Однако индикатор mAP в консоли считается лучшей метрикой, чем Loss, поэтому обучайте модель до тех пор, пока mAP возрастает.
В зависимости от различных параметров обучение может занимать часы, это нормально. Мне понадобилось около 15 часов, но первые впечатления от модели я получил примерно после 7 часов, то есть 4000 итераций.
Модель готова к демонстрации. Давайте попробуем использовать изображения, которые она никогда раньше не видела. Для этого нужно запустить такие команды:
!./darknet detector test data/face_mask.data cfg/face_mask.cfg backup/face_mask_best.weights
Вы заметили, что мы использовали face_mask_best.weights, а не face_mask_final.weights? К счастью, наша модель сохраняет лучшие веса (mAP достиг 87,16 %) в папке резервного копирования на случай, если мы тренируем её на большем количестве эпох, чем следовало бы (что, возможно, приведёт к переобучению).
Изображения ниже взяты из Pexels, набора изображений высокого разрешения, и невооружённым глазом видно, что они существенно отличаются от тестового и тренировочного наборов данных и, таким образом, имеют другое распределение. Чтобы посмотреть, насколько модель способна к обобщению, я выбрал эти фото:
На изображениях выше модель сработала точно, и она довольно уверена в своих прогнозах. Примечательно, что изображение справа не запутало модель надетой на глобус маской: модель показывает, что прогнозы сделаны не только на основании того, надета ли маска, но и на основании контекста вокруг маски.
Два изображения выше, очевидно, показывают, что люди не носят маски, и модель, кажется, довольно легко распознаёт и это.
На двух примерах выше можно проверить производительность модели в случаях, когда на изображении есть люди в масках и без них. Модель может идентифицировать лица даже на размытом фоне, и этот факт вызывает восхищение.
Я заметил, что относительно стоящего впереди человека модель не столь уверена (38 % в чёткой области) в сравнении с прогнозом для человека сразу за ним (100 % в размытой области). Это может быть связано с качеством обучающего набора данных, таким образом, модель, по-видимому, в определённой степени подвержена влиянию (по крайней мере она не является неточной).
Конечно, большое преимущество Yolo — её скорость. Поэтому я хочу показать вам, как она работает с видео:
!./darknet detector demo data/face_mask.data cfg/face_mask.cfg backup/face_mask_best.weights -dont_show vid1.mp4 -i 0 -out_filename res1.avi
Это был мой первый пошаговый туториал о том, как сделать собственный детектор с помощью YOLOv3 на пользовательском наборе данных. Надеюсь, он был вам полезен. А если хотите научиться создавать собственные нейронные сети и решать задачи с помощью глубокого обучения — обратите внимание на курс Machine Learning и Deep Learning.
Узнайте, как прокачаться и в других специальностях или освоить их с нуля:
ПРОФЕССИИ
КУРСЫ