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

Yolov4

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 объектов:


Подробнее..

Самая сложная задача в 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.


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

Как работает Object Tracking на YOLO и DeepSort

10.08.2020 00:11:52 | Автор: admin
Object Tracking очень интересное направление, которое изучается и эволюционирует не первый десяток лет. Сейчас многие разработки в этой области построены на глубоком обучении, которое имеет преимущество над стандартными алгоритмами, так как нейронные сети могут аппроксимировать функции зачастую лучше.

Но как именно работает Object Tracking? Есть множество Deep Learning решений для этой задачи, и сегодня я хочу рассказать о распространенном решении и о математике, которая стоит за ним.

Итак, в этой статье я попробую простыми словами и формулами рассказать про:

  • YOLO отличный object detector
  • Фильтры Калмана
  • Расстояние Махаланобиса
  • Deep SORT


YOLO отличный object detector

Сразу нужно сделать очень важную пометку, которую нужно запомнить Object Detection это не Object Tracking. Для многих это не будет новостью, но часто люди путают эти понятия. Простыми словами:

Object Detection это просто определение объектов на картинке/кадре. То есть алгоритм или нейронная сеть определяют объект и записывают его позицию и bounding boxes (параметры прямоугольников вокруг объектов). Пока что речи о других кадрах не идет, и алгоритм работает только с одним.

Пример:



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

Пример:



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

Поэтому Object Detection играет очень важную роль в задаче трэкинга.

Почему YOLO? Да потому что YOLO считается эффективнее многих других алгоритмов для определения объектов. Вот небольшой график для сравнения от создателей YOLO:



Здесь мы рассматриваем YOLOv3-4, поскольку это самые последние версии и они эффективнее предыдущих.

Архитектуры разных Object Detectors

Итак, существует несколько архитектур нейронных сетей, созданных для определения объектов. Они в основном разделяются на двухуровневые, такие как RCNN, fast RCNN и faster RCNN, и одноуровневые, такие как YOLO.

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

Обычно это выглядит так (для faster RCNN, которая является самой быстрой из перечисленных двухуровневых систем):

  1. Подается картинка/кадр на вход
  2. Кадр прогоняется через CNN для формирования feature maps
  3. Отдельной нейронной сетью определяются регионы с высокой вероятностью нахождения в них объектов
  4. Дальше эти регионы с помощью RoI pooling сжимаются и подаются в нейронную сеть, определяющую класс объекта в регионах




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

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

Вообще архитектура YOLO в первых блоках не сильно отличается по логике блоков от других детекторов, то есть на вход подается картинка, дальше создаются feature maps с помощью CNN (правда в YOLO используется своя CNN под названием Darknet-53), затем эти feature maps определенным образом анализируются (об этом чуть позже), выдавая на выходе позиции и размеры bounding boxes и классы, которым они принадлежат.



Но что такое Neck, Dense Prediction и Sparse Prediction?

С Sparse Prediction мы разобрались немного ранее это просто повторение того, как двухуровневые алгоритмы работают: определяют по отдельности регионы и затем классифицируют эти регионы.

Neck (или шея) это отдельный блок, который создан для того, чтобы агрегировать информацию от отдельных слоев с предыдущих блоков (как показано на рисунке выше) для увеличения аккуратности предсказания. Если Вас заинтересовало это можете погуглить термины Path Aggregation Network, Spatial Attention Module и Spatial Pyramid Pooling.

И, наконец, то, что отличает YOLO от всех других архитектур блок под названием (на нашей картинке выше) Dense Prediction. На нем мы сфокусируемся чуть сильнее, потому что это очень интересное решение, которое как раз позволило YOLO вырваться в лидеры по эффективности определения объектов.

YOLO (You Only Look Once) несет в себе философию смотреть на картинку один раз, и за этот один просмотр (то есть один прогон картинки через одну нейронную сеть) делать все необходимые определения объектов. Как это происходит?

Итак, на выходе от работы YOLO мы обычно хотим вот это:



Что делает YOLO когда учится на данных (простыми словами):

Шаг 1: Обычно картинки решейпят под размер 416x416 перед началом обучения нейронной сети, чтобы можно было их подавать батчами (для ускорения обучения).

Шаг 2: Делим картинку (пока что мысленно) на клетки размером axa. В YOLOv3-4 принято делить на клетки размером 13x13 (о различных скейлах поговорим чуть позже, чтобы было понятнее).



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



Как же рисуются эти прямоугольники (bounding boxes) вокруг клетки? Как определяется их размер и позиция? Здесь в борьбу вступает техника anchor boxes (в переводе якорные коробки, или якорные прямоугольники). Они задаются в самом начале либо самим пользователем, либо их размеры определяются исходя из размеров bounding boxes, которые есть в датасете, на котором будет тренироваться YOLO (используется K-means clustering и IoU для определения самых подходящих размеров). Обычно задают порядка 3 различных anchor boxes, которые будут нарисованы вокруг (или внутри) одной клетки:



Зачем это сделано? Сейчас все будет понятно, так как мы обсудим то, как YOLO обучается.

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

Давайте теперь подумаем, что нам нужно получить на выходе.

Для каждой клетки, нам нужно понять две принципиальные вещи:

  1. Какой из anchor boxes, из 3 нарисованных вокруг клетки, нам подходит больше всего и как его можно немного подправить для того, чтобы он хорошо вписывал в себя объект
  2. Какой объект находится внутри этого anchor box и есть ли он вообще


Какой же должен быть тогда output у YOLO?

1. На выходе для каждой клетки мы хотим получить:



2. Output должен включать в себя вот такие параметры:



Как определяется objectness? На самом деле этот параметр определяется с помощью метрики IoU во время обучения. Метрика IoU работает так:



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

