Приветствую всех читателей Хабра! Сегодня я хочу поделиться с вами своим опытом запуска YOLACT на edge-устройстве RockChip. Несмотря на то, что процесс запуска занял больше времени, чем я ожидал, я решил поделиться с вами своими наработками, чтобы помочь другим разработчикам, которые могут столкнуться с той же задачей. В конце концов я нашёл способ запуска yolact, который позволил достичь высокой производительности и качества модели. Надеюсь, что мой опыт будет полезен для вас и поможет вам избежать ошибок, которые я совершил. Приятного чтения!
Так уж вышло, что задачам классификации изображений, детекции и сегментации объектов на изображениях посвящено множество статей, обзоров и мануалов. Вообще, тема алгоритмов компьютерного зрения является интересной. При этом, когда речь заходит об instance-segmentation, авторы ограничиваются обзором Mask-RCNN и Fast R-CNN, сетей которые вышли более семи лет назад. Запуск в реал‑тайме этих сетей в принципе невозможен даже на мощном железе, что уж говорить об «одноплатниках». Пытаясь восполнить этот пробел и имея под рукой мощный и дешевый «firefly» я натолкнулся на yolact.
Немного о самой yolact
Статья «YOLACT. Real-time Instance Segmentation» вышла в 2019 году. И на тот момент сетка была в четыре раза производительней других алгоритмов, решающих подобную задачу (33 fps против 8). При этом точность (mAp) у yolact была примерно на 17 процентов ниже чем у знаменитой Mask-RCNN. Авторы заявляют, что yolact это первая instance-segmentation сеть работающая в "реал-тайме", что весьма и весьма впечатляет.
Нужно сказать, что instance-segmentation является наиболее интересной задачей сегментации, так как ближе всего повторяет процесс, который происходит у нас в мозгу, когда мы смотрим на окружающие объекты. Мы видим не только границы объектов, но и понимаем к какому классу эти объекты принадлежат. Yolact делает это за счёт "пирамиды свёрточных слоёв", выделяющих карты признаков разного масштаба, в основании. И Protonet, использующейся для генерации k-масок (k = 32). Как пишут авторы, архитектура сети была основана на RetinaNet + FPN.
Немного о RockChip
Firefly RK3588 имеет на борту нейропроцессор (NPU) с тремя ядрами, мощностью до 6 TOPS. И для того чтобы использовать весь его потенциал (запустить инференс на NPU) необходимо перевести модель в определенный формат (граф) - rknn, а веса модели сжать (квантизировать) до точности 16 или 8 бит. Сделать это можно с помощью небольшого скрипта, пример которого находится в официальном репозитории firefly. Конвертация в rknn-формат может идти напрямую из model.pt, а также из model.onnx.
Обзор yolact-проектов
dbolya. В интернете существует несколько репозиториев с реализацией yolact. В первую очередь - это репозиторий от авторов оригинальной статьи: dbolya. Проект не отличается дружелюбностью к пользователю (несмотря на объемный "Readme"), но запускается из коробки. Большим минусом данного проекта является невозможность сконвертировать модель в onnx формат. Автор признается, что не собирается переписывать модель, чтобы добавить возможность конвертации в onnx, так как это снижает её производительность. Конвертация напрямую в rknn также невозможна. Поэтому, если вы собираетесь запускать сегментацию на компьютере, сервере и т. п. можно использовать этот проект, а дальше можно не читать.
Ma-Dan. К счастью, существует "форк" yolact от Ma-Dan, в котором из класса модели выкинуты "ненужные" декораторы и всякие прочие jit-компиляторы, что позволяет сконвертировать её в onnx формат. Но у Ma-Dan модели есть странный выход (Рис. 1), из-за чего конвертация в rknn-формат, необходимый для запуска на firefly, не проходит. Наверное, этот "output" можно удалить и всё заработает, но я этого не проверял.
PINTO. Ещё одну версию yolact можно найти среди сотен других моделей у автора PINTO0309 (Честно, я не знаю чем занимается этот человек в свободное время, но подозреваю, что он живет на Венере, так как в сутках там ~5832 часа, иначе как объяснить его количество «contributions»). Всё что нужно - скачать репозиторий, скачать подготовленные автором модели и пост-процессы (в формате onnx) с помощью download.sh — скрипта. Поздравляю, вы на 90% приблизились к запуску yolact на edge-девайсе.
Тут есть несколько моментов. Во‑первых, нужно сконвертировать модель (например «yolact_base_54_800 000_550×550») в rknn формат. Цифры в конце названия модели означают размер входного изображения. Во‑вторых, нужно сконвертировать «postprocess550×550» в rknn формат. А это сделать невозможно не так просто — привет слои «reshape 1×30 963». Проще запустить постпроцесс с помощью onnxruntime. В‑третьих, то что у автора называется постпроцесс (postprocess550×550.onnx), на самом деле это лишь часть постпроцесса. Так, если сравнить его с оригинальным репозиторием (dbolya/yolact), то окажется что это часть класса Detect(), который выдаёт четыре аутпута: классы, боксы, скоры и маски (classes, boxes, scores, masks). Поэтому, для получения результата как на картинке (Рис.2), нужно добавить следующие строчки:
функция постпроцесса
def prep_display(results):
def crop(bbox, shape):
x1 = max(int(bbox[0] * shape[1]), 0)
y1 = max(int(bbox[1] * shape[0]), 0)
x2 = max(int(bbox[2] * shape[1]), 0)
y2 = max(int(bbox[3] * shape[0]), 0)
return (slice(y1, y2), slice(x1, x2))
bboxes, scores, class_ids, masks = [], [], [], []
for result, mask in zip(results[0][0], results[1]):
bbox = result[:4].tolist()
score = result[4]
class_id = int(result[5])
if threshold <= score:
mask = np.where(mask > 0.5, class_id + 1, 0).astype(np.uint8)
region = crop(bbox, mask.shape)
cropped = np.zeros(mask.shape, dtype=np.uint8)
cropped[region] = mask[region]
bboxes.append(bbox)
class_ids.append(class_id)
scores.append(score)
masks.append(cropped)
return bboxes, scores, class_ids, masks
где "results" - это четыре тензора, полученных на выходе postprocess550x550.
и, наконец, отрисовываем результат:
функция рисующая маски и боксы на изображении
def onnx_draw(frame, bboxes, scores, class_ids, masks):
colors = get_colors(len(COCO_CLASSES))
frame_height, frame_width = frame.shape[0], frame.shape[1]
# Draw masks
if len(masks) > 0:
mask_image = np.zeros(MASK_SHAPE, dtype=np.uint8)
for mask in masks:
color_mask = np.array(colors, dtype=np.uint8)[mask]
filled = np.nonzero(mask)
mask_image[filled] = color_mask[filled]
mask_image = cv2.resize(mask_image, (frame_width, frame_height), cv2.INTER_NEAREST)
cv2.addWeighted(frame, 0.5, mask_image, 0.5, 0.0, frame)
# Draw boxes
for bbox, score, class_id in zip(bboxes, scores, class_ids):
x1, y1 = int(bbox[0] * frame_width), int(bbox[1] * frame_height)
x2, y2 = int(bbox[2] * frame_width), int(bbox[3] * frame_height)
color = colors[class_id + 1]
frame = draw_box(frame, (x1, y1, x2, y2), color, class_id, score)
return frame
Но есть и четвертый, самый неприятный момент — yolact_base_54_800000_550x550.onnx от PINTO нельзя обучить. А если у вас получится это сделать, то в случае если количество классов будет отличаться от исходных 80, то postprocess550x550.onnx перестанет работать. Так как размер входного тензора у postprocess550x550 фиксированный. PINTO пишет, что размер входных тензоров у onnx-моделей можно менять с помощью определённых программ, но заморачиваться с этим каждый раз когда вам нужно обучить модель на новые классы, вместо того, чтобы иметь нормально работающий постпроцесс, как-то не хочется.
Подводя итоги, если вы не собираетесь переучивать yolact на свои классы, а хотите быстро запустить модель для демонстрации, то можно обойтись репозиторием PINTO.
postprocess550x550
На самом деле вся эта история с postprocess550x550.onnx была связана с предположением, что он будет работать быстрее, чем постпроцесс написанный вручную и использующий циклы на python. Это предположение оказалось неверным.
feiyuhuahuo. Последним и окончательным вариантом оказался Yolact_minimal. Сам проект как и модель является упрощенным вариантом оригинального проекта dbolya/yolact. Обучение модели, оценка точности и конвертация её в onnx-формат делаются в несколько строчек и изменением конфигов. Модель прекрасно конвертируется в fp16, а если использовать resnet101 в качестве backbone, то и квантуется до int8, правда точность после квантизации оставляет желать лучшего. Один из вариантов увеличения точности квантованной модели — это использование Quantization Aware Training.
Единственный минус — для запуска инференса на устройстве, необходимо перенести туда весь пост процесс, причем использовать можно только те функции, которые работают с numpy-массивами. После этого можно получить кое-какие результаты.
Скачиваем случайную картинку с интернета, обрезаем до нужного размера и скармливаем её нейронке. Смотрим на результат:
Видим, что результат пока не удовлетворительный. Куча ббоксов, маски перекрывают друг‑друга, странный «score». Как бы там ни было, уже что‑то появилось и с этим можно работать. «Score» больше единицы наводит на некоторые мысли. Сравнивая rknn модель и модель Yolact_minimal, например с помощью netron.app (как оказалось он прекрасно «кушает» rknn‑модели), можно увидеть, что выход у исходной сетки прежде чем быть отфильтрован по «nms_score_thre» проходит через функцию softmax (class_pred = F.softmax(class_pred, -1)). Понятно, значит при конвертации потерялся softmax, давайте добавлять:
def np_softmax(x):
np_max = np.max(x, axis=1)
sft_max = []
for idx, pred in enumerate(x):
e_x = np.exp(pred - np_max[idx])
sft_max.append(e_x / e_x.sum())
sft_max = np.array(sft_max)
return sft_max
# Обратите внимание, что softmax рассчитывается в цикле,
# для каждого вектора класса отдельно (что логично).
После чего получаем симпатичный результат. И это на устройстве размером с пластиковую карту!
Всё работает как надо, и теперь можно выставлять уровень уверенности, порог который плохо распознанные объекты не должны пересекать.
Осталось обучить модель под ваши задачи. Как это сделать написано в репозитории Yolact_minimal. Если кратко, то главное получить JSON файл с разметкой в формате COCO (custom_ann.json). И вставить название нужных классов в конфиг.
Тренировка на 50-ти картинках занимает около 1,5 часов на одной карточке V100. Сеть работает только с изображениями размеры которых кратны 32. Мной были выбраны изображения 544 на 544px.
В заключение, хочу подчеркнуть, что использование нейросетей в решении задач компьютерного зрения становится все более популярным и востребованным. Мой опыт показал, что модель YOLACT на базе RK3588 способна обрабатывать видеопоток с высокой точностью. Кроме того, благодаря открытому исходному коду, любой желающий может самостоятельно запустить модель и настроить ее под свои нужды.
Если у вас есть какие-либо вопросы или предложения, не стесняйтесь писать их в комментариях. Я всегда готовы поделиться своим опытом и помочь в решении сложных задач.
Также хочу порекомендовать свой репозиторий для запуска yolact: GitHub. В нём находятся две rknn‑сетки: «yolact_550.rknn» и «yolact_544.rknn», и описаны два постпроцесса: ONNX и RKNN, для первой и второй модели, соответственно. Спасибо за внимание!
le2
пару лет назад в течение нескольких месяцев дома поковырял плату от firefly - 3399pro. Поделюсь своей картиной мира, возможно будет кому-то полезной. Буду рад, если кто-то мои тезисы опровергнет.
- NPU - не про скорость, а про энергопотребление. (например разблокировку телефона по лицу без NPU делать глупо) Рилтаймовые сетки работают не медленнее на GPU и, внезапно, работают ещё быстрее на CPU при утилизации нескольких ядер с библиотекой XNNPACK.
- практически все быстрее работает на GPU, если вам нужны ещё трансформации картинок в реалтайме. Потому что инференс это обычно только 50% времени цикла. (в этом и подхвох. Во всех статьях авторы сравнивают только время инференса, хотя рабочий цикл программы занимает больше времени. )
-NPU всегда головняк. Процесс чипа разработки занимает несколько лет. Берутся актуальные на тот момент слои нейросетей и реализуются в железе. В момент когда NPU добирается до пользователя, то пользователю достается доступным древний TеnsorFlow1.3 с неактуальными возможностями. Неподдерживающиеся слои можно исполнять на CPU, но тогда вылезают транспортные расходы - нужно гонять данные до NPU и обратно раз 5-8 на один инференс. Смысл использования NPU в этом случае теряется.
- NPU - всегда разрабатывается мелкими стартапами, к коим Рокчип и любой другой вендор не имеют отношения. Практически это означает что вам будет выдан бинарный запускаемый файл, который якобы может сконверчивать ONNX, TF lite и что-то еще в понятный для NPU формат RKNN. На практике вас будут ждать два варианта - вашу нейронку программу сконвертить не может либо может, но инференс будет не верный!
- NPU через PCIe или однокристальный ничего не добавляет. Практически производительнсть можно оценить через внешний свисток с NPU через USB3.0. Времена будут близки.
- на форуме файрфлай вам в лучшем случае ответит такой же бедолага как и вы. Файрфлай никакую поддержку железок не оказывает.
- на китайском форуме сапорта NPU меня тупо кикнули, убив аккаунт, при попытке задать вопрос на английском (в английской версии).
Тем не менее все проблемы я порешал, всё что хотел сделал. Для России, сейчас, Рокчип это, вероятно, лучший вариант для встраиваемых решений. Но для себя решил что никакого тайного знания железки на ARM не несут. 99% работ лучше решать на ПК с x86, а переходить в эбмеддед нужно при наличии бизнес-плана.
124bit
Интересная статья и интересный комментарий
Paskin
Согласен с большинством утверждений. Единственное - NPU это (в теории) еще и про короткий путь от камеры до инференса, при условии синхронизации по кадрам. Это подход, который продвигает Nvidia в своих Jetson-ах, в которых ISP "ближе" к GPU/NPU чем центральный процессор - а также Kendryte и некоторые другие компании.
К сожалению, у них не получается снизить цену так, чтобы Jetson-ы можно было встраивать в камеры (все-таки 400+ долларов без обьектива за Advantech это очень много для массового рынка) - да и CSI не самый удобный интерфейс в смысле монтажа. А с USB или сетью - производительность сразу падает. Но для серверных применений Nvidia активно продвигает принцип "из сети - прямо в DLA", собственно за этим они Mellanox и покупали.
Интел же пытаются идти тем путем, которым идут компании в областях обороны и других "небюджетных" решений - в которых и модели, и ISP, и пре/пост-процессинг тупо конвертируются в FPGA. Но получается пока неэкономично по питанию/теплу и сложно для использования.
120gramm
Подскажите если отбросить вопрос энергопотребления что лучше выбрать по характеристикам цена/качество для inference моделей не связанных с компьютерным зрением (просто вычисления с плавающей точкой в режиме реального времени). cpu/gpu/stick? Модель не сложная (несколько признаков) но количество одновременно анализируемых параллельно объектов более 50.
le2
умозрительно такие вещи делать невозможно. Нужно просто взять и запустить вас софт на всех железках.
Если ваша нейросеть с плавающей точкой и вы не можете ее отквантовать, то быстрее всего будет на GPU.
GPU это +5..+10 долларов к цене чипа.
NPU это такаже +5..+10 долларов к цене чипа.
Производительность CPU можно примерно оценить по рассеиваемой мощности (TDP). Я проводил тесты на десятке железок с ARM на задачах машинного зрения. Какой-то усредненный алгоритм выдавал 3-4 fps на процессорах начального уровня,Распберри Zero и 25fps Распберри 4.