Русский
Русский
English
Статистика
Реклама

Yolo

Из песочницы Breakout-YOLO знакомимся с шустрой object-detection моделью, играя в классический Арканоид

21.06.2020 14:07:34 | Автор: admin


Всем привет! Весенний семестр для некоторых студентов 3-го курса ФУПМ МФТИ ознаменовался сдачей проектов по курсу Методы оптимизации. Каждый должен был выделить интересную для себя тему (или придумать свою) и воплотить её в жизнь в виде кода, научной статьи, численного эксперимента или даже бота в Telegram.


Жёстких ограничений на выбор темы не было, поэтому можно было дать разгуляться фантазии. You Only Live Once! воскликнул я, и решил использовать эту возможность, чтобы привнести немного огня в бессмертную классику.,


Выбор проекта




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


И тут я вспомнил, что год назад вместе с одногруппниками писал браузерный Арканоид на JavaScript. Почему бы не добавить в эту бородатую игрушку немного рок-н-ролла, а точнее модного нынче computer vision-а? Эта область ML представлялась мне довольно интересной и проект на эту тему стал бы прекрасной мотивацией для изучения.


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


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




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



Мне же хотелось перенести функцию управления на жесты пользователя. Управление платформой я утвердил следующее: её положение определяется положением некоторого жеста на видео с веб-камеры.


То есть естественным образом возникла задача real-time object-detection на вход алгоритму поступает кадр из видео, на выходе хотим иметь изображение с объектами, обведёнными в рамку (bounding box) прямо как на картинке ниже:



И все это нужно делать быстро, выдавая хотя бы 20-30 FPS, ведь никому не интересно играть в слайд-шоу. Не сомневаюсь, что причастные к CV люди по названию статьи догадались какую модель я выбрал, остальных же охотно приглашаю под следующий заголовок.


Tiny-YOLOv3




You Only Look Once


YOLO это популярная архитектура CNN для решения задачи object-detection:



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


За подробным описанием архитектуры отсылаю интересующегося читателя к оригинальной статье и её обзору от Deep Systems.


Better, Faster, Stronger


Одной из последних* версий YOLO является YOLOv3. От первоначальной модели эту отличает несколько новых фич: авторы отказались от fully-connected слоя в конце, таким образом остались только свёрточные слои. Для улучшения распознавания объектов разных размеров было добавлено 2 дополнительных выходных слоя, что напоминает концепцию feature pyramid.


Были задействованы anchor box наборы ограничительных рамок, а точнее их параметров, относительно которых сеть делает предсказания bounding box-ов. Для повышения точности предсказаний их нужно перевычислять отдельно для каждого датасета. Обычно это делают с помощью метода k-средних, кластеризуя множество bounding box-ов из трейна.


Для того, чтобы регуляризовать сеть, к каждому свёрточному слою был прикручен batch normalization. Также был применён multi-scale training случайное изменение разрешения входящего изображения при обучении.


Детальное описание всех изменений можно найти в статьях про YOLOv2 и YOLOv3 и обзоре.


Я же взял Tiny-YOLOv3 уменьшенную версию этой модели без последнего слоя:



В ней используется слой пулинга вместо свёрточного слоя с страйдом 2. Всё это делает её быстрее и компактнее (~ 35 Мб), а значит более предпочтительнее для моей задачи.


* на момент выбора модели. С тех пор успела выйти YOLOv4 и нечто, называющее себя YOLOv5.


Работа с данными




Перейдём к самой рабоче-крестьянской части проекта.


Выбор жестов и датасета


Для управления игрой были выбраны следующие 4 жеста:



Вопрос: на чем обучать модель?


Первое, что пришло в голову взять датасет какого-нибудь жестового языка, например американского: ASL Alphabet, Sign Language MNIST и обучится на нем.
Но меня отпугнуло следующее:


  • В отсмотренных мною датасетах все фотографии были сделаны на однородном фоне
  • Не все выбранные мною жесты были представлены в датасетах
  • Я не нашел именно object detection датасета с bounding box-ами, так что в любом случае пришлось бы доразмечать данные

Модели же предстояло работать на разных ноутбуках с разными веб-камерами в условиях если не агрессивного фона и плохого освещения, то как минимум силуэта человека на заднем плане. По этой причине (а также из-за своей неосведомлённости в deep learning) я не мог обоснованно сказать, как YOLO обученная на этих данных поведет себя в бою. К тому же сроки предзащиты проекта поджимали права на ошибку не было.


Поэтому я посчитал правильным действовать наверняка и решил...



собрать и разметить свой датасет.


Не имей сто рублей, а имей сто друзей


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


Как для достижения большего разнообразия в датасете, так и для облегчения собственных страданий пришлось прибегнуть к бесплатному краудсорсингу помощи друзей. Я попросил каждого из этих восьми несчастных записать на веб-камеру 2-ух минутное видео с 4 жестами по 15 секунд на жест каждой рукой. Сам я сделал 4 таких видео в разной одежде и на разных фонах.


Далее в дело вступал удобнейший GUI для разметки от AlexeyAB. С помощью него я нарезал видео на кадры и, набравшись терпения, сел размечать. Директорию с изображениями я предварительно синхронизировал с гугл-доком при помощи Insync, чтобы не терять время на перенос датасета в облако.


К концу 8-го часа разметки у меня имелось около 8000 изображений с аннотациями в .txt файлах, полное нежелание продолжать процесс и, наверное, парочка новых сидячих болезней. Поэтому, удовлетворившись проделанной работой, я пересчитал anchor box-ы, разделил данные на тест и трейн в жадном соотношении 1:9 и перешел к следующему этапу.


Обучение




Снаряжаемся видеокарточками


Времени до первого показа проекта оставалось всё меньше и меньше, поэтому я пошёл куда глаза глядят по пути наименьшего сопротивления и решил обучать модель в Darknet написанном на C и CUDA open-source фреймворке для создания и обучения нейронных сетей.


Вычислительных мощностей мне объективно не хватало из графических процессоров с поддержкой CUDA под рукой имелась только GeForce GT 740M (Compute Capability = 3.0), которой требовалось 10-12 часов для обучения Tiny-YOLOv3. Поэтому я стал счастливым обладателем подписки на Colab Pro с Tesla P100-PCIE-16GB под капотом, сократившей это время до 3 часов. Стоит отметить, что Colab уже давно и так бесплатно предоставляет всем желающим мощную Tesla T4, правда с ограничениями по времени использования. О них я узнал по всплывшей на экране надписи You cannot currently connect to a GPU due to usage limits in Colab, заигравшись с обучением YOLO на всяких кошечках-собачках, интереса ради.


Чтобы нащупать trade-off между точностью распознавания и быстродействием, я взял 4 модели: Tiny-YOLOv3 с размерами входного изображения 192x192, 256x256, 416x416 и XNOR Tiny-YOLOv3, в которой свёртки аппроксимируются двоичными операциями, что должно ускорять работу сети. Указанные Ёлки обучались в течение 8, 10, 12 и 16 тысяч итераций соответственно.


Метриками качества обученных моделей послужили mean Average Precision с IoU=0.5(далее обозначается как mAP@0,5) и FPS.


Результаты обучения



На графиках выше представлены зависимости значений лосс-функции на трейне и mAP@0.5 на тесте от номера итерации обучения.


Как видите, XNOR модели нездоровилось. Несмотря на финальный mAP@0.5 88%, при хорошем освещении она еле-еле распознавала пару жестов с минимальным confidence. FPS 15 также намекал, что что-то пошло не так. Это не могло не расстраивать, так как я возлагал на XNOR большие надежды. На момент написания этой статьи мне всё ещё не ясно, в чём была проблема, возможно я что-то напутал в .cfg файле.



Остальные же модели обучились приемлемо. Полученные значения FPS справедливы для следующей


конфигурации

ASUS Vivobook S15, i7 1.8GHz, GeForce MX250


В погоне за быстродействием для использования в игре я выбрал 192х192 модель.


Дабы не зависящий от размера входного изображения mAP@0.5 не вводил в заблуждение, надо сказать, что эта метрика в данной ситуации несколько обманчива.