А теперь начинается самое интересное. Представим, что мы создатели YOLO и нам нужно натренировать ее на то, чтобы распознавать людей на кадре/картинке. Мы подаем картинку из датасета в YOLO, там происходит feature extraction в начале, а в конце у нас получается CNN слой, который рассказывает нам о всех клеточках, на которые мы разделили нашу картинку. И если этот слой рассказывает нам неправду о клеточках на картинке, то у нас должен быть большой Loss, чтобы потом его уменьшать при подаче в нейронную сеть следующих картинок.

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



Как мы видим из картинки, этот слой, размером 13x13 (для картинок изначального размера 416x416) для того, чтобы рассказывать про каждую клетку на картинке. Из этого последнего слоя и достается информация, которую мы хотим.

YOLO предсказывает 5 параметров (для каждого anchor box для определенной клетки):



Чтобы было легче понять, есть хорошая визуализация на эту тему:



Как можно понять их этой картинки, задача YOLO максимально точно предсказать эти параметры, чтобы максимально точно определять объект на картинке. А confidence score, который определяется для каждого предсказанного bounding box, является неким фильтром для того, чтобы отсеять совсем неточные предсказания. Для каждого предсказанного bounding box мы умножаем его IoU на вероятность того, что это определенный объект (вероятностное распределение рассчитывается во время обучения нейронной сети), берем лучшую вероятность из всех возможных, и если число после умножения превышает определенный порог, то мы можем оставить этот предсказанный bounding box на картинке.

Дальше, когда у нас остались только предсказанные bounding boxes с высоким confidence score, наши предсказания (если их визуализировать) могут выглядеть примерно вот так:



Мы можем теперь использовать технику NMS (non-max suppression), чтобы отфильтровать bounding boxes таким образом, чтобы для одного объекта был только один предсказанный bounding box.



Нужно также знать, что YOLOv3-4 предсказывают на 3-х разных скейлах. То есть картинка делится на 64 grid cells, на 256 клеток и на 1024 клетки, чтобы также видеть маленькие объекты. Для каждой группы клеток алгоритм повторяет необходимые действия во время предсказания/обучения, которые были описаны сверху.

В YOLOv4 было использовано много техник для увеличения точности модели без сильной потери скорости. Но для самого предсказания оставили Dense Prediction таким же, как и у YOLOv3. Если Вы интересуетесь тем, что же такого магического сделали авторы, чтобы поднять так точность не теряя скорости, есть отличная статья, написанная про YOLOv4.

Надеюсь мне удалось немного донести то, как работает YOLO в целом (точнее последние две версии, то есть YOLOv3 и YOLOv4), и в Вас это пробудит желание воспользоваться этой моделью в будущем, или узнать про ее работу чуть подробнее.

Раз мы разобрались с, пожалуй, лучшей нейронной сетью для Object Detection (если оценивать скорость/качество), давайте наконец перейдем к тому, как же нам связывать информацию о наших определенных YOLO объектов между кадрами видео. Как программа может понимать, что человек на предыдущем кадре это тот же человек, что и на новом?

Deep SORT

Для понимания этой технологии, следует сначала разобраться с парой математических аспектов расстояние Махалонобиса и фильтр Калмана.

Расстояние Махалонобиса

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




Допустим, у нас есть две переменные X1 и X2. Для каждой из них у нас есть много измерений.



Теперь, допустим, у нас появилось 2 новых измерения:



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

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

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



Что на самом деле делает расстояние Махалонобиса:
  • Избавляется от ковариации переменных
  • Делает дисперсию (variance) переменных равной 1
  • После этого использует обычное евклидово расстояние для трансформированных данных


Посмотрим на формулу, как вычисляется расстояние Махалонобиса:



Давайте разберемся, что означают составляющие нашей формулы:
  1. Эта разница разница между нашей новой точкой и средними значениями для каждой переменной
  2. S это ковариационная матрица, о которой мы говорили чуть ранее


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

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

Фильтр Калмана

Чтобы понять, что это крутая, проверенная штука, которая может применяться в очень многих областях, достаточно знать, что фильтр Калмана применялся в 1960-х. Да-да, я намекаю именно на это полет на Луну. Он применялся там в нескольких местах, включая работу с траекторий полета туда и обратно. Фильтр Калмана также часто применяется в анализе временных рядов на финансовых рынках, в анализе показателей различных датчиков на заводах, предприятиях и много где еще. Надеюсь, мне удалось вас немного заинтриговать и мы вкратце опишем фильтр Калмана и как он работает. Я также советую прочитать вот эту статью на Хабре, если Вы хотите узнать о нем подробнее.

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

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

Фильтр Калмана считает 3 принципиально важные вещи:

1) Усиление Калмана (Kalman Gain):



Сразу прошу прощения за картинки, у Хабра какие-то проблемы с формулами (или я не до конца разобрался почему они у меня не отображаются).

2) Оцениваем значение нужной нам переменной на ЭТОМ этапе, с учетом посчитанного нами усиления Калмана и показаний с датчиков (в нашем случае, показание термометра), а также значений с предыдущего этапа, который происходил в прошлом.



3) Оцениваем НОВУЮ ошибку (неопределенность), чтобы применить ее на следующем этапе:



Таким образом, весь алгоритм может выглядеть вот так:



Итак, допустим мы задали изначальную температуру 69 градусов (прикинули примерно), с ошибкой в 2 градуса. Тогда, зная, что у термометра погрешность примерно 4 градуса (ага, супер точный термометр, ну это в моей Вселенной), наше значение KG по формуле (1) будет равно 2/(2+4) = 0.33. Дальше мы можем легко измерить наше новое значение воды (более точное), вставив термометр в воду и померив температуру. Получилось 70 градусов (допустим), и теперь мы можем более точно оценить температуру воды в стакане. Она будет равна по формуле (2), где нам теперь известно все, 68+0.33(70-68)=68.66. А наша новая ошибка по формуле (3) будет равна (1-0.33)2 = 1.32. Теперь на следующих шагах мы будем применять соответсвенно вычисленные нами новые значения для ошибки, температуры воды, и будем смотреть на новые показания прибора, когда мы его заново опустим в воду. График будет выглядеть примерно вот так:



Это потрясающе, как фильтр Калмана может избавлять нас от шума и помогать находить настоящее значение!

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



DeepSORT наконец-то!

Итак, мы теперь знаем, что такое фильтр Калмана и расстояние Махалонобиса. Технология DeepSORT просто связывает эти два понятия между собой для того, чтобы переносить информацию от одного кадра к другому, и добавляет новую метрику, под названием appearance. Сначала с помощью object detection определяются позиция, размер и класс одного bounding box. Потом можно в принципе применить Венгерский алгоритм, чтобы связать определенные объекты с ID объектов, которые раньше были на кадре и отслеживаются с помощью фильтров Калмана и все будет супер, как в оригинальном SORT. Но технология DeepSORT позволяет улучшить точность определения и уменьшить количество переключений между объектами, когда, допустим, один человек на кадре загораживает ненадолго другого, и теперь человек, которого загородили, считается новым объектом. Как она это делает?

Она добавляет крутой элемент в свою работу так называемый внешний вид людей, которые появляются на кадре (appearance). Этот внешний вид был натренирован отдельной нейронной сетью, которая была создана авторами DeepSORT. Они использовали порядка 1,100,000 картинок с более 1000 разных людей, чтобы нейронная сеть правильно предсказывала В оригинальном SORT есть проблема так как там не используется внешний вид объекта, то по факту, когда объект что-то закрывает на несколько кадров (например, другой человек или колона внутри здания), то алгоритм затем присваивает другой ID этому человеку в следствие чего так называемая память об объектах у оригинального SORT довольно краткосрочная.

Итак, теперь объекты имеют два свойства их динамика движения и их внешний вид. Для динамики у нас есть показатели, которые фильтруются и предсказываются с помощью фильтра Калмана (u,v,a,h,u,v,a,h), где u,v это позиция предсказанного прямоугольника по X и Y, a это соотношение сторон предсказанного прямоугольника (aspect ratio), h высота прямоугольника, ну и производные по каждой величине. Для внешнего вида тренировали нейронную сеть, которая имела структуру:



И в конце выдавала feature vector, размером 128x1. А дальше, вместо того, чтобы считать дистанцию между определенными объектами с помощью YOLO, и объектами, за которыми мы уже следили на кадре, а потом приписывать определенный ID просто с помощью расстояния Махалонобиса, авторы создали новую метрику для подсчета расстояния, которая включает в себя как предсказания с помощью фильтров Калмана, так и косинусовое расстояние (cosine distance), как называют его иначе, коэффициент Отиаи.

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



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

Таким образом, простая дополнительная метрика Da помогла создать новый, элегантный алгоритм DeepSORT, который применяется во многих проблемах и является довольно популярным в задаче Object Tracking.

Статья получилась довольно увесистой, спасибо тем, кто дочитал до конца! Надеюсь, мне удалось рассказать что-то новое и помочь Вам в понимании того, как работает Object Tracking на YOLO и DeepSORT.
Подробнее..

Компьютерное зрение в промышленной дефектоскопии Часть 1 Как мы заставляли нейронку пялиться на ржавчину

11.02.2021 14:07:02 | Автор: admin


Сегодня мы хотим поделиться опытом решения задачи детекции дефектов на снимках промышленных объектов методами современного компьютерного зрения.


Наш рассказ будет состоять из нескольких частей:


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

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


Заметка от партнера IT-центра МАИ и организатора магистерской программы VR/AR & AI компании PHYGITALISM.


Описание задачи



Рис 1. Схематичное изображение рассматриваемого проекта.


Машинное обучение (machine learning / ML) в общем и компьютерное зрение (computer vision / CV) в частности находят сегодня все больше применений в решение задач из промышленной области (пример). Начиная от задач нахождения бракованных деталей на конвейере и заканчивая управлением беспилотным транспортом везде используются глубокие архитектуры, позволяющие детектировать многочисленные объекты разных категорий, предсказывать пространственное расположение объектов друг относительно друга и многое другое.


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



Рис 2.Схематичное изображение парового котла, аналогичного тому, что рассматривался в проекте.


Кратко опишем предметную постановку задачи:


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


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


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


Мы постараемся осветить все основные этапы при разработке проекта, связанного с машинным


  • обучением;
  • сбор данных;
  • разметка данных;
  • дополнение данных (аугментация);
  • обучение модели;
  • оценка качества работы модели;
  • подготовка к внедрению.

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


Набор данных



Рис. 3 Пример объекта обучающей выборки фото, сделанное при осмотре внутренности остановленного котла.


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


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


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

Виды задач распознавания образов на изображениях



Рис.4 Пример разметки для задачи детекции объектов на изображениях из датасета MS COCO. Иллюстрация из репозитория detectron2.


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


  • выделения области интереса;
  • классификация объекта в области интереса.

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


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


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


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


Про устройство формата MS COCO и формы предсказаний можно узнать здесь.


Разметка данных



Рис. 5 Демонстрация работы CVAT для разметки дефекта трубы в виде полигональной маски.


