Как стать автором
Обновить

Детекция объектов. YOLO. Часть 2

Уровень сложностиСредний
Время на прочтение9 мин
Количество просмотров6.1K

Введение

Когда пытаешься разобраться в работе YOLO по статьям в интернете, постоянно натыкаешься на примерно такое объяснение: «Алгоритм делит изображение сеткой SxS, где каждому элементу этой сетки соответствует N ббоксов с координатами, предсказаниями классов и тд...». Но лично мне становилось только непонятнее от такого высокоуровнего описания.. Ведь в исследованиях часто всё происходит примерно так: перебирают гипотезы, пока не получат приемлемый результат, а потом уже придумывают красивое описание. Поэтому для ясности хочется рассказать, как вообще приходили к идеям, которые ложились в основу YOLOv1 и последующих версий.

Немного про особенности Feature Map

Сначала поговорим про Feature Map, чтобы потом стало понятно откуда взялась эта непонятная сетка SxS)) Итак, давайте представим, что у нас есть маленькое трехканальное (RGB) изображение 10×10 пикселей. Мы прогоняем его через две свёртки: с ядром 5×5 и ядром 3×3. Соответствующий код на Torch выглядит так:

import torch
import torch.nn as nn

image = torch.rand(3,10,10) # Типа изображение

conv1 = nn.Conv2d(3,1,5) # Свертка с ядром 5
conv2 = nn.Conv2d(1,1,3) # Свертка с ядром 3

feature_map1 = conv1(image)               # Результат после первой свёрки
print(f'FM1 shape: {feature_map1.shape}') # FM1 shape: torch.Size([1, 6, 6])
feature_map2 = conv2(feature_map1)        # Результат после второй всёртки
print(f'FM2 shape: {feature_map2.shape}') # FM2 shape: torch.Size([1, 4, 4])

Визуализация этого кода примерно такая:

Визуализация кода
Визуализация кода

На изображении видно, что левая верхняя ячейка Feature map 2 является результатом свертки выделенной области из Feature map 1. В то же время, каждая ячейка из выделенной области на Feature map 1 является результатом сверки соответствующей области исходного изображения. Получается, что выделенный элемент на Feature map 2 отображает признаки выделенной области на исходном изображении Input image. Поскольку Feature map 2 имеет размер 4×4 — можно сказать, что он как бы делит исходное изображение сеткой 4×4, так как каждый его элемент смотрит на 1 из 16 частей изображения.

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

Low, Mid, High Level Features
Low, Mid, High Level Features

Low‑Level Feature описывает всякие непонятные линии и закарючки, так как ядро сворачивает очень маленькую часть изображения. Mid‑Level Feature описывает уже что‑то более осмысленное, так как отображает часть побольше. И наконец High‑Level Feature уже содержит информацию, которую можно как‑то интерпртировать (наличие колеса, фар, решетки радиатора и тп)

YOLOv1

В 2015 году Joseph Redmon опубликовал статью You Only Look Once: Unified, Real‑Time Object Detection. Гипотеза, которую ему удалось проверить и получить хороший результат заключалась в следующем: «А что если взять какую‑то предобученную модель для классификации картинок и просто заменить последнии слои так, чтобы она предсказывала не вероятность классов, а какой‑то тензор, в котором будет содержаться информация о ббоксах и классах?».

Архитектура

В статье сначала строится архитектура для классификации изображений, вдохновленная GoogLeNet:

я тут немного порисовал, так как в оригинальной статье уже сразу финальная архитектура с последними слоями для детекции
я тут немного порисовал, так как в оригинальной статье уже сразу финальная архитектура с последними слоями для детекции

Эта архитектура училась классифицировать изображения на ImageNet (1000 classes) и на валидации вошла в top-5 Accuracy с 88%. После того, как мы обучили классификатор, замораживаем первые 20 слоёв. Т.е. их веса больше не будут меняться в ходе дальнейшего обучения (ничего волшебного - это обычный Fine-Tuning)

Теперь у нас есть предобученные 20 слоёв, которые извлекают информацию из изображения. Добавим к ним 4 необученных сверточных слоя + полносвязный слой. В самом конце возвращается тензор, в котором будет информация о ббоксах и соответствующих классах.
Вот финальная архитектура (Изменились только последние слои 😳👍🏻):

Архитектура YOLOv1
Архитектура YOLOv1

Пока что не обращаем внимание на размерность выходного тензора 7\times7\times30
Давайте подумаем, а как вообще записать информацию о ббоксах в выходной тензор?

Размер выходного тензора

В прошлой статье я говорил, что для описания бокса нам нужно 5 чисел:
x, y, w, h, conf
Для того, чтоб предсказать класс объекта, находящегося в боксе, нам нужен вектор длины C , где C — это количество классов (каждая компонента соответствует вероятности какого‑то класса). В датасете Pascal Voc 20 классов, так что C=20 . Следовательно для информации об одном ббоксе нам нужен вектор длины 25\space(5 +20=25)