Дело в том, что в собранном датасете жесты на фотографиях расположены примерно на одном расстоянии от камеры, поэтому модели переобучились на это расстояние. Это видно по тому, как 192х192 модель работает при сильном приближении/отдалении от веб-камеры она не умеет предсказывать очень большие/маленькие bounding box-ы, так как не видела таковых в датасете. Однако такое переобучение соответствует поставленной задаче распознаванию жестов на установленном расстоянии, удобном пользователю.


Таким образом, важно понимать, что mAP@0.5 здесь тесно связан с конкретной задачей, и на самом деле при уменьшении размера входного изображения модель теряет в точности распознавания очень близких/далеких к камере жестов, что, однако, практически не влияет на точность на тесте.


Let's play!




Немного про архитектуру


Для распознавания жестов в браузере я использовал библиотеку TensorFlow.js. Обученные веса сначала были сконвертированы в Keras, а затем преобразованы в TFJS Graph model.


Так выглядит демо-версия (загрузка может занять некоторое время):



Сама игра написана на Javascript, за основу был взят этот туториал. Её архитектура предельно проста и заключается лишь в перерисовке кадров с помощью setInterval. На некоторых девайсах наблюдался довольно низкий FPS, поэтому я установил модель распознавать жесты каждую 10-ую перерисовку.


Несмотря на это, всё равно могут быть заметны фризы в момент распознавания. Чтобы их избежать я планирую распараллелить распознавание и перерисовку с помощью Web Worker.


А вот теперь поиграем


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


Управление игрой:


  • Нажатие на кнопку Play/пауза в игре жест finger up
  • Перемещение курсора в меню/снятие с паузы жест circle
  • Перемещение платформы жест fist
  • Выстрел шариком с липкой платформы жест pistol

Поиграть можно здесь. Я рекомендую использовать FIrefox, но Chrome/Chromium также поддерживаются.


Так выглядит геймплей:



Вместо заключения




Внимательный читатель спросит: А причем тут собственно методы оптимизации? Да ни при чем! Но такая свобода выбора темы проекта дала стимул плотно начать разбираться в интересном разделе машинного обучения, и это здорово, на мой взгляд.


Sapere aude, хабровчане!


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





P. S.


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

Подробнее..

Перевод Распознавание маски на лице с помощью YOLOv3

19.04.2021 16:09:25 | Автор: admin

YOLO или You Only Look Once это архитектура свёрточных нейронных сетей, которая используется для распознавания множественных объектов на изображении. В 2020, на фоне пандемии, задача детектирования объектов (object detection) на изображении стала как никогда актуальной. Эта статья даёт полное пошаговое руководство для тех, кто хочет научиться распознавать объекты с помощью YOLO на разных данных. Предполагается, что вы уже знаете, как делать распознавание объектов с помощью методов глубокого обучения и, в частности, вы знаете основы YOLO, так что давайте погрузимся в нашу задачу.


Я собираюсь работать с YOLOv3 одной из самых ходовых версий YOLO, которая включает в себя современную, удивительно точную и быструю систему обнаружения объектов в реальном времени. Новые версии, такие как YOLOv4, YOLOv5, могут достичь даже лучших результатов. Этот проект вы найдёте в моём репозитории на Github.

Рабочее окружение

Чтобы реализовать проект, я использовал Google Colab. Первые эксперименты с предварительной обработкой не были вычислительно дорогими, поэтому выполнялись на моём ноутбуке, но модель обучалась на GPU Google Colab.

Активировать GPU в Colab можно в меню Edit->Notebook Settings.Активировать GPU в Colab можно в меню Edit->Notebook Settings.

Набор данных

Для начала, чтобы сделать детектор маски, нужны соответствующие данные. Кроме того, из-за природы 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.101092896174863390 0.4013671875 0.3333333333333333 0.080078125 0.120218579234972671 0.6689453125 0.3155737704918033 0.068359375 0.13934426229508196

Это точное преобразование вышеупомянутого файла .xml в подходящий текст.

Примечание: очень важно сгруппировать изображения и соответствующие TXT в одну папку.

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

Пока всё хорошо, давайте продолжим.

Разделение данных

Чтобы обучить нашу модель и проверить её на этапе обучения, мы должны разделить данные на два набора набор обучения и набор тестирования. Пропорция составила 90 и 10 % соответственно. Поэтому я создал две новые папки и поместил 86 изображений с аннотациями в test_folder, а остальные 767 изображений в train_folder.

Клонирование фреймворка darknet

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

!git clone https://github.com/AlexeyAB/darknet

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

!wget https://pjreddie.com/media/files/darknet53.conv.74

darknet53.conv.74 основа сети YOLOv3, которая вначале обучается классификации на наборе данных ImageNet и играет роль экстрактора. Чтобы использовать её для распознавания, дополнительные веса сети YOLOv3 перед обучением инициализируются случайным образом. Но, конечно, на этапе обучения сеть получит надлежащие веса.

Последний шаг

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

  1. face_mask.names: создайте содержащий классы задачи файл _.names.

В нашем случае исходный набор данных Kaggle имеет 3 категории: with_mask, without_mask и mask_weared_incorrect [с маской, без маски, маска надета неправильно].

Чтобы немного упростить задачу, я объединил две последние категории в одну. Итак, есть две категории, Good и Bad, на основании того, правильно ли кто-то носит свою маску:

1. Good.2. Bad.
  1. face_mask.data: создайте файл _.data, который содержит релевантную информацию о нашей задаче, с ним будет работать программа:

classes = 2train = data/train.txtvalid  = data/test.txtnames = data/face_mask.namesbackup = 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 должны располагаться в одной папке с соответствующими текстовыми аннотациями.

Это означает, что проект структурирован так:

MyDrivedarknet      ...      backup      ...      cfg            face_mask.cfg      ...      data            face_mask.data            face_mask.names            train.txt            test.txtface_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

Оптимизировано для HabraStorage, gif с потерями.Оптимизировано для HabraStorage, gif с потерями.

Это был мой первый пошаговый туториал о том, как сделать собственный детектор с помощью YOLOv3 на пользовательском наборе данных. Надеюсь, он был вам полезен. А если хотите научиться создавать собственные нейронные сети и решать задачи с помощью глубокого обучения обратите внимание на курс Machine Learning и Deep Learning.

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Scaled YOLO v4 самая лучшая нейронная сеть для обнаружения объектов на датасете MS COCO

07.12.2020 20:14:08 | Автор: admin

Scaled YOLO v4 является самой точной нейронной сетью (55.8% AP) на датасете Microsoft COCO среди всех опубликованных нейронных сетей на данный момент. А также является лучшей с точки зрения соотношения скорости к точности во всем диапазоне точности и скорости от 15 FPS до 1774 FPS. На данный момент это Top1 нейронная сеть для обнаружения объектов.

Scaled YOLO v4 обгоняет по точности нейронные сети:

  • Google EfficientDet D7x / DetectoRS or SpineNet-190 (self-trained on extra-data)
  • Amazon Cascade-RCNN ResNest200
  • Microsoft RepPoints v2
  • Facebook RetinaNet SpineNet-190




Мы показываем, что подходы YOLO и Cross-Stage-Partial (CSP) Network являются лучшими с точки зрения, как абсолютной точности, так и соотношения точности к скорости.

График Точности (вертикальная ось) и Задержки (горизонтальная ось) на GPU Tesla V100 (Volta) при batch=1 без использования TensorRT:



Даже при меньшем разрешении сети Scaled-YOLOv4-P6 (1280x1280) 30 FPS чуть точнее и в 3.7х раза быстрее, чем EfficientDetD7 (1536x1536) 8.2 FPS. Т.е. YOLOv4 эффективнее использует разрешение сети.

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

Scaled YOLOv4 точнее и быстрее, чем нейронные сети:
  • Google EfficientDet D0-D7x
  • Google SpineNet S49s S143
  • Baidu Paddle-Paddle PP YOLO
  • И многие другие


Scaled YOLO v4 это серия нейронных сетей, созданная из улучшенной и отмасштабированной сети YOLOv4. Наша нейронная сеть была обучена с нуля без использования предобученных весов (Imagenet или любых других).