Для того, чтобы разметить сырые данные существует несколько основных способов. Во-первых, можно воспользоваться услугами сервисов облачной распределенной разметки данных, вроде Amazon Mechanical Turk или Яндекс.Толока, во-вторых, если данные приходят не из реального мира, а генерируются искусственно, то разметку можно генерировать вместе с данными, ну и самым доступным способом является использование специализированного ПО для разметки данных.


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


Некоторые альтернативные инструменты разметки:



Усиление обобщающей способности


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



Рис. 5.1 Изображение из сообщества Memes on Machine Learning for Young Ladies. Там же можно увидеть пример того как не нужно делать аугментации.


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


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


Метрики качества


Для того, чтобы измерять качество работы алгоритма после процесса обучения на тестовой выборке в задачах детекции объектов на изображениях традиционно используют метрику mean average precision (mAP), рассчитанную для каждого класса по отдельности и усредненную для всех классов. Значение этой метрики рассчитываются при разных уровнях характеристики Intersection over Union (IoU). Здесь мы не будем подробно останавливаться на разъяснении устройства этих функций, всем заинтересованным предлагаем пройти по ссылкам ниже на статьи и заметки, которые помогут освоится в данном вопросе, но все же поясним некоторые основные моменты оценки качества работы алгоритмов в нашей задаче.


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


  • mAP (mean average precision) среднее значение точности по всем классам (поскольку у термина могут быть разные трактовки, рекомендуем ознакомится с различными вариантами здесь).
  • AP (средняя точность) средняя точность по каждому отдельному классу.
  • Precision Recall кривая.
  • Число случаев когда дефект был обнаружен и он на самом деле был (TP).
  • Число случаев когда дефект был обнаружен, но его не было на самом деле (FP) т. е. ложное срабатывание.

Расчет метрик производился следующим образом. Так как целевым объектом для оценки был ограничивающий прямоугольник (bounding box), то интерес для оценки представляет три свойства:


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

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


Рассмотрим пример вычисления метрик на основе примера из рис. ниже. Для этого определяется величина IoU, которая равна отношению площади пересечения прямоугольников (серый прямоугольник) к площади их объединения. Она принимает значения в отрезке [0;1]. Можно выбрать определённый порог и считать, что при превышении этого порога прямоугольники совпадают.


Определение TP:
Если IoU больше определенного порога и метки классов совпадают, то предсказание считается правильным.


Определение FP:
Если IoU меньше определенного порога и метки классов совпадают, то предсказание считается ложным.


На основании этих показателей рассчитывается точность и полнота. Если кратко, то точность показывает насколько хорошо модель предсказывает дефекты определенного класса из тех которые были обнаружены вообще. Чем больше значение, тем меньше ошибок совершается. Значение в отрезке от [0;1].



Рис. 6 Пример рассчитанной Precision-Recall кривой для одного из классов дефектов для архитектуры DetectoRS.


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



Рис. 7 Сравнение технических метрик для выбранных архитектур для процесса обучения.


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


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


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



*Рис. 8 Сравнение технических метрик для выбранных архитектур для процесса использования.* тестирование проводилось на видеокарте RTX 2080 Ti, тестирование проводилось на CPU AMD Ryzen 7 2700X Eight-Core Processor.*


Деление данных на обучающую и тестовую выборку


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


Рассмотрим пример, изображенный на рис. 9 случай, когда каждый объект принадлежит одному классу:

Рис. 9 Деление данных на тестовую и обучающую выборку.


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

Рис. 10 Каждый объект может содержать иметь несколько классов.


Для решения проблемы разделения такого вида данных на подвыборки была использована библиотека scikit-multilearn и метод iterative_train_test_split. На вход подавалась матрица из нулей и единиц. Каждая строка обозначала изображение. Единицы стояли в столбцах с номерами соответствующими номерам классов. Если применить функцию к объектам на рис. выше, то получится следующее разделение:

Рис. 11 Результат разделения на тестовую и обучающую выборку.


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


Выбор архитектуры


На основе результатов анализа известных архитектур собранных на paperwithcode (на момент 3 квартала 2020 года) для детектирования дефектов были выбраны две архитектуры:




Рис. 12 Сравнение архитектур на бенчмарке MS COCO object detection с сайта papesrwithcode (3 квартал 2020 года).



Рис.13 Сравнение архитектур на бенчмарке MS COCO, полученное авторами архитектуры YOLOv4.


YOLOv4 попала в этот список из-за своей скорости работы. Мы хотели посмотреть на результаты разных моделей и выбрать итоговый вариант в зависимости от требований. Потенциально могла быть потребность в обработке видео и для этого случая планировалось использовать YOLOv4. Более точная, но медленная работа ожидалась от DetectoRS.


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


DetectoRS


Данная архитектура базируется на использовании специального типа сверток (Switchable Atrouse Convolution / SAC) и в верхнем уровне устроена в виде рекурсивной пирамиды (Recursive Feature Pyramid / RFP), объединяющий локальные и глобальные признаки.



Рис. 14 Основные нововведения, используемые авторами архитектуры DetectoRS: (a) рекурсивная пирамида признаковых описаний, используемая для объединения глобальных признаков на изображении; (b) переключательные свертки типа atrouse для работы с локальными признаками.


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


YOLOv4


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

Рис. 15 Принцип работы из оригинальной статьи про YOLO.


Видео с объяснением работы можно найти здесь.


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


Фреймворки для обучения моделей в задачах CV


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


Для разработки под Python, двумя наиболее популярными фреймворками являются MMdetection (open source) и Detectron2 (Facebook research). Для разработки под C, существует фреймворк Darknet (open source). Подробнее про то, как использовать данные фреймворки можно прочитать в заметках один, два, три.


MMDetection


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