Ббоксы должны соответстовать какой‑то области изображения, следовательно, выходной тензор будет иметь размерS \times S, где каждый элемент отражает область на исходном изображении. Автор взял S=7, то есть выходной тензор как бы разбивает исходное изображение на 7 * 7 частей.

Далее, пусть каждой области соответствует ббоксов (автор выбрал B=2)
Т.е. каждому элементу выходного тензора S \times S должен быть сопоставлен вектор размераB \cdot(5 + C). В статье класс решили предсказывать только для ббокса с самым большим conf , поэтому формула немного упростится и станет B \cdot 5 + C
Общая формула размера выходного тензора выглядит так: S\times S \times (B \cdot5 + C)
Отсюда мы и получаем 7 \times 7 \times 30🤯😳🥳

Лосс и обучение

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

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

YOLO предсказывает несколько ббоксов для каждой области изображения. Во время обучения, мы хотим, чтобы только один ббокс был ответственным за каждый объект. Ответственным выбирается тот, у кого самый большой IoU c Ground Truth (истинным ббоксом из разметки)

Далее в статье показываются метрики, по которым видно, насколько YOLO быстрее, чем Fast R‑CNN и какая у неё хорошая метрика mAP на валидационном датасете VOC 2007. Интересно заметить, что в таблицах показаны метрики для комбинированной модели YOLO + Fast R‑CNN, которые дают хороший результат по качеству.

YOLOv2 (YOLO9000)

На волне хайпа уже в следующем году Joseph Redmon публикует улучшение YOLOv1 в этой статье. Название 9000 говорит о том, что модель способна отличить аж 9к классов! При этом оставаясь достаточно качественной и быстрой.

Главным недостатком предыдущей модели были ошибки локализации + маленький recall, по сравнению с двухстадийными детекторами. Относительно маленький recall значит, что модель часто вообще не видит объект там, где его видит, например, Fast R‑CNN. Поэтому основная задача — это улучшить геометрическую точность предсказания ббоксов и recall.

Anchor Boxes

Помимо всяких улучшений за счет батч норма, увеличения разрешения и тп, вводится очень важное архитектурное изменение, заключающееся в добавлении Anchor Boxes (я не знаю как это перевести на русский). Ранее YOLO предсказывала координаты с нуля, прямо из полносвязного слоя. Как мы помним, двухстадийные детекторы предварительно имели целый набор гипотез для ббоксов. Как показывает практика, модели гораздо проще предсказывать поправки к наперед заданным ббоксам, чем с нуля их строить. Поэтому в YOLOv2 решили взять хорошую идею из двухстадийных детекторов и использовать Anchor Boxes! (Возможно такая идея пришла как раз после работы с комбинированной моделью YOLOv1 + Fast R‑CNN). Такое нововведение также позволило предсказывать гораздо больше ббоксов.

После добавления Anchor Boxes в YOLO появилась проблема, заключающаяся в том, что размеры боксов подбираются вручную. Решением проблемы стало использование k‑means clustering для автоматической генерации гипотез. По сути дефолтные боксы (Anchor Boxes) в YOLO генерируются на основе конкретного датасета, на котором вы хотите обучать модель.

Другая проблема заключалась в том, что на ранних итерациях обучения, нейронке довольно сложно предсказывать (x, y), поскольку изначально её веса рандомные, и смещение дефолт бокса может быть произвольным, т. е. он может вообще уплыть в любую часть изображения. Чтобы этого избежать, YOLOv2 вместо предсказания поправок, предсказывает относительные координаты внутри grid cell (элемент сетки, которая "делит" изображение), и коэффициенты для поправок к ширине и высоте.

Сеть выдаёт по прежнему 5 чисел для каждого ббокса: t_x, t_y, t_w, t_h, t_0
Пусть (с_x, c_y) — это смещение координат левого верхнего угла grid cell (это надо для расчета абсолютных координат, тк ранее мы говорили, что предсказывать будем относительные координаты)
Пусть (p_w, p_h) — это ширина и высота дефолт бокса
Тогда финальные координаты предсказанного бокса считаются следующим образом:

 \begin{cases} b_x=\sigma(t_x)+c_x \\ b_y=\sigma(t_y) + c_y\\ b_w=p_w e^{t_w} \\ b_h=p_he^{t_h}  \end{cases}

Выглядит сложно на первый взгляд, но по сути модель просто КАК‑ТО предсказала свои 5 чисел, потом для координат мы используем функцию активации \sigma(x), у которой область значений [0, 1], а далее прибавили координаты левого угла grid cell, чтобы перейти к абсолютным координатам. Ширину и высоту дефолт бокса мы просто домножили на коэффициенты, зависящие от параметров модели. Важно помнить, что это мы сами так ввели и определили вычисление координат ббоксов, а дальше оно само уже под капотом обучится.

Детекция объектов на маленьких участках изображения

