Привет, Хабр! С вами Максим Алёшин, Data Scientist и участник профессионального сообщества NTA. В этом посте мы познакомимся с возможностями YOLOv7 для определения поз людей на видео, обсудим принцип работы алгоритма, разберёмся, чем принципиально отличается подход к детекции скелетов человека в модели YOLOv7 и других фреймворках, подробно пройдёмся по всем шагам запуска на инференс предобученной модели YOLOv7-pose для детекции людей с их скелетами.  В процессе копания в первоисточниках и не только, мне удалось почерпнуть несколько интересных фактов о YOLO, чем я поделюсь. Некоторые труднопереводимые термины будут оставаться как есть. Введение Немного о YOLO YOLOv7-pose Ключевые точки Принцип работы YOLOv7 на детекции Загрузка модели YOLOv7-pose Детекция скелета с YOLOv7 Детекция на видео Заключение  Обнаружение объектов (Object detection) — это очень обширная область компьютерного зрения и одно из самых важных применений компьютерного зрения «в природе». С развитием различных подходов в этой области возникла также задача детекции ключевых точек (keypoint detection), которые могут быть различными: части лица, конечности тела и другие. Определение позы человека (pose estimation, будем иногда называть это задачей детекции скелета) — это частный случай детекции ключевых точек, в котором ими являются части человеческого тела. В некоторых случаях использование моделей детекции ключевых точек даёт ощутимое преимущество перед обычными моделями детекции объектов, которые гарантируют на выходе лишь получение области, ограничивающей сам объект на изображении (boundary box или bbox). Так, детекция скелета может быть полезна в самых разных применениях: например, в системах мониторинга состояния человека, приложениях дополненной реальности, в компьютерной графике при создании анимации персонажа. Мы обратили внимание на модели детекции скелета благодаря их потенциальному применению в рамках проекта с использованием компьютерного зрения. В нашем случае подразумевается использование YOLOv7-pose совместно с моделью‑детектором для определения факта передачи мобильных устройств от одного человека другому. Логика, реализуемая после определения точек скелета, позволяет, во‑первых, запускать детектор мобильного устройства не на всём кадре, а только в окрестности координат ключевых точек запястий, во‑вторых по этим же координатам при наличии в кадре нескольких человек, предсказывать вероятность передачи устройства из рук в руки.  YOLO (You Only Look Once, «ты смотришь лишь раз») — метод идентификации и распознавания объектов на фотографиях в реальном времени, который впервые был предложен в 2015 году. Благодаря скорости, точности и простоте в обучении YOLO быстро стал самым используемым алгоритмом для идентификации объектов, в последствии к его совершенствованию приложили свои усилия разные разработчики. YOLOv1, YOLOv2 и YOLOv3 были реализованы одним коллективом авторов под началом Джозефа Редмона. Третья версия стала, последней над которой работал Редмон: он перестал заниматься задачами компьютерного зрения по этическим соображениям.  YOLOv4 была выпущен Алексеем Бочковским и др., и изначально являлась форком YOLOv3. Все перечисленные версии были основаны на фреймворке DarkNet. Начиная c YOLOv5 стал использоваться PyTorch. Это самая популярная в настоящее время версия, она был выпущена компанией Ultralytics (они же недавно представили усовершенствованную версию YOLOv8). Примечательно, что YOLOv6 была представлена китайской компанией Meituan примерно в одно время с YOLOv7, которая в свою очередь была выпущена уже упомянутым Бочковским и др. Некоторые из существовавших ответвлений мы даже не упомянули. Сложно ответить на вопрос, какой из последних репозиториев является актуальной официальной версией, ведь они развиваются параллельно. YOLOv7 — это не просто архитектура обнаружения объектов. Она предоставляет новые блоки предсказаний на выходе (prediction heads), которые помимо определения bbox объектов могут выдавать  ключевые точки и выполнять сегментацию объектов, что не было стандартным для предыдущих моделей YOLO. Сама модель была создана путем изменения архитектуры, а также оптимизации процесса обучения, получившей название «bag‑of‑freebies» (дословно мешок бесплатных подарков), что позволило повысить точность без уменьшения скорости детекции. Подробно архитектуру сети и возможности алгоритма авторы описывают в своей публикации «YOLOv7: Trainable bag‑of‑freebies sets new state‑of‑the‑art for real‑time object detectors» . YOLOv7 обучена на датасете COCO, в котором есть разметка ключевых точек скелета. К слову, среди всех официальных репозиториев YOLO предобученная модель для детекции скелета с сопутствующими скриптами постобработки есть только в YOLOv7. Современные модели определения поз в отличие от ранних предшественников позволяют детектировать скелеты сразу нескольких человек в кадре. В большинстве случаев это приводит к значительному увеличению времени детекции, в связи с чем в некоторых моделях предусмотрено ограничение количества детектируемых человек. Обычно алгоритмы определения поз работают в два этапа и делятся на два типа в зависимости от их порядка: Сверху вниз (Top‑down):  1 этап. Детекция человека  2 этап. Локализация ключевых точек скелета в найденной области.  Снизу вверх (Bottom‑up) 1 этап. Детекция ключевых точек с построением тепловой карты  2 этап. Объединение групп точек, принадлежащих одному человеку.  Top‑down подходы более ресурсозатратны, поскольку обычно используют тяжелые сетки для детекции людей, их вычислительная сложность увеличивается линейно с количеством людей в кадре. Bottom‑up подходы в целом имеют меньшую сложность и преимущество в виде постоянного времени детекции. Однако при этом наблюдается существенное снижение точности по сравнению с первыми. Для извлечения информации необходимы различные дополнительные этапы корректировки и уточнения для извлечения лучших ключевых точек из тепловой карты.  Но, YOLOv7-pose — одноэтапная модель определения поз одновременно нескольких человек на изображении. Алгоритм детекции поз YOLOv7 похож на Bottom‑up, но в нём не происходит построения тепловой карты. В модели YOLO детекция человека и ключевых точек позы происходит совместно, в один этап. Среди всех обнаруженных объектов с помощью метода non‑maximum suppression происходит фильтрация результатов предсказания. Как пишут разработчики YOLOv7 в приложении к своей публикации, размещённой в репозитории, для подготовки YOLOv7-pose они использовали настройки, описанные авторами модели YOLO‑Pose, которые получили её на основе YOLOv5. Также отметим, что качество предсказаний моделей детекции ключевых точек определяется с помощью метрики OKS (Object Keypoint Similarity — схожесть ключевых точек объекта). Согласно авторам YOLOv7-pose OKS их модели на тестовом наборе COCO равна 90.3%. Несмотря на наличие весов предобученной модели в репозитории, готового скрипта для детекции скелета там нет. Чтобы реализовать его самостоятельно разберёмся, как работает модель, в каком формате выдаёт результат. YOLOv7-pose способна детектировать 17 ключевых точек скелета, что соответствует разметке в наборе данных COCO. Изобразим наглядно эти точки.  Семейство алгоритмов YOLO — это «ancor‑based» модели (модели на основе якорей). Общая идея в том, чтобы сначала создать множество потенциальных bbox, а затем выбрать наиболее подходящие варианты, соответствующие нашим целевым объектам. Модель генерирует сетку поверх изображения и в каждом узле сетки (якоре, anchor) создаёт опорные боксы (anchor boxes) нескольких размеров (anchor sizes). В каждом узле повторяется один и тот же набор боксов. Таким образом, модель будет обучаться в процессе перемещения по сетке и изменения размеров этих bbox, это проще, чем генерирование bbox с нуля. Однако одна из проблем этого подхода заключается в том, что размеры bbox могут варьироваться — от крошечных до огромных. Поэтому обычно невозможно определить универсальный набор размеров якорей для любых изображений. По этой причине архитектуры моделей на основе якорей обычно используют Feature‑Pyramid‑Network (сети пирамид признаков), которые, если говорить обобщённо, извлекают признаки одновременно из изображений разных масштабов. По этой причине YOLOv7 работает одновременно с несколькими сетками на изображении. Подробнее можно почитать тут (хороший материал с объяснением принципа работы). Приступим к реализации алгоритма, по ходу разбираясь со структурой модели и выводов.  Конфигурация системы, на которой будем работать: персональный компьютер с Windows 11, Python 3.10, восьми ядерный CPU — AMD Ryzen 7 3700X, 32 ГБ RAM, GPU — GeForce RTX 2060 c 8 ГБ GDDR5, диск — NVMe SSD.  Весь описанный ниже код можно запускать даже на CPU, поскольку работа модели в режиме выдачи предсказаний не столь требовательна, как при её обучении. Однако, время обработки на CPU может быть на порядки выше, чем на GPU + CPU. И если при обработке отдельного изображения разница во времени выполнения не столь заметна, то при запуске детекции на видео наличие GPU уже существенно: на нашей системе обработка изображения разрешением 4900×6400 пикселей не превышала 4 секунд на CPU и занимала менее 1 секунды при подключении GPU. В завершении поделюсь некоторыми тестами. Необходимые библиотеки устанавливаются в окружение из requirements.txt. Используется torch 1.11.0+cu113, torchvision 0.12.0+cu113, opencv‑python 4.6. Первым делом склонируем себе репозиторий yolov7. Перейдём в директорию и установим необходимые библиотеки. Также предварительно скачаем веса, они находятся в разделе релизов репозитория, для детекции скелетов доступна только одна модификация весов — yolov7-w6-pose.pt. Папка weights должна быть предварительно создана. Импортируем необходимые библиотеки и функции. attempt_load понадобится нам для загрузки весов модели, letterbox для приведения размера картинки к нужным пропорциям, non_max_suppression_kpt для фильтрации предсказаний модели, xywh2xyxy для изменения координат bbox формата центр + размеры прямоугольника в координаты углов прямоугольника, output_to_keypoint для преобразования вывода модели в координаты ключевых точек, plot_skeleton_kpts и plot_one_box для прорисовки скелета и bbox.  Итак, загрузим модель и посмотрим информацию о ней, установим в качестве устройства для расчётов видеокарту, если она доступна. Командой eval() переведём модель в режим инференса. Также посмотрим информацию о количестве классов в модели и количестве ключевых точек. Результат вывода этого блока кода следующий: Device: cuda:0 Fusing layers...  Number of classes: 1 Number of keypoints: 17 Действительно, предсказывается вероятность единственного класса nc = 1, то есть человека, для него детектируется 17 ключевых точек nkpt = 17. На инференс модели подаётся батч картинок в формате torch.Tensor. Мы будем работать здесь с отдельными изображениями, поэтому в нашем случае батч будет содержать отдельную картинку. Но перед подачей картинки в модель, её размер должен удовлетворять условию кратности каждой стороны шагу stride сетки, по которой модель пробегает для детекции объектов. Это обусловлено алгоритмом работы, о котором мы писали выше. Загрузим тестовое изображение, посмотрим на него и его размеры, и проследим, как оно изменится после применения функции letterbox. Предварительно объявим функцию для более простого вывода картинок. Original image (576, 768, 3) Resized image (512, 640, 3) Мы не случайно выбрали размер страйда равным 64. После того, как мы подадим картинку на вход модели, получим тензор с результатами детекции на множестве прямоугольников в каждом узле сетки, сгенерированной моделью YOLO. Таких сеток несколько, с разным шагом stride. YOLOv7-pose генерирует 4 сетки с шагами [8, 16, 32, 64], соответственно выбираем максимальный из них. Размер картинки мы установили равным 640, чтобы выполнялось условие кратности шагу. И в каждом узле вычисление производится в прямоугольниках нескольких размеров, они также называются якорями anchors. Anchors: [[19, 27, 44, 40, 38, 94], [96, 68, 86, 152, 180, 137], [140, 301, 303, 264, 238, 542], [436, 615, 739, 380, 925, 792]] Здесь каждый вложенный массив — это пары размеров таких прямоугольников (w, h) для детекции на якоре, здесь по три пары. Индексы вложенных массивов anchors соответствуют значениям страйдов [8, 16, 32, 64]. Детекция производится на сетках с этими шагами.  Таким образом, для нашей картинки размером (512, 640) модель сгенерирует 4 сетки c размерами: (512/s, 640/s), где s — величина страйда. Итого 5120 + 1280 + 320 + 80 = 6800 узлов во всех сетках. Умножая это значение на количество якорей для каждой сетки получаем: 6800 ⋅ 3 = 20 400 прямоугольников‑кандидатов.  Действительно, преобразуем картинку к torch.Tensor, сделаем вложенной в массив и передадим модели, посмотрим на размер предиктов. Transformed to tensor image: torch.Size([1, 3, 512, 640]) Predictions shape: torch.Size([1, 20400, 57]) Получили 20400 возможных bbox. Для каждого прямоугольника-кандидата модель выдаёт 57 значений: c 0 по 3 — bbox найденного человека в формате xyxy; 4, 5 — вероятность и метка предсказанного класса (confidence, label); остальные тройки значений: [x, y, conf, x, y, conf,...] — координаты и значимость найденной точки скелета.  Среди этих предиктов лишь часть имеют значимую вероятность, кроме того, результирующий bbox целевого объекта совсем не обязан совпадать с боксом на якоре. Для их фильтрации используется функция non_max_suppression_kpt, не будем здесь разбираться в подробностях её работы, но отметим, что фильтрация происходит в два этапа, сначала по пороговому значению для вероятности детекции человека conf_thres, затем оставшиеся значения фильтруются по взвешенному среднему iou_thres для вероятностей детекции точек скелета. По умолчанию в реализованной разработчиками функции эти значения соответственно 0.25 и 0.45. Оставим пороговое значение для детекции человека без изменения, а пороговое значение детекции точек скелета увеличим и установим iou_thres = 0.65, чтобы уменьшить количество ложных срабатываний. Detected poses: 1 Prediction shape: torch.Size([1, 57]) Далее функцией output_to_keypoint предикты преобразуются к списку, содержащему оставшиеся после фильтрации распознанные позы. На каждый обнаруженный скелет приходится массив из 58 значений, содержащий номер батча (batch_id), метку класса (class_id), bbox (c_x, c_y, w, h), значимость (conf), координаты ключевых точек скелета и их вероятности (kpt_x, kpt_y, kpt_conf). То есть, имеет следующий вид:   Посмотрим на результат, предварительно объявив функцию для прорисовки скелета и bbox. Мы успешно детектировали скелет человека и построили его на изображении. Однако, модель делает предсказания на изменённом по размеру изображении, ещё и с отступами по краям, которые были добавлены для выполнения условия кратности размеров шагам сетки с якорями. Чтобы нанести детектированные ключевые точки на исходное изображение, необходимо написать функцию для перевода предсказанных точек в систему координат исходного изображения. Помимо этого, соберём все описанные шаги работы модели с изображением в функцию make_pose_prediction.  Проверим для разнообразия на другом изображении. Видим, что в некоторых ситуациях модель может ошибочно определять ключевые точки, но не будем требовать от неё слишком многого, тут и человеческим взглядом не сразу понятно, где какая нога у танцора на переднем плане! В то же время модель может детектировать части скелета и успешно их относит к одному человеку. На фотографиях с толпой людей, очевидно, ошибок может быть больше. Перенесём этот алгоритм на обработку видео. В качестве примера возьмём любительское видео с горнолыжного склона в разрешении 1280×720 пикселей, частотой 30 кадров в секунду и продолжительностью 18 секунд.  Используем OpenCV для чтения видео и обработаем моделью каждый кадр. Все кадры будем писать в новый файл. Одновременная запись нового файла с отображением кадров неминуемо замедлит процесс обработки, но мы не ставим целью запускать детекцию в реальном времени. Для ускорения процесса можно избежать создания нового файла и записи кадров в него в цикле. Напишем функцию для запуска детекции скелетов на видео.  VideoWriter принимает имя выходного файла, FourCC (код кодека, используемый для кодирования видео), частоту кадров и разрешение видео в виде кортежа. Мы использовали все параметры исходного видео, полученные через VideoCapture, который содержит данные о полученном видео. Теперь мы можем вызывать метод, передав в него путь к любому видео. Результат: Для сравнительной оценки скорости работы алгоритма мы провели подсчёт времени инференса на описанной выше системе (ПК AMD Ryzen 3700X + GeForce RTX 2060 Super) и на ноутбуке c четырёх ядерным CPU Intel i7–1165G7, 32 ГБ RAM, видеокартой GeForce MX450 c 2 ГБ GDDR5, NVMe SSD диском. Производились замеры только времени детекции скелетов, операции по отображению и записи кадров видео с обнаруженными скелетами были полностью исключены из скрипта. С кодом скрипта и результатами можно ознакомиться в Jupyter ноутбуке. Ниже приводим результаты измерений времени детекций в данном видео, содержащем 562 кадра разрешением 1280×720, для конфигураций с использованием GPU и без. Конфигурация Время обработки, секунды Ryzen 3700X + RTX 2060 Super 27 Ryzen 3700X 483 i7-1165G7 + MX450 99 i7-1165G7 908 Обработка видео длительностью 18 секунд самой мощной нашей системой заняла 27 секунд. Заметим, что благодаря реализации алгоритма детекции в YOLOv7 это время практически не зависит от количества людей в кадре. Но для обработки видео с таким разрешением в реальном времени необходимы более мощные видеокарты либо оптимизация алгоритма, в частности может помочь масштабирование изображения или запуск детекции не на каждом кадре.  Мы рассмотрели архитектуру YOLOv7, узнали о её новых возможностях, таких как, сегментация, детектирование скелета человека, которые выделяют её среди предшествующих YOLO. На наглядном примере убедились в простоте использования фреймворка. Надеюсь, что руководство и обзор будут полезны читателям, возможно кто‑то почерпнёт для себя новое об алгоритмах YOLO, о принципе их работы. Отмечу, что есть и другие модели для детекции скелета, которые показывают довольно хорошие результаты, несмотря на некоторые ограничения. Так, MoveNet от Google, в которой используются MobileNetV2 и CenterNet, может работать на мобильных устройствах «из коробки» и даёт неплохую точность. OKS модели по информации разработчиков составляет от 80% до 90% в зависимости от категории датасета (см. описание модели, средней метрики не приводятся). Её авторы в качестве преимущества отмечают отсутствие необходимости использования алгоритма «non‑maximum suppression«. MoveNet также была обучена на COCO датасете, однако разработчики расширили его датасетом, собранным из различных спортивных видео, чтобы улучшить качество детекции людей в движении.  Jupyter Notebook с разобранным примером и готовым скриптом для обработки видео можно найти в репозитории GitHub. Пишите, если хотите увидеть примеры практического применения YOLOv7-pose не только для детектирования скелета, а также сравнение точности и скорости работы c MoveNet или другими моделями.  Профессиональное сообщество