Устройство фреймворка


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


  • подготовить веса при тренированной модели в формате .pth или прописать самостоятельно инициализацию новых весов;
  • написать конфигурационный файл в виде Python скрипта со всеми настройками для сети и описание хода обучения, валидации и пр.;
  • выбрать способ логирования процесса обучения (доступна запись в текстовый файл и логирование с помощью tensorboard);
  • организовать данные в файловой системе согласно выбранному типу разметки (доступны MS COCO, Pascal VOC и поддерживается возможность внедрение пользовательских форматов);
  • написать основный скрипт, собирающий воедино все перечисленные выше составные части.

После тестов локально на компьютере, на котором была установлена и развернута среда для работы с MMDetection, аналогично YOLOv4, сборка была перенесена внутрь NVidia Docker контейнера.


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


Наш конфигурационный файл для обучения на данных с трубами для 9 классов дефектов:
# Обучения на 9 классах без масокmodel = dict(    type='CascadeRCNN',    pretrained='torchvision://resnet50',    backbone=dict(        type='DetectoRS_ResNet',        depth=50,        num_stages=4,        out_indices=(0, 1, 2, 3),        frozen_stages=1,        norm_cfg=dict(type='BN', requires_grad=True),        norm_eval=True,        style='pytorch',        conv_cfg=dict(type='ConvAWS'),        sac=dict(type='SAC', use_deform=True),        stage_with_sac=(False, True, True, True),        output_img=True),    neck=dict(        type='RFP',        in_channels=[256, 512, 1024, 2048],        out_channels=256,        num_outs=5,        rfp_steps=2,        aspp_out_channels=64,        aspp_dilations=(1, 3, 6, 1),        rfp_backbone=dict(            rfp_inplanes=256,            type='DetectoRS_ResNet',            depth=50,            num_stages=4,            out_indices=(0, 1, 2, 3),            frozen_stages=1,            norm_cfg=dict(type='BN', requires_grad=True),            norm_eval=True,            conv_cfg=dict(type='ConvAWS'),            sac=dict(type='SAC', use_deform=True),            stage_with_sac=(False, True, True, True),            pretrained='torchvision://resnet50',            style='pytorch')),    rpn_head=dict(        type='RPNHead',        in_channels=256,        feat_channels=256,        anchor_generator=dict(            type='AnchorGenerator',            scales=[8],            ratios=[0.5, 1.0, 2.0],            strides=[4, 8, 16, 32, 64]),        bbox_coder=dict(            type='DeltaXYWHBBoxCoder',            target_means=[0.0, 0.0, 0.0, 0.0],            target_stds=[1.0, 1.0, 1.0, 1.0]),        loss_cls=dict(            type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),        loss_bbox=dict(            type='SmoothL1Loss', beta=0.1111111111111111, loss_weight=1.0)),    roi_head=dict(        type='CascadeRoIHead',        num_stages=3,        stage_loss_weights=[1, 0.5, 0.25],        bbox_roi_extractor=dict(            type='SingleRoIExtractor',            roi_layer=dict(type='RoIAlign', out_size=7, sample_num=0),            out_channels=256,            featmap_strides=[4, 8, 16, 32]),        bbox_head=[            dict(                type='Shared2FCBBoxHead',                in_channels=256,                fc_out_channels=1024,                roi_feat_size=7,                num_classes=9,                bbox_coder=dict(                    type='DeltaXYWHBBoxCoder',                    target_means=[0.0, 0.0, 0.0, 0.0],                    target_stds=[0.1, 0.1, 0.2, 0.2]),                reg_class_agnostic=True,                loss_cls=dict(                    type='CrossEntropyLoss',                    use_sigmoid=False,                    loss_weight=1.0),                loss_bbox=dict(type='SmoothL1Loss', beta=1.0,                               loss_weight=1.0)),            dict(                type='Shared2FCBBoxHead',                in_channels=256,                fc_out_channels=1024,                roi_feat_size=7,                num_classes=9,                bbox_coder=dict(                    type='DeltaXYWHBBoxCoder',                    target_means=[0.0, 0.0, 0.0, 0.0],                    target_stds=[0.05, 0.05, 0.1, 0.1]),                reg_class_agnostic=True,                loss_cls=dict(                    type='CrossEntropyLoss',                    use_sigmoid=False,                    loss_weight=1.0),                loss_bbox=dict(type='SmoothL1Loss', beta=1.0,                               loss_weight=1.0)),            dict(                type='Shared2FCBBoxHead',                in_channels=256,                fc_out_channels=1024,                roi_feat_size=7,                num_classes=9,                bbox_coder=dict(                    type='DeltaXYWHBBoxCoder',                    target_means=[0.0, 0.0, 0.0, 0.0],                    target_stds=[0.033, 0.033, 0.067, 0.067]),                reg_class_agnostic=True,                loss_cls=dict(                    type='CrossEntropyLoss',                    use_sigmoid=False,                    loss_weight=1.0),                loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0))        ],        train_cfg=[            dict(                assigner=dict(                    type='MaxIoUAssigner',                    pos_iou_thr=0.5,                    neg_iou_thr=0.5,                    min_pos_iou=0.5,                    match_low_quality=False,                    ignore_iof_thr=-1),                sampler=dict(                    type='RandomSampler',                    num=512,                    pos_fraction=0.25,                    neg_pos_ub=-1,                    add_gt_as_proposals=True),                pos_weight=-1,                debug=False),            dict(                assigner=dict(                    type='MaxIoUAssigner',                    pos_iou_thr=0.6,                    neg_iou_thr=0.6,                    min_pos_iou=0.6,                    match_low_quality=False,                    ignore_iof_thr=-1),                sampler=dict(                    type='RandomSampler',                    num=512,                    pos_fraction=0.25,                    neg_pos_ub=-1,                    add_gt_as_proposals=True),                pos_weight=-1,                debug=False),            dict(                assigner=dict(                    type='MaxIoUAssigner',                    pos_iou_thr=0.7,                    neg_iou_thr=0.7,                    min_pos_iou=0.7,                    match_low_quality=False,                    ignore_iof_thr=-1),                sampler=dict(                    type='RandomSampler',                    num=512,                    pos_fraction=0.25,                    neg_pos_ub=-1,                    add_gt_as_proposals=True),                pos_weight=-1,                debug=False)        ],        test_cfg=dict(            score_thr=0.05, nms=dict(type='nms', iou_thr=0.5),            max_per_img=100)))train_cfg = dict(    rpn=dict(        assigner=dict(            type='MaxIoUAssigner',            pos_iou_thr=0.7,            neg_iou_thr=0.3,            min_pos_iou=0.3,            match_low_quality=True,            ignore_iof_thr=-1),        sampler=dict(            type='RandomSampler',            num=256,            pos_fraction=0.5,            neg_pos_ub=-1,            add_gt_as_proposals=False),        allowed_border=0,        pos_weight=-1,        debug=False),    rpn_proposal=dict(        nms_across_levels=False,        nms_pre=2000,        nms_post=2000,        max_num=2000,        nms_thr=0.7,        min_bbox_size=0),    rcnn=[        dict(            assigner=dict(                type='MaxIoUAssigner',                pos_iou_thr=0.5,                neg_iou_thr=0.5,                min_pos_iou=0.5,                match_low_quality=False,                ignore_iof_thr=-1),            sampler=dict(                type='RandomSampler',                num=512,                pos_fraction=0.25,                neg_pos_ub=-1,                add_gt_as_proposals=True),            pos_weight=-1,            debug=False),        dict(            assigner=dict(                type='MaxIoUAssigner',                pos_iou_thr=0.6,                neg_iou_thr=0.6,                min_pos_iou=0.6,                match_low_quality=False,                ignore_iof_thr=-1),            sampler=dict(                type='RandomSampler',                num=512,                pos_fraction=0.25,                neg_pos_ub=-1,                add_gt_as_proposals=True),            pos_weight=-1,            debug=False),        dict(            assigner=dict(                type='MaxIoUAssigner',                pos_iou_thr=0.7,                neg_iou_thr=0.7,                min_pos_iou=0.7,                match_low_quality=False,                ignore_iof_thr=-1),            sampler=dict(                type='RandomSampler',                num=512,                pos_fraction=0.25,                neg_pos_ub=-1,                add_gt_as_proposals=True),            pos_weight=-1,            debug=False)    ])test_cfg = dict(    rpn=dict(        nms_across_levels=False,        nms_pre=1000,        nms_post=1000,        max_num=1000,        nms_thr=0.7,        min_bbox_size=0),    rcnn=dict(        score_thr=0.05, nms=dict(type='nms', iou_thr=0.5), max_per_img=100))dataset_type = 'CocoDataset'data_root = 'data/coco/'classes = ('ПРМУ_поперечная трещина на изгибе', 'ПРМУ_выход трубы из ряда',           'ПРМУ_Крип', 'ПРМУ_свищи', 'ПРМУ_разрыв трубы',           'ПРМУ_поперечная трещина в околошовной зоне',           'ПРМУ_трещина в основном металле', 'ПРМУ_продольные трещины',           'ПРМУ_Цвета побежалости')img_norm_cfg = dict(    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)train_pipeline = [    dict(type='LoadImageFromFile'),    dict(type='LoadAnnotations', with_bbox=True),    dict(type='Resize', img_scale=(1280, 720), keep_ratio=True),    dict(type='RandomFlip', flip_ratio=0.5),    dict(        type='Normalize',        mean=[123.675, 116.28, 103.53],        std=[58.395, 57.12, 57.375],        to_rgb=True),    dict(type='Pad', size_divisor=32),    dict(type='DefaultFormatBundle'),    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels'])]test_pipeline = [    dict(type='LoadImageFromFile'),    dict(        type='MultiScaleFlipAug',        img_scale=(1280, 720),        flip=False,        transforms=[            dict(type='Resize', keep_ratio=True),            dict(type='RandomFlip'),            dict(                type='Normalize',                mean=[123.675, 116.28, 103.53],                std=[58.395, 57.12, 57.375],                to_rgb=True),            dict(type='Pad', size_divisor=32),            dict(type='ImageToTensor', keys=['img']),            dict(type='Collect', keys=['img'])        ])]data = dict(    samples_per_gpu=2,    workers_per_gpu=1,    train=dict(        type='CocoDataset',        classes=('ПРМУ_поперечная трещина на изгибе',                 'ПРМУ_выход трубы из ряда', 'ПРМУ_Крип', 'ПРМУ_свищи',                 'ПРМУ_разрыв трубы',                 'ПРМУ_поперечная трещина в околошовной зоне',                 'ПРМУ_трещина в основном металле', 'ПРМУ_продольные трещины',                 'ПРМУ_Цвета побежалости'),        ann_file='data/coco/annotations/instances_train.json',        img_prefix='data/coco/train/',        pipeline=[            dict(type='LoadImageFromFile'),            dict(type='LoadAnnotations', with_bbox=True),            dict(type='Resize', img_scale=(1280, 720), keep_ratio=True),            dict(type='RandomFlip', flip_ratio=0.5),            dict(                type='Normalize',                mean=[123.675, 116.28, 103.53],                std=[58.395, 57.12, 57.375],                to_rgb=True),            dict(type='Pad', size_divisor=32),            dict(type='DefaultFormatBundle'),            dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels'])        ]),    val=dict(        type='CocoDataset',        classes=('ПРМУ_поперечная трещина на изгибе',                 'ПРМУ_выход трубы из ряда', 'ПРМУ_Крип', 'ПРМУ_свищи',                 'ПРМУ_разрыв трубы',                 'ПРМУ_поперечная трещина в околошовной зоне',                 'ПРМУ_трещина в основном металле', 'ПРМУ_продольные трещины',                 'ПРМУ_Цвета побежалости'),        ann_file='data/coco/annotations/instances_val.json',        img_prefix='data/coco/val/',        pipeline=[            dict(type='LoadImageFromFile'),            dict(                type='MultiScaleFlipAug',                img_scale=(1280, 720),                flip=False,                transforms=[                    dict(type='Resize', keep_ratio=True),                    dict(type='RandomFlip'),                    dict(                        type='Normalize',                        mean=[123.675, 116.28, 103.53],                        std=[58.395, 57.12, 57.375],                        to_rgb=True),                    dict(type='Pad', size_divisor=32),                    dict(type='ImageToTensor', keys=['img']),                    dict(type='Collect', keys=['img'])                ])        ]),    test=dict(        type='CocoDataset',        classes=('ПРМУ_поперечная трещина на изгибе',                 'ПРМУ_выход трубы из ряда', 'ПРМУ_Крип', 'ПРМУ_свищи',                 'ПРМУ_разрыв трубы',                 'ПРМУ_поперечная трещина в околошовной зоне',                 'ПРМУ_трещина в основном металле', 'ПРМУ_продольные трещины',                 'ПРМУ_Цвета побежалости'),        ann_file='data/coco/annotations/instances_val.json',        img_prefix='data/coco/val/',        pipeline=[            dict(type='LoadImageFromFile'),            dict(                type='MultiScaleFlipAug',                img_scale=(1280, 720),                flip=False,                transforms=[                    dict(type='Resize', keep_ratio=True),                    dict(type='RandomFlip'),                    dict(                        type='Normalize',                        mean=[123.675, 116.28, 103.53],                        std=[58.395, 57.12, 57.375],                        to_rgb=True),                    dict(type='Pad', size_divisor=32),                    dict(type='ImageToTensor', keys=['img']),                    dict(type='Collect', keys=['img'])                ])        ]))evaluation = dict(interval=1, metric='bbox')optimizer = dict(type='SGD', lr=0.0001, momentum=0.9, weight_decay=0.0001)optimizer_config = dict(grad_clip=None, type='OptimizerHook')lr_config = dict(    policy='step',    warmup=None,    warmup_iters=500,    warmup_ratio=0.001,    step=[8, 11],    type='StepLrUpdaterHook')total_epochs = 12checkpoint_config = dict(interval=-1, type='CheckpointHook')log_config = dict(    interval=10,    hooks=[dict(type='TextLoggerHook'),           dict(type='TensorboardLoggerHook')])dist_params = dict(backend='nccl')log_level = 'INFO'load_from = './checkpoints/detectors_cascade_rcnn_r50_1x_coco-32a10ba0.pth'resume_from = Noneworkflow = [('train', 1)]work_dir = './logs'seed = 0gpu_ids = range(0, 1)

Обучение модели


Обучение модели занимало порядка 2 часов на RTX 2080 Ti. Прогресс можно было отслеживать с помощью запущенного tensorboard. Ниже приведены графики эволюции метрик и функции ошибки в процессе обучения на реальных данных.



Рис. 16 Зависимость mAP от числа итераций обучения для архитектуры DetectoRS для датасета дефектов труб по 9 классам на валидационном датасете.



Рис. 17 Зависимость функции потерь (multiclass cross entropy) от числа итераций обучения для архитектуры DetectoRS для датасета дефектов труб по 9 классам.


Подготовка модели для использования


После того, как сеть была обучена, ее конфигурационный файл и веса, были помещены внутрь отдельного NVidia Docker контейнера вместе с самим фреймворком и необходимыми зависимостями, для того, чтобы использовать готовую архитектуру внутри прототипа ПО для детекции дефектов.


Стоит отметить, что, несмотря на то, что данная архитектура обучается в несколько раз быстрее чем YOLOv4, она занимает в 2 раза больше памяти (500 MB для DetectoRS против 250 MB для YOLOv4 для хранения весов модели) и работает на порядок медленнее (1 с. для DetectoRS против 10 мс. для YOLOv4).


Малое время обучения DetectoRS отчасти объясняется тем, что веса базовых слоев сети (backbone и neck) были взяты из претренированной на ImageNet датасете аналогичной архитектуры и в процессе обучения не изменялись. Такой прием называется transfer learning. Про него вы можете подробнее прочитать в этой заметке.


Darknet


Оригинальная реализация YOLOV4 написана на C c использованием CUDA C. Было принято решение использовать оригинальную реализацию модели, хотя обычно доминируют эксперименты на Python. Это накладывало свои ограничения и риски, связанные с необходимость разбираться в коде на C, в случае каких-то проблем или переделки частей под свои нужды. Подробной документации с примерами не было, поэтому пришлось в некоторых местах смотреть исходный код на C.
Для успешного запуска нужно было решить несколько проблем:


  1. Собрать проект.
  2. Понять что необходимо для обучения модели.
  3. Обучить модель.
  4. Подготовить код для использования.

Сборка проекта


Первая сборка проекта происходила на Windows. Для сборки использовался CMake, поэтому особых проблем с этим не возникло. Единственная проблема была с компиляцией динамической библиотеки для обёртки на Python. Пришлось редактировать файл с проектом для Visual Studio, чтобы включить поддержку CUDA. Динамическая библиотека была нужна т. к. это позволяло использовать код на Python для запуска модели.


После было принято решение перенести сборку внутрь Docker контейнера. Для того чтобы оставить возможность использовать видеокарту был установлен NVIDIA Container Toolkit. Это позволяет достаточно просто организовать перенос проекта на другую машину при необходимости, а также упрощает дальнейшее использование. Благодаря наличию различных образов nvidia/cuda на Docker Hub, можно достаточно просто менять конфигурации. Например, переключение между различными версиями CUDA или cuDNN.


Необходимые файлы для обучения


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


Обучение модели


Обучение модели производилось в контейнере NVIDIA Docker. Занимало порядка 12 часов на RTX 2080 Ti. Прогресс можно было отслеживать периодически копируя график функции потерь из контейнера на хост, где запущен контейнер. Красным показано значение mAP на тестовой выборке.



Рис. 18 График обучения YOLOv4.


Подготовка модели для использования


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


Результаты


После всех экспериментов были выявлены следующие зависимости:


  • Обучение на смеси данных (реальные + искусственные) приводит к небольшому ухудшению обобщающей способности (это связано с тем, что в данной итерации генератора синтетических данных присутствуют недостаточно разнообразные данные и однотипное освещение);
  • После недолгого подбора гиперпараметров для архитектуры DetectoRS удалось добиться показателя $mAP (IoU=0.5) = 0.85$, а для архитектуры YOLOv4 $mAP(IoU=0.5) = 0.74$ ;
  • Некоторые типы дефектов, такие как разного рода трещины или выход труб из ряда, детектируются лучше чем иные типы дефектов, такие как вздутие труб. Это можно объяснить не только дисбалансом и малым количеством примеров, но и тем, что для определения некоторых типов дефектов, нужно больше пространственной информации (аналогично этому, в реальной детекции дефектов, некоторые дефекты определяются на глаз, а некоторые требуют специальных измерительных приборов). Потенциально, использование помимо RGB каналов с камеры также еще и канала глубины (RGB-D) могло бы помочь с детектированием этих сложных пространственных дефектов: в этом случае мы смогли бы прибегнуть к методам и алгоритмама 3D ML.


Рис. 19 Сравнение работы архитектур, обученных на разных датасетах: Real только реальные изображение, Mixed обучении на смеси реальных и синтетических изображений, Mask обучения на реальных изображениях, с многоугольной разметкой областей.



Рис. 20 Пример некорректной детекции модели DetectoRS, обученной на синтетических данных отсутствие посторонних предметов в синтетическом датасете приводит к определению куска деревянной балки как трещины.



Рис. 21 Пример детекции дефектов в виде многоугольных масок модели DetectoRS, обученной на датасете реальных изображений с соответствующей разметкой.



Рис. 22 Пример детекции дефектов в виде прямоугольников для одного и того же изображения с помощью разных архитектур.


Финальное решение с интерфейсом


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


Для архитектуры ПО была выбрана следующая схема:
Финальное решение с интерфейсом
В итоге, был получен работающий прототип ПО с интерфейсом, который можно было бы просто использовать как со стороны пользователя так и для разработчиков. Так как проекты на основе ML часто требуют вычислительных мощностей видеокарт NVIDIA для своей работы, то было принято решение сделать клиент-серверное приложение. При необходимости всё можно развернуть на одном компьютере. Если же есть в наличии свободный сервер с видеокартой, то основную логику можно перенести туда, оставив возможность любым пользователям использовать сервис даже на слабом по вычислительным возможностям компьютере.


Для архитектуры ПО была выбрана следующая схема:

Рис. 23 Схема архитектуры прототипа ПО для детектирования дефектов на изображениях.


Все модели для детектирования дефектов работают внутри NVIDIA Docker. Остальные части, кроме Web-интерфейса внутри обычных контейнеров Docker. Логика работы следующая:


  1. Пользователь загружает изображение и выбирает нужную модель вместе с порогом принятия решения.
  2. Изображение отправляется на сервер. Оно сохраняется на диске и сообщение с заданием на обработку попадает в RabbitMQ, в соответствующую очередь.
  3. Модель берёт сообщение с заданием из очереди, когда готова выполнить предсказание. Выполняет предсказание и сохраняет необходимые файлы на диск. Отправляет сообщение в RabbitMQ о готовности результат.
  4. Когда результат готов он отображается в web-интерфейсе.

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


RabbitMQ также позволяет изолировать систему друг от друга и начать разработку независимо. При разработке сервера не надо дожидаться реализации ML моделей и наоборот. Каждая система общается только с RabbitMQ. В случае падения какого-то сервиса сообщение не потеряется, если соответствующим образом настроить RabbitMQ.



Рис. 24 Демонстрация работы созданного прототипа ПО для детекции дефектов на трубах.


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


Основные источники


Статьи описывающие SOTA глубокие архитектуры в задаче детекции:

Статьи, посвященные выбору метрик качества в задачах детекции объектов на изображениях:

Фундаментальные монографии на тему современного компьютерного зрения:
  • Szeliski, R., 2010. Computer vision: algorithms and applications. Springer Science & Business Media.
  • Nixon, M. and Aguado, A., 2019. Feature extraction and image processing for computer vision. Academic press.
  • Jiang, X. ed., 2019. Deep Learning in Object Detection and Recognition. Springer.
  • Pardo, A. and Kittler, J. eds., 2015. Progress in Pattern Recognition, Image Analysis, Computer Vision, and Applications: 20th Iberoamerican Congress, CIARP 2015, Montevideo, Uruguay, November 9-12, 2015, Proceedings (Vol. 9423). Springer.
Подробнее..

Категории

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

  • Имя: Макс
    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