Чтобы находить объекты разного масштаба, нам надо иметь несколько Feature Map. Как я уже говорил в начале этой статьи: есть Low-Level Features и High-Level Features. Так вот, чтобы находить мелкие объекты - нам нужен Low-Level Feature Map. В YOLOv2 используется passthrough layer, который конкатенирует разные Feature Map, предварительно приводя их к одному размеру.

Немного конкретнее: в YOLOv2 после первых сверток есть Feature Map размера 26\times26\times512 , который содержит в себе низкоуровневую информацию. После последних сверток у нас есть Feature Map размера 13\times13 . Чтобы их объединить, нам надо сделать reshape для низкоуровневых фичей: 26\times26\times512=13*2\times13*2\times512=13\times13\times2048и теперь можно объединять с High-Level Features

Новый Feature extractor

В YOLOv2 поменяли backbone, теперь в основе лежит сеть Darknet-19. Она чем-то похожа на VGG, тк в основном там фильтры 3x3, но она не такая толстая и ресурсозатратная. Думаю про Darknet-19 читатель может отдельно почитать, если возник такой интерес🤓

В целом, это все основные изменения в архитектуре YOLOv2

YOLOv3

Свою третью последнюю статью про YOLO Joseph Redmon начинает многообещающе со следующих слов:

I didn’t do a whole lot of research this year. Spent a lot of time on Twitter. Played around with GANs a little. I had a little momentum left over from last year. I managed to make some improvements to YOLO. But, honestly, nothing like super interesting, just a bunch of small changes that make it better

Во-первых, YOLOv3 теперь может работать уже на ТРЕХ разных скейлах. Это значит, что информация извлекается из 3-х разных Feature Map. Вдобавок в статье уже ссылаются на Feature Pyramid Networks. По сути эти "пирамиды" описывают способ, которым можно объединить Feature Map с разных уровней сверток. Вот пример того, как он работает:

import torch
import torch.nn as nn
# 3 Feature Map'ы, которые мы хотим объединить
high_feature_map = torch.rand(1, 512, 13, 13) # high level features
mid_feature_map = torch.rand(1, 256, 26, 26)  # middle level features
low_feature_map = torch.rand(1, 128, 52, 52)  # low level features

Нужно, чтобы у всех тензоров было одинаковое число каналов, например, 64 (это 2-я компонента тензора). Сделаем это при помощи сверток с единичным ядром:

# Сделаем для каждого тензора 64 канала
h_conv = nn.Conv2d(512, 64, 1) # Свертка для high level feature map
m_conv = nn.Conv2d(256, 64, 1) # Свертка для middle level feature map
l_conv = nn.Conv2d(128, 64, 1) # Свертка для low level feature map

high_feature_map = h_conv(high_feature_map)
mid_feature_map = m_conv(mid_feature_map)
low_feature_map = l_conv(low_feature_map)

print(high_feature_map.shape) # torch.Size([1, 64, 13, 13])
print(high_feature_map.shape) # torch.Size([1, 64, 26, 26])
print(high_feature_map.shape) # torch.Size([1, 64, 52, 52])

Отлично! Теперь надо сложить high_feature_map и mid_feature_map. Для этого сделаем high_feature_map такого же размера при помощи nn.Upsample

upsample = nn.Upsample(scale_factor=2, mode='nearest')
high_feature_map = upsample(high_feature_map)
print(high_feature_map.shape) # torch.Size([1, 64, 26, 26])
high_mid_feature_map = torch.add(high_feature_map, mid_feature_map)

Теперь аналогично делаем Upsampling для high_mid_feature_map, складываем его с low_feature_map и получаем тензор, который содержит информацию c 3-х разных scale. Таким образом YOLOv3 может видеть ещё более мелкие объекты на изображении.

Darknet-19 ---> Darknet-53 🤯

В YOLOv3 поменялся Feature extractor, теперь там 53 сверточных слоя, отсюда и название)) Cеть стала значительно глубже, что позволило улучшить точность. В Darknet-53 встречаются Residual блоки, аналогичные тем, что используются в сети ResNet. Эти блоки позволяют эффективно передавать информацию через слои, что способствует обучению более глубоких нейронных сетей. Модель обычно предварительно обучается на больших наборах данных (например, ImageNet), а затем дообучается на конкретной задаче обнаружения объектов. Она является ключевым компонентом YOLOv3, обрабатывая входные изображения и извлекая признаки, которые затем используются для обнаружения объектов на изображениях.

Заключение

Сам создатель YOLO Joseph Redmon опубликовал 3 статьи, которые тут и описываются. Дальнейшие улучшения уже делали его последователи в YOLOv4, YOLOv5, YOLOv6 и тд. Тем не менее основные идеи и дальнейший вектор развития был заложен именно в первых трех версиях. YOLO сейчас широко используется для задач обнаружения объектов в реальном времени. Например, обработка кадров с видеопотока мобильного устройства, умных камер и тд. Следующие статьи думаю будут больше про практику и применение / внедрение моделей :)

Теги:
Хабы:
Всего голосов 6: ↑6 и ↓0+6
Комментарии0

Публикации