Рейтинг точности опубликованных нейронных сетей: paperswithcode.com/sota/object-detection-on-coco


Скорость нейронной сети YOLOv4-tiny достигает 1774 FPS на игровой видеокарте GPU RTX 2080Ti при использовании TensorRT+tkDNN (batch=4, FP16): github.com/ceccocats/tkDNN

YOLOv4-tiny может исполняться в real-time со скоростью 39 FPS / 25ms Latency на JetsonNano (416x416, fp16, batch=1) tkDNN/TensorRT:


Scaled YOLOv4 намного эффективнее использует ресурсы параллельных вычислителей, таких как GPU и NPU. Например, GPU V100 (Volta) имеет производительность: 14 TFLops 112 TFLops-TC images.nvidia.com/content/technologies/volta/pdf/tesla-volta-v100-datasheet-letter-fnl-web.pdf

Если мы будем тестировать обе модели на GPU V100 с batch=1, с параметрами --hparams=mixed_precision=true и без --tensorrt=FP32, то:

  • YOLOv4-CSP (640x640) 47.5% AP 70 FPS 120 BFlops (60 FMA)
    Исходя из BFlops, должно быть 933 FPS = (112 000 / 120), но в действительности мы получаем 70 FPS, т.е. используется 7.5% GPU = (70 / 933)
  • EfficientDetD3 (896x896) 47.5% AP 36 FPS 50 BFlops (25 FMA)
    Исходя из BFlops, должно быть 2240 FPS = (112 000 / 50), но в действительности мы получаем 36 FPS, т.е. используется 1.6% GPU = (36 / 2240)


Т.е. эффективность вычислительных операций на устройствах с массивными параллельными вычислениями типа GPU, используемых в YOLOv4-CSP (7.5 / 1.6) = в 4.7x раза лучше, чем эффективность операций, используемых в EfficientDetD3.

Обычно нейронные сети запускаются на CPU только в исследовательских задачах для более легкой отладки, а характеристика BFlops на данный момент имеет только академический интерес. В реальных задачах важны реальные скорость и точность, а не характеристики на бумаге. Реальная скорость YOLOv4-P6 в 3.7х раза выше, чем EfficientDetD7 на GPU V100. Поэтому почти всегда используются устройства с массовым параллелизмом GPU / NPU / TPU/ DSP с гораздо более оптимальными: скоростью, ценой и тепловыделением:

  • Embedded GPU (Jetson Nano/Nx)
  • Mobile-GPU/NPU/DSP (Bionic-NPU/Snapdragon-DSP/Mediatek-APU/Kirin-NPU/Exynos-GPU/...)
  • TPU-Edge (Google Coral/Intel Myriad/Mobileye EyeQ5/Tesla-motors TPU 144 TOPS-8bit)
  • Cloud GPU (nVidia A100/V100/TitanV)
  • Cloud NPU (Google-TPU, Huawei Ascend, Intel Habana, Qualcomm AI 100, ...)


Также при использовании нейронных сетей On Web обычно используется GPU через библиотеки WebGL, WebAssembly, WebGPU, for this case the size of the model can matter: github.com/tensorflow/tfjs#about-this-repo

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

  • Текущий лучший размер Литографии процессоров (Semiconductor device fabrication) равен 5 нанометров.
  • Размер кристаллической решетки кремния равен 0.5 нанометров.
  • Атомный радиус кремния равен 0.1 нанометра.

Решение это вычислители с массивным параллелизмом: на одном кристалле или на нескольких кристаллах, соединенных интерпозером. Поэтому крайне важно создавать нейронные сети, которые эффективно используют вычислители с массивным параллелизмом, такие как GPU и NPU.

Улучшения в Scaled YOLOv4 по сравнению с YOLOv4:
  • В Scaled YOLOv4 применяли оптимальные способы масштабирования сети для получения YOLOv4-CSP -> P5 -> P6 -> P7 сетей
  • Улучшенная архитектура сети: оптимизирован Backbone, а также в Neck (PAN) используются Cross-stage-partial (CSP) connections и Mish-активация
  • Во время обучения используется Exponential Moving Average (EMA) это частный случай SWA: pytorch.org/blog/pytorch-1.6-now-includes-stochastic-weight-averaging
  • Для каждого разрешения сети обучается отдельная нейронная сеть (в YOLOv4 обучали только одну нейронную сеть для всех разрешений)
  • Улучшены нормализаторы в [yolo] слоях
  • Изменены активации для Width и Height, что позволяет быстрее обучать сеть
  • Используется параметр [net] letter_box=1 (сохраняет соотношение сторон входного изображения) для сетей с большим разрешением (для всех кроме yolov4-tiny.cfg)


Архитектура нейронной сети Scaled-YOLOv4 (примеры трех сетей: P5, P6, P7):


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


Простейший пример CSP-соединения (слева обычная сеть, справа CSP-сеть):


Пример CSP-соединения в YOLOv4-CSP / P5 / P6 / P7
(слева обычная сеть, справа CSP-сеть использу):


В YOLOv4-tiny используются 2 CSP-соединения (используя Partial Concatenation)


YOLOv4 применяется в различных областях и задачах:

И во многих других задачах.

Имеются реализации на различных фреймворках:


Как скомпилировать и запустить Обнаружение объектов в облаке бесплатно:
  1. colab: colab.research.google.com/drive/12QusaaRj_lUwCGDvQNfICpa7kA7_a2dE
  2. video: www.youtube.com/watch?v=mKAEGSxwOAY


Как скомпилировать и запустить Обучение в облаке бесплатно:
  1. colab: colab.research.google.com/drive/1_GdoqCJWXsChrOiY8sZMr_zbr_fH-0Fg?usp=sharing
  2. video: youtu.be/mmj3nxGT2YQ


Также подход YOLOv4 может использоваться в других задачах, например, при обнаружении 3D объектов:


Подробнее..

Обнаружение объектов с помощью YOLOv3 на Tensorflow 2.0

08.05.2021 14:13:54 | Автор: admin
Кадр из аниме "Жрица и медведь"Кадр из аниме "Жрица и медведь"

До появления YOLO большинство способов обнаружения объектов пытались адаптировать классификаторы для детекции. В YOLO же, обнаружение объектов было сформулировано как задача регрессии на пространственно разделенных ограничивающих рамок (bounding boxes) и связанных с ними вероятностей классов.

В данной статье мы узнаем о системе YOLO Object Detection и как реализовать подобную систему в Tensorflow 2.0

О YOLO:

Наша унифицированная архитектура чрезвычайно быстра. Базовая модель YOLO обрабатывает изображения в режиме реального времени со скоростью 45 кадров в секунду. Уменьшенная версия сети, Fast YOLO, обрабатывает аж 155 кадра в секунду

You Only Look Once: Unified, Real-Time Object Detection, 2015

Что такое YOLO?

YOLO это новейшая (на момент написания оригинальной статьи) система (сеть) обнаружения объектов. Она была разработана Джозефом Редмоном (Joseph Redmon). Наибольшим преимуществом YOLO над другими архитектурами является скорость. Модели семейства YOLO исключительно быстры и намного превосходят R-CNN (Region-Based Convolutional Neural Network) и другие модели. Это позволяет добиться обнаружения объектов в режиме реального времени.

На момент первой публикации (в 2016 году) по сравнению с другими системами, такими как R-CNN и DPM (Deformable Part Model), YOLO добилась передового значения mAP (mean Average Precision). С другой стороны, YOLO испытывает трудности с точной локализацией объектов. Однако в новой версии были внесены улучшения в скорости и точности системы.

Альтернативы (на момент публикации статьи): Другие архитектуры в основном использовали метод скользящего окна по всему изображению, и классификатор использовался для определенной области изображения (DPM). Также, R-CNN использовал метод предложения регионов (region proposal method). Описываемый метод сначала создает потенциальные bounding boxы. Затем, на области, ограниченные bounding boxами, запускается классификатор и следующее удаление повторяющихся распознаваний, и уточнение границ рамок.

YOLO переосмыслила задачу обнаружения объектов в задачу регрессии.Она идет от пикселей изображения к координатам bounding boxов и вероятностей классов. Тем самым, единая сверточная сеть предсказывает несколько bounding boxов и вероятности классов для содержания этих областей.

Теория

Так как YOLO необходимо только один взгляд на изображение, то метод скользящего окна не подходит в данной ситуации. Вместо этого, изображение будет поделено на сетку с ячейками размером S x S. Каждая ячейка может содержать несколько разных объектов для распознавания.

Во-первых, каждая ячейка отвечает за прогнозирование количества bounding boxов. Также, каждая ячейка прогнозирует доверительное значение (confidence value) для каждой области, ограниченной bounding boxом. Иными словами, это значение определяет вероятность нахождения того или иного объекта в данной области. То есть в случае, если какая-то ячейка сетки не имеет определенного объекта, важно, чтобы доверительное значение для этой области было низким.

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

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

Давайте подробней опишем вывод модели.

В YOLO используются anchor boxes (якорные рамки / фиксированные рамки) для прогнозирования bounding boxов. Идея anchor boxов сводится к предварительному определению двух различных форм. И таким образом, мы можем объединить два предсказания с двумя anchor boxами (в целом, мы могли бы использовать даже большее количество anchor boxов). Эти якоря были рассчитаны с помощью датасета COCO (Common Objects in Context) и кластеризации k-средних (K-means clustering).

У нас есть сетка, где каждая ячейка предсказывает:

  • Для каждого bounding box'а:

    • 4 координаты (tx , ty , tw , th)

    • 1 objectness error (ошибка объектности), которая является показателем уверенности в присутствии того или иного объекта

  • Некоторое количество вероятностей классов

Если же присутствует некоторое смещение от верхнего левого угла на cx , cy то прогнозы будут соответствовать:

b_{x} = \sigma(t_{x}) + c_{x}\\ b_{y} = \sigma(t_{y}) + c_{y}\\ b_{w} = p_{w}e^{t_{w}}\\ b_{h} = p_{h}e^{t_{h}}

где pw (ширина) и ph (высота) соответствуют ширине и высоте bounding box'а. Вместо того, чтобы предугадывать смещение как в прошлой версии YOLOv2, авторы прогнозируют координаты местоположения относительно местоположения ячейки.

Этот вывод является выводом нашей нейронной сети. В общей сложности здесьS x S x [B * (4+1+C)] выводов, где B это количество bounding box'ов, которое может предсказать ячейка на карте объектов, C это количество классов, 4 для bounding box'ов, 1 для objectness prediction (прогнозирование объектности). За один проход мы можем пройти от входного изображения к выходному тензору, который соответствует обнаруженным объектам на картинке. Также стоит отметить, что YOLOv3 прогнозирует bounding box'ы в трех разных масштабах.

Теперь, если мы возьмем вероятность и умножим их на доверительные значения, мы получим все bounding box'ы, взвешенные по вероятности содержания этого объекта.

Простое нахождение порогового значения избавит нас от прогнозов с низким доверительным значением. Для следующего шага важно определить метрику IoU (Intersection over Union / Пересечение над объединением). Эта метрика равняется соотношению площади пересекающихся областей к площади областей объединенных.

После этого все равно могут остаться дубликаты, и чтобы от них избавиться нужно использовать подавление не-максимумов (non-maximum suppression). Подавление не-максимумов заключается в следующем: алгоритм берёт bounding box с наибольшей вероятностью принадлежности к объекту, затем, среди остальных граничащих bounding box'ов с данной области, возьмёт один с наивысшим IoU и подавляет его.

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

Yolov3Yolov3

Мы также рекомендуем прочитать следующие статьи о YOLO:

Реализация в Tensorflow

Первым шагом в реализации YOLO это подготовка ноутбука и импортирование необходимых библиотек. Целиком ноутбук с кодом вы можете на Github или Kaggle:

Следуя этой статье, мы сделаем полную сверточную сеть (fully convolutional network / FCN) без обучения. Для того, чтобы применить эту сеть для определения объектов, нам необходимо скачать готовые веса от предварительно обученной модели. Эти веса были получены от обучения YOLOv3 на датасете COCO (Common Objects in Context). Файл с весами можно скачать по ссылке официального сайта.

# Создаем папку для checkpoint'ов с весами.# !mkdir checkpoints# Скачиваем файл с весами для YOLOv3 с официального сайта.# !wget https://pjreddie.com/media/files/yolov3.weights# Импортируем необходимые библиотеки.import cv2import numpy as np import tensorflow as tf from absl import loggingfrom itertools import repeatfrom PIL import Imagefrom tensorflow.keras import Modelfrom tensorflow.keras.layers import Add, Concatenate, Lambdafrom tensorflow.keras.layers import Conv2D, Input, LeakyReLUfrom tensorflow.keras.layers import MaxPool2D, UpSampling2D, ZeroPadding2Dfrom tensorflow.keras.regularizers import l2from tensorflow.keras.losses import binary_crossentropyfrom tensorflow.keras.losses import sparse_categorical_crossentropyyolo_iou_threshold = 0.6 # Intersection Over Union (iou) threshold.yolo_score_threshold = 0.6 # Score threshold.weightyolov3 = 'yolov3.weights' # Путь до файла с весами.size = 416 # Размер изображения. checkpoints = 'checkpoints/yolov3.tf' # Путь до файла с checkpoint'ом.num_classes = 80 # Количество классов в модели.# Список слоев в YOLOv3 Fully Convolutional Network (FCN).YOLO_V3_LAYERS = [    'yolo_darknet',    'yolo_conv_0',    'yolo_output_0',    'yolo_conv_1',    'yolo_output_1',    'yolo_conv_2',    'yolo_output_2']

По причине того, что порядок слоев в Darknet (open source NN framework) и tf.keras разные, то загрузить веса с помощью чистого функционального API будет проблематично. В этом случае, наилучшим решением будет создание подмоделей в keras. TF Checkpoints рекомендованы для сохранения вложенных подмоделей и они официально поддерживаются Tensorflow.

# Функция для загрузки весов обученной модели.def load_darknet_weights(model, weights_file):    wf = open(weights_file, 'rb')    major, minor, revision, seen, _ = np.fromfile(wf, dtype=np.int32, count=5)    layers = YOLO_V3_LAYERS    for layer_name in layers:        sub_model = model.get_layer(layer_name)        for i, layer in enumerate(sub_model.layers):            if not layer.name.startswith('conv2d'):                continue            batch_norm = None            if i + 1 < len(sub_model.layers) and \                sub_model.layers[i + 1].name.startswith('batch_norm'):                    batch_norm = sub_model.layers[i + 1]            logging.info("{}/{} {}".format(                sub_model.name, layer.name, 'bn' if batch_norm else 'bias'))                        filters = layer.filters            size = layer.kernel_size[0]            in_dim = layer.input_shape[-1]            if batch_norm is None:                conv_bias = np.fromfile(wf, dtype=np.float32, count=filters)            else:                bn_weights = np.fromfile(wf, dtype=np.float32, count=4*filters)                bn_weights = bn_weights.reshape((4, filters))[[1, 0, 2, 3]]            conv_shape = (filters, in_dim, size, size)            conv_weights = np.fromfile(wf, dtype=np.float32, count=np.product(conv_shape))            conv_weights = conv_weights.reshape(conv_shape).transpose([2, 3, 1, 0])            if batch_norm is None:                layer.set_weights([conv_weights, conv_bias])            else:                layer.set_weights([conv_weights])                batch_norm.set_weights(bn_weights)    assert len(wf.read()) == 0, 'failed to read weights'    wf.close()

На этом же этапе, мы должны определить функцию для расчета IoU. Мы используем batch normalization (пакетная нормализация) для нормализации результатов, чтобы ускорить обучение. Так как tf.keras.layers.BatchNormalization работает не очень хорошо для трансферного обучения (transfer learning), то мы используем другой подход.

# Функция для расчета IoU.def interval_overlap(interval_1, interval_2):    x1, x2 = interval_1    x3, x4 = interval_2    if x3 < x1:        return 0 if x4 < x1 else (min(x2,x4) - x1)    else:        return 0 if x2 < x3 else (min(x2,x4) - x3)def intersectionOverUnion(box1, box2):    intersect_w = interval_overlap([box1.xmin, box1.xmax], [box2.xmin, box2.xmax])    intersect_h = interval_overlap([box1.ymin, box1.ymax], [box2.ymin, box2.ymax])    intersect_area = intersect_w * intersect_h    w1, h1 = box1.xmax-box1.xmin, box1.ymax-box1.ymin    w2, h2 = box2.xmax-box2.xmin, box2.ymax-box2.ymin    union_area = w1*h1 + w2*h2 - intersect_area    return float(intersect_area) / union_area class BatchNormalization(tf.keras.layers.BatchNormalization):    def call(self, x, training=False):        if training is None: training = tf.constant(False)        training = tf.logical_and(training, self.trainable)        return super().call(x, training)# Определяем 3 anchor box'а для каждой ячейки.   yolo_anchors = np.array([(10, 13), (16, 30), (33, 23), (30, 61), (62, 45),                        (59, 119), (116, 90), (156, 198), (373, 326)], np.float32) / 416yolo_anchor_masks = np.array([[6, 7, 8], [3, 4, 5], [0, 1, 2]])

В каждом масштабе мы определяем 3 anchor box'а для каждой ячейки. В нашем случае если маска будет:

  • 0, 1, 2 означает, что будут использованы первые три якорные рамки

  • 3, 4 ,5 означает, что будут использованы четвертая, пятая и шестая

  • 6, 7, 8 означает, что будут использованы седьмая, восьмая, девятая

# Функция для отрисовки bounding box'ов.def draw_outputs(img, outputs, class_names, white_list=None):    boxes, score, classes, nums = outputs    boxes, score, classes, nums = boxes[0], score[0], classes[0], nums[0]    wh = np.flip(img.shape[0:2])    for i in range(nums):        if class_names[int(classes[i])] not in white_list:            continue        x1y1 = tuple((np.array(boxes[i][0:2]) * wh).astype(np.int32))        x2y2 = tuple((np.array(boxes[i][2:4]) * wh).astype(np.int32))        img = cv2.rectangle(img, x1y1, x2y2, (255, 0, 0), 2)        img = cv2.putText(img, '{} {:.4f}'.format(            class_names[int(classes[i])], score[i]),            x1y1, cv2.FONT_HERSHEY_COMPLEX_SMALL, 1, (0, 0, 255), 2)    return img

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

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

Создавая нашу модель, мы строим нашу модель с помощью функционального API, который будет легко использовать. С его помощью мы можем без труда определить ветви в нашей архитектуре (ResNet Block) и делить слои внутри архитектуры.

def DarknetConv(x, filters, size, strides=1, batch_norm=True):    if strides == 1:        padding = 'same'    else:        x = ZeroPadding2D(((1, 0), (1, 0)))(x)        padding = 'valid'    x = Conv2D(filters=filters, kernel_size=size,              strides=strides, padding=padding,              use_bias=not batch_norm, kernel_regularizer=l2(0.0005))(x)    if batch_norm:        x = BatchNormalization()(x)        x = LeakyReLU(alpha=0.1)(x)    return xdef DarknetResidual(x, filters):    previous = x    x = DarknetConv(x, filters // 2, 1)    x = DarknetConv(x, filters, 3)    x = Add()([previous , x])    return xdef DarknetBlock(x, filters, blocks):    x = DarknetConv(x, filters, 3, strides=2)    for _ in repeat(None, blocks):        x = DarknetResidual(x, filters)           return xdef Darknet(name=None):    x = inputs = Input([None, None, 3])    x = DarknetConv(x, 32, 3)    x = DarknetBlock(x, 64, 1)    x = DarknetBlock(x, 128, 2)    x = x_36 = DarknetBlock(x, 256, 8)    x = x_61 = DarknetBlock(x, 512, 8)    x = DarknetBlock(x, 1024, 4)    return tf.keras.Model(inputs, (x_36, x_61, x), name=name)  def YoloConv(filters, name=None):    def yolo_conv(x_in):        if isinstance(x_in, tuple):            inputs = Input(x_in[0].shape[1:]), Input(x_in[1].shape[1:])            x, x_skip = inputs            x = DarknetConv(x, filters, 1)            x = UpSampling2D(2)(x)            x = Concatenate()([x, x_skip])        else:            x = inputs = Input(x_in.shape[1:])        x = DarknetConv(x, filters, 1)        x = DarknetConv(x, filters * 2, 3)        x = DarknetConv(x, filters, 1)        x = DarknetConv(x, filters * 2, 3)        x = DarknetConv(x, filters, 1)        return Model(inputs, x, name=name)(x_in)    return yolo_conv  def YoloOutput(filters, anchors, classes, name=None):    def yolo_output(x_in):        x = inputs = Input(x_in.shape[1:])        x = DarknetConv(x, filters * 2, 3)        x = DarknetConv(x, anchors * (classes + 5), 1, batch_norm=False)        x = Lambda(lambda x: tf.reshape(x, (-1, tf.shape(x)[1], tf.shape(x)[2],                                        anchors, classes + 5)))(x)        return tf.keras.Model(inputs, x, name=name)(x_in)    return yolo_outputdef yolo_boxes(pred, anchors, classes):    grid_size = tf.shape(pred)[1]    box_xy, box_wh, score, class_probs = tf.split(pred, (2, 2, 1, classes), axis=-1)    box_xy = tf.sigmoid(box_xy)    score = tf.sigmoid(score)    class_probs = tf.sigmoid(class_probs)    pred_box = tf.concat((box_xy, box_wh), axis=-1)    grid = tf.meshgrid(tf.range(grid_size), tf.range(grid_size))    grid = tf.expand_dims(tf.stack(grid, axis=-1), axis=2)    box_xy = (box_xy + tf.cast(grid, tf.float32)) /  tf.cast(grid_size, tf.float32)    box_wh = tf.exp(box_wh) * anchors    box_x1y1 = box_xy - box_wh / 2    box_x2y2 = box_xy + box_wh / 2    bbox = tf.concat([box_x1y1, box_x2y2], axis=-1)        return bbox, score, class_probs, pred_box

Теперь определим функцию подавления не-максимумов.

def nonMaximumSuppression(outputs, anchors, masks, classes):    boxes, conf, out_type = [], [], []    for output in outputs:        boxes.append(tf.reshape(output[0], (tf.shape(output[0])[0], -1, tf.shape(output[0])[-1])))        conf.append(tf.reshape(output[1], (tf.shape(output[1])[0], -1, tf.shape(output[1])[-1])))        out_type.append(tf.reshape(output[2], (tf.shape(output[2])[0], -1, tf.shape(output[2])[-1])))    bbox = tf.concat(boxes, axis=1)    confidence = tf.concat(conf, axis=1)    class_probs = tf.concat(out_type, axis=1)    scores = confidence * class_probs      boxes, scores, classes, valid_detections = tf.image.combined_non_max_suppression(        boxes=tf.reshape(bbox, (tf.shape(bbox)[0], -1, 1, 4)),        scores=tf.reshape(            scores, (tf.shape(scores)[0], -1, tf.shape(scores)[-1])),        max_output_size_per_class=100,        max_total_size=100,        iou_threshold=yolo_iou_threshold,        score_threshold=yolo_score_threshold)      return boxes, scores, classes, valid_detections

Основная функция:

def YoloV3(size=None, channels=3, anchors=yolo_anchors,            masks=yolo_anchor_masks, classes=80, training=False):    x = inputs = Input([size, size, channels])    x_36, x_61, x = Darknet(name='yolo_darknet')(x)    x = YoloConv(512, name='yolo_conv_0')(x)    output_0 = YoloOutput(512, len(masks[0]), classes, name='yolo_output_0')(x)    x = YoloConv(256, name='yolo_conv_1')((x, x_61))    output_1 = YoloOutput(256, len(masks[1]), classes, name='yolo_output_1')(x)    x = YoloConv(128, name='yolo_conv_2')((x, x_36))    output_2 = YoloOutput(128, len(masks[2]), classes, name='yolo_output_2')(x)    if training:        return Model(inputs, (output_0, output_1, output_2), name='yolov3')    boxes_0 = Lambda(lambda x: yolo_boxes(x, anchors[masks[0]], classes),                  name='yolo_boxes_0')(output_0)    boxes_1 = Lambda(lambda x: yolo_boxes(x, anchors[masks[1]], classes),                  name='yolo_boxes_1')(output_1)    boxes_2 = Lambda(lambda x: yolo_boxes(x, anchors[masks[2]], classes),                  name='yolo_boxes_2')(output_2)    outputs = Lambda(lambda x: nonMaximumSuppression(x, anchors, masks, classes),                  name='nonMaximumSuppression')((boxes_0[:3], boxes_1[:3], boxes_2[:3]))    return Model(inputs, outputs, name='yolov3')

Функция потерь:

def YoloLoss(anchors, classes=80, ignore_thresh=0.5):    def yolo_loss(y_true, y_pred):        pred_box, pred_obj, pred_class, pred_xywh = yolo_boxes(            y_pred, anchors, classes)        pred_xy = pred_xywh[..., 0:2]        pred_wh = pred_xywh[..., 2:4]        true_box, true_obj, true_class_idx = tf.split(            y_true, (4, 1, 1), axis=-1)        true_xy = (true_box[..., 0:2] + true_box[..., 2:4]) / 2        true_wh = true_box[..., 2:4] - true_box[..., 0:2]        box_loss_scale = 2 - true_wh[..., 0] * true_wh[..., 1]        grid_size = tf.shape(y_true)[1]        grid = tf.meshgrid(tf.range(grid_size), tf.range(grid_size))        grid = tf.expand_dims(tf.stack(grid, axis=-1), axis=2)        true_xy = true_xy * tf.cast(grid_size, tf.float32) - \            tf.cast(grid, tf.float32)        true_wh = tf.math.log(true_wh / anchors)        true_wh = tf.where(tf.math.is_inf(true_wh),                      tf.zeros_like(true_wh), true_wh)        obj_mask = tf.squeeze(true_obj, -1)        true_box_flat = tf.boolean_mask(true_box, tf.cast(obj_mask, tf.bool))        best_iou = tf.reduce_max(intersectionOverUnion(            pred_box, true_box_flat), axis=-1)        ignore_mask = tf.cast(best_iou < ignore_thresh, tf.float32)        xy_loss = obj_mask * box_loss_scale * \            tf.reduce_sum(tf.square(true_xy - pred_xy), axis=-1)        wh_loss = obj_mask * box_loss_scale * \            tf.reduce_sum(tf.square(true_wh - pred_wh), axis=-1)        obj_loss = binary_crossentropy(true_obj, pred_obj)        obj_loss = obj_mask * obj_loss + \            (1 - obj_mask) * ignore_mask * obj_loss        class_loss = obj_mask * sparse_categorical_crossentropy(            true_class_idx, pred_class)        xy_loss = tf.reduce_sum(xy_loss, axis=(1, 2, 3))        wh_loss = tf.reduce_sum(wh_loss, axis=(1, 2, 3))        obj_loss = tf.reduce_sum(obj_loss, axis=(1, 2, 3))        class_loss = tf.reduce_sum(class_loss, axis=(1, 2, 3))        return xy_loss + wh_loss + obj_loss + class_loss    return yolo_loss

Функция "преобразовать цели" возвращает кортеж из форм:

(    [N, 13, 13, 3, 6],    [N, 26, 26, 3, 6],    [N, 52, 52, 3, 6])

Где N число меток в пакете, а число 6 означает [x, y, w, h, obj, class] bounding box'а.

@tf.functiondef transform_targets_for_output(y_true, grid_size, anchor_idxs, classes):    N = tf.shape(y_true)[0]    y_true_out = tf.zeros(      (N, grid_size, grid_size, tf.shape(anchor_idxs)[0], 6))    anchor_idxs = tf.cast(anchor_idxs, tf.int32)    indexes = tf.TensorArray(tf.int32, 1, dynamic_size=True)    updates = tf.TensorArray(tf.float32, 1, dynamic_size=True)    idx = 0    for i in tf.range(N):        for j in tf.range(tf.shape(y_true)[1]):            if tf.equal(y_true[i][j][2], 0):                continue            anchor_eq = tf.equal(                anchor_idxs, tf.cast(y_true[i][j][5], tf.int32))            if tf.reduce_any(anchor_eq):                box = y_true[i][j][0:4]                box_xy = (y_true[i][j][0:2] + y_true[i][j][2:4]) / 2                anchor_idx = tf.cast(tf.where(anchor_eq), tf.int32)                grid_xy = tf.cast(box_xy // (1/grid_size), tf.int32)                indexes = indexes.write(                    idx, [i, grid_xy[1], grid_xy[0], anchor_idx[0][0]])                updates = updates.write(                    idx, [box[0], box[1], box[2], box[3], 1, y_true[i][j][4]])                idx += 1    return tf.tensor_scatter_nd_update(        y_true_out, indexes.stack(), updates.stack())def transform_targets(y_train, anchors, anchor_masks, classes):    outputs = []    grid_size = 13    anchors = tf.cast(anchors, tf.float32)    anchor_area = anchors[..., 0] * anchors[..., 1]    box_wh = y_train[..., 2:4] - y_train[..., 0:2]    box_wh = tf.tile(tf.expand_dims(box_wh, -2),                    (1, 1, tf.shape(anchors)[0], 1))    box_area = box_wh[..., 0] * box_wh[..., 1]    intersection = tf.minimum(box_wh[..., 0], anchors[..., 0]) * \    tf.minimum(box_wh[..., 1], anchors[..., 1])    iou = intersection / (box_area + anchor_area - intersection)    anchor_idx = tf.cast(tf.argmax(iou, axis=-1), tf.float32)    anchor_idx = tf.expand_dims(anchor_idx, axis=-1)    y_train = tf.concat([y_train, anchor_idx], axis=-1)    for anchor_idxs in anchor_masks:        outputs.append(transform_targets_for_output(            y_train, grid_size, anchor_idxs, classes))        grid_size *= 2    return tuple(outputs) # [x, y, w, h, obj, class]def preprocess_image(x_train, size):    return (tf.image.resize(x_train, (size, size))) / 255

Теперь мы можем создать нашу модель, загрузить веса и названия классов. В COCO датасете их 80.

yolo = YoloV3(classes=num_classes)load_darknet_weights(yolo, weightyolov3)yolo.save_weights(checkpoints)class_names =  ["person", "bicycle", "car", "motorbike", "aeroplane", "bus", "train", "truck",    "boat", "traffic light", "fire hydrant", "stop sign", "parking meter", "bench",    "bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe",    "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard",    "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard",    "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl",    "banana","apple", "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut",    "cake","chair", "sofa", "pottedplant", "bed", "diningtable", "toilet", "tvmonitor", "laptop",     "mouse","remote", "keyboard", "cell phone", "microwave", "oven", "toaster", "sink",    "refrigerator","book", "clock", "vase", "scissors", "teddy bear", "hair drier", "toothbrush"]def detect_objects(img_path, white_list=None):    image = img_path     # Путь к изображению.    img = tf.image.decode_image(open(image, 'rb').read(), channels=3)    img = tf.expand_dims(img, 0)    img = preprocess_image(img, size)    boxes, scores, classes, nums = yolo(img)    img = cv2.imread(image)    img = draw_outputs(img, (boxes, scores, classes, nums), class_names, white_list)    cv2.imwrite('detected_{:}'.format(img_path), img)    detected = Image.open('detected_{:}'.format(img_path))    detected.show()    detect_objects('test.jpg', ['bear'])

Итог

В этой статье мы поговорили об отличительных особенностях YOLOv3 и её преимуществах перед другими моделями. Мы рассмотрели способ реализации с использованием TensorFlow 2.0 (TF должен быть не менее версией 2.0).

Ссылки

Подробнее..

Самая сложная задача в Computer Vision

15.06.2020 12:08:41 | Автор: admin
Среди всего многообразия задач Computer Vision есть одна, которая стоит особняком. К ней обычно стараются лишний раз не притрагиваться. И, если не дай бог работает, не ворошить.
У неё нет общего решения. Практически для каждого применения существующие алгоритмы надо тюнинговать, переобучать, или судорожно копаться в куче матриц и дебрях логики.

Статья о том как делать трекинг. Где он используется, какие есть разновидности. Как сделать стабильное решение.

Что можно трекать


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

Что можно трекать?

  • Людей
  • Животных
  • Машины и прочую технику

Конечно, можно трекать что угодно (точки на пальцах, или на лице), но 99% задач из перечисленных категорий.

Разберем в каких задачах это используется на практике для каждой категории.

Люди


Трекинг людей нужен чаще всего.
Самая стандартная задача на которой больше всего решений трекинг людей на улице. Для этого даже большой челлендж есть (даже не один):



Где применяется трекинг на улице/в толпе? Всякие системы безопасности/защиты периметра. Иногда чтобы отслеживать кого-то.

Не сказать что существует готовое общее решение работающее всегда. Почему? В такой толпе даже человек неправильно всё разметит:



Другая задача, на мой взгляд самая востребованная, и не такая маргинальная подсчёт числа посетителей торговых точек, аналитика по торговому залу. Короче статистика для бизнеса. На хабре было несколько статей на эту тему, например эта. Сюда же можно отнести задачи плана подсчёт входящих/выходящих людей в транспорте.



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



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

Животные


Глобально, трекинг животных в 95% нужен только на фермах. В природе делают скорее детекцию, чтобы посчитать статистику. А на ферме очень полезно знать какая корова не отдоилась и куда она пошла. Как ни странно, но таких фирм очень много. Вот несколько которые решают эти задачи Cows.ai, Cattle-Care. Но, как водиться, таких не мало.

image
(Видео с сайта первых)

Можно трекать овец. Можно свиней.

А можно Рыбок! (картинка от google, но есть много разных фирм, например Aquabyte)

image

Машины и прочая техника


Машины и прочие самолёты обычно трекать очень просто. Они ездят по практически идеальным траекториям, что сильно упрощает работу алгоритма. Прочую технику зачастую трекают военные, и это редко встречается на практике.

А машины трекать нужно и в системах умного города, и в системах выписывания штрафов, и в всяких системах сопровождения гонок.



Как можно трекать


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

  • Детекция объектов в кадре. Не важно что тут используется. Главное чтобы детекция выдавала положение объекта. Это может быть YOLO, SSD, или любой из десятков более новых алгоритмов.
  • Алгоритм распознавания объекта в кадре. Так называемая задача ReIndentification. Это может быть:

    1. Распознавание человека по телу
    2. Распознавание человека по лицу
    3. Распознавание типа автомобиля
    4. и.т.д, и.т.п.

  • Алгоритмы сопоставления объектов по физическим параметрам. Это может быть сопоставление по IoU(пересечение выделенного прямоугольника), сопоставление по скелету (насколько они похожи), сопоставление по положению на полу/на земле/на дороге. и.т.д.
  • Алгоритм межкадрового трекинга. Эта часть самая загадочная.
  • Сюда входит много алгоритмов оптического трекинга, оптического потока, и.т.д

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

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

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

Что важно отметить при переходе к алгоритмам трекинга


95% качества при трекенге, как впрочем и у любой задачи ComputerVision установка камеры.
Сравните:




Где проще посчитать людей?

Но помните, установка камеры сверху зачастую усложняет создание системы. Ни Yolo ни SSD, ни стандартные ReID алгоритмы не будут работать по таким объектам. Потребуется полномасштабное переобучение (ниже приведу пример).

Детекция объектов в кадре


Про детекцию есть много постов на Хабре. Есть сети которые показывают более высокое качество. Есть сети которые показывают более высокую скорость. Зачастую скорость может быть завязана на железо. На одной железке быстро на второй медленно.

Если вы не разбираетесь что вам надо, то на июнь 2020 хорошим вариантом является YOLOv4, обеспечивающая баланс качества/производительности. Для максимальной скорости можно посмотреть в сторону YOLOv3- tiny или в сторону быстрых версий EfficientDet.

(Ещё мутный YOLOv5 появился за время написания статьи, но там какая-то хрень).

Но в ряде случаев эти решения могут не быть оптимальными.

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

Для трекинга детекция это основа. Какие бы крутые у вас не были следующие алгоритмы без детекции трекинг не будет работать.

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

Обычно детекторы учатся на каких-нибудь универсальных датасетах (например Microsoft COCO):

image

Там мало людей которые плохо видны, скорее всего нет камеры похожей на вашу (ИК, широкий угол). Нет ваших условий освещения, и многое другое.

Переобучение детектора по датасету из используемых условий может уменьшить число ошибок в 2-3 раза. Лично я видел примеры где и в 10-20 уменьшало (процентов с 60-70 точности до 98-99).

Отдельным моментом стоит отметить что в ситуациях когда люди/животные/машины в нестандартных ракурсах переобучать придётся всегда. Вот пара примеров как работает непереобученный Yolo v4:





Несколько мест где можно взять готовые нейронки для детекции:


Я не буду подробно заостряться на этой теме, про детекцию безумно много статей.

Person ReIndentification


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



Вы не видели как она вышла из толпы. Вы не видели её последние 30 секунд. Но вы же знаете, что это она.

Тут работает именно алгоритм распознавания по телу.

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

Особенно после разрыва трека.

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

  • Market-1501
  • CUHK03
  • MARS
  • DukeMTMC
  • iLIDS-VID

Большая часть датасетов собрана каким-то таким образом:

image

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

Есть множество способов обучить такие нейронки, если вам это интересно, то вот тут есть хорошая подборка с исходниками.

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

image

У нейронной сети создаётся бутылочное горлышко, из небольшого числа нейронов. А на выходе нейронной сети будет число нейронов по числу людей в датасете. Сеть научится распознавать людей, но параллельно научится описывать их в качестве небольшого вектора.

Так просто никто уже не делает. При обучении последних версий ReID используется и hard negative mining, и различные хитрые аугментации и трюки с подрезанием, и фокальные потери, и многое-много другое.

Всё бы хорошо, но у ReID алгоритмов есть слишком много проблем. Даже по датасетам которые идеально под них заточены (улица, одинаковые условия освещения, почти одинаковое время съемки, отсутствие пёстрого фона, нет больших пересечений людей, нет заслонений объектами переднего фона, человек целиком в кадре, и.т.д., и.т.п.), даже у самых последних моделей точность будет на уровне 95-98%

На практике такие сети очень сложно использовать: Предположим у вас завод где все ходят в одинаковой униформе

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

Ниже мы рассмотрим как алгоритмы ReID используются в сетях трекинга и иногда ощутимо повышают результат.

ReID по одежде


Одна из вариаций ReID распознавание по одежде:

  1. Пример сорсов
  2. Пример алгоритма
  3. Пример алгоритма

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



Например, если у вас люди носят три типа формы можете обучить по ним!

ReID по лицу


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

Про распознавание по лицам можно почитать например тут 1, 2.

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

Про точность распознавания лиц и их применимость я писал длинную статью.

Пусть будет ещё картиночка про олдфажеский TripletLoss для обучения лиц, который уже не используется:



ReID не на людях


Алгоритм реиндентификации очень удобен для трекинга. Хотите посмотреть где он работает на животных? Например тут:


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

Очень хорошо ReID работает на коровах. Каждая корова по шкуре уникальна: 1, 2

И, как я слышал, ReID не очень работает на животных где окрас одинаков, например овцы или свиньи.

Прочие ReID


Какие-то аналоги ReID можно использовать для трекинга автомобилей (например распознавание марки автомобиля). Можно обучить ReID для трекинга людей по головам.

Но так как ReID обычно обучать муторно достаточно редко вижу его использование для трекинга вне случаев описанных выше (не для трекинга применений масса!).

Алгоритмы сопоставления объектов по физическим параметрам


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

  • Размеры выделенной области (bbox) объекта. Чем более похожи объекты в соседних кадрах по размеру тем с большей вероятностью это один объект.
    image
  • Положение центра масс. Чем ближе центр масс к планируемой траектории тем с большей вероятностью это один и тот же объект.
  • Скорость передвижения объекта в прошлых кадрах. Чем больше скорость и ускорение соответствуют какому-то объекту, тем с большей вероятностью это одно и то же.
  • Координаты проекции на землю. Можно вычислить если знать как висит камера. Для установок камер вертикально сверху совпадает с позапрошлым пунктом. Хорошо работает при трекинге машин.

  • Близость позы. ReID алгоритмы работают в длительном окне времени, не учитывая позу человека, стараясь отвязаться от неё. Но если сэтап имеет достаточно высокую скорость обработки, то наоборот, можно привязывать соседние детекции по позе человека.

    image
  • Наверняка есть что-то что я ещё забыл. Но оно сильно реже на практике.

Оптический трекер


Ну вот мы и пришли. К могучем, огромному, бесполезному оптическому трекингу!

По этой теме фанатеют многие. Смысл этого класса алгоритмов очень простой.

Пусть в первом кадре у нас есть заданное положение объекта. Нужно найти его в следующем.
И не важно какой объект: человек, машина, точка на руке, вертолёт или падающий лист.

Задача достаточно простая и древняя. Для неё существует множество подходов. Начинающихся ещё с OpticalFlow задачи:

image

(найти для каждой точки кадра порождающую её точку с прошлого кадра).

А потом пошло поехало:


И многое, многое другое.

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

Обычно после этого он натыкается на примеры из Эндриана Розеброка:


И использует их в продакшне (видел минимум в двух фирмах)

Но нет Не надо так. Каждый раз когда вы берёте пример с pyimagesearch и несёте его в прод вы делаете грустным ещё одного котика (с).



Эдриан хороший популяризатор. Возможно он даже знает как делать машинное зрение. Но 95% его примеров это пример использования готовой библиотеки. А готовая библиотека почти всегда бесконечно далека от прода.

Разберём что может пойти не так (специально записал!):

  1. Похожие объекты, оптический трекинг не учитывает направление и скорость, он выбирает оптимальный минимум ближе всего:


  2. Неадекватный детектор потери. Например человек входит в дверь. Оптический трекер это не любит:

  3. Неадекватная реакция на сильно изменение формы. Резкий разворот/поворот убивает трекеры:

  4. Перепады яркости

Есть и другие, менее значительные причины, которые тоже могут проявится. Это приводит к нескольким утверждениям:

  1. Если у вас более-менее стабильная детекция оптический трекер нет смысла использовать
  2. Если у вас нет опыта тонкого тюнинга таких алгоритмов то тоже не надо брать такое
  3. Если у вас есть несколько независимых алгоритмов, а их результаты вы планируете склеивать в ручную то лучше не надо использовать оптический трекер. Только при автоматической склейке где какой-то ML алгоритм выберет параметры склейки сам.

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

  1. Оптимизация скорости работы. Запускать детектор пореже, а трекинг меньше ест.
  2. Универсальность работы на любом железе.

Но по мне оба аргумента очень сомнительны при современном уровне развития технологий.

Единственное место, наверное, где трекинг имеет смысл вы не можете набрать базу для детекции. Вот пример как можно трекать вот через классические, встроенные в OpenCV алгоритмы (реинициализирую руками после разрыва):

И вот что выдаст обычный YOLOv4 (детекции клею через SORT, про который будет ниже):


Понятно, что YOLO можно дообучить, и всё будет сильно лучше. Но, наверное, когда нет таких сил можно и через трекинг прототип сделать.

А вот что будет оптическим трекером из OpenCV (GOTURN, каждый раз когда разрыв реинициализирую заново):


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

Часть 2


Как всё это завязать.

Нагенерили детекций. Возможно нагенерили каких-то метрик близости между ними. Как всё это завязать друг с другом?

Есть много подходов. Проблемы есть почти везде. Начнём с простого.

Классическая математика


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

Как ни странно, один из самых классических подходов, где в качестве модели был взят фильтр Калмана, а в качестве целераспределения венгерский алгоритм выстрелил в 2016, взяв первое место в MOT соревновании (SORT). И если по точности он был плюс-минус сравним с другими решениями, то по скорости бил в 20 раз. А по понятности в 100.

Почему этого не произошло раньше? Не знаю. Подход настолько классический и дубовый, что для определения параметров спутников нам его ещё году в 2008 на кафедре МФТИ преподавали. И эта программа, как я понимаю, лет 20-30 не менялась.
Скорее всего так вышло потому, что не было ни одной opensource реализации нормальной. Всё пряталось в глубинах продуктового софта.

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

На базу такого алгоритма можно насадить любые другие метрики близости. Например близость по позе. Или близость точек между результатами оптического трекинга.

Или в конце концов близость между ReID описаниями. Именно так делает, например DeepSORT.

Сравните качество работы SORT против Deep SORT:



И то и то собрано на базе детекции из Yolov4 которая выглядиттак.

Надо сказать что данный подход до сих пор в топе.

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

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


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

Только вот не везде этот подход будет работать. ReID нестабилен в большом временном окне. ReID не будет работать по одинаковым вещам как машины, или олени. Или, например, работники в униформе.

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

Нейронончки везде


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

image

К этому подходу будет относится и упомянутые уже LSTM сети трекинга:

image

И так же упомянутые сети трекинга скелетов:



image

Казалось бы. Раз можно достичь высокие результаты так и надо делать всегда! Но, как ни странно, нет.

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

Только вот есть одна проблема. Обучать по видео это огромные объемы датасетов. Длительная разметка, не понятен профит от обучения относительно того же SORT. Даже FairMot надо переобучать. И куда сложнее переобучать, чем отдельно YOLO.

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

Как в реальности


Мы затронули интересный вопрос. А как всё устроено на практике?. Тут всё интересно. По сути, как мне кажется: на практике всё либо очень сложно, либо очень просто.

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



Бывает и сложнее. Я почти уверен, что если у любой Tesla или Yandex-машинке разрисовать схему обучения алгоритмов трекинга то там сума сойдёт даже адекватный человек.

По тому что видел лично я:

  • В двух стартапа видел схему с уже упомянутого pyimagesearch. Когда происходила детекция, а для связывания детекций использовался оптический трекер. В одном стартапе работало на гране допустимого качества. Во втором не работало.
  • В одном стартапе разрабатывали детектор + аналог МНК для слежения. Но там трекались точки на человеке. Работало хорошо.
  • Сами участвовали/разрабатывали несколько систем трекинга авто. И везде трекинг был через какого-нибудь Калмана/аналог с МНК. Видел несколько фирм где в целом было так же. Работало у всех хорошо.
  • Видел 3 фирмы где в качестве трекинга использовался SORT. Одной из них разрабатывали процесс обучения детектора, который подавался в SORT. У всех всё было классно, всё работало.
  • Участвовали/до сих пор участвуем в разработке стартапа где трекаются люди в помещениях. Используются почти все техники которые тут упомянуты. Но вес каких-то очень мал. Работает в целом неплохо. Но, конечно, хуже того что человек глазом может.

Что посоветую использовать


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

Это сработает и для трекинга людей, и для машин, и для котиков.


Если вы хотите залезть в ReID то проще всего это будет сделать через DeepSort. Но надо понимать, что ReID это огромный мир, вариантов использования масса. Многие из них напрямую не связаны с трекингом.

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

Вообще, правильно помнить, что без правильного оборудования трекинг это всегда мучение. Хорошая и правильно поставленная камера >> алгоритма трекинга.

Если вы решили трекать что-то сложное или нестандартное, или захотели сильно повысить точность того что у вас есть вам придётся использовать большинство описанных техник!

P.S.


Тема трекинга очень большая. Я наверняка что-то забыл. Что-то, может, неправильно написал. Да и примеров не много. Но это всё можно поправить в комментариях! Особенно последний пункт.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru