7.3. Hello BlazeFace

BlazeFace – это нейронная сеть для обнаружения лиц из коллекции Google MediaPipe. Один вызов вывода возвращает ограничивающий прямоугольник вокруг каждого обнаруженного лица вместе с шестью лицевыми точками – правый глаз, левый глаз, нос, рот, правое ухо, левое ухо. Каждая OpenMV Cam, поставляемая с поддержкой нейронных сетей, несёт модель blazeface_front_128.tflite во флеш-памяти, поэтому запуск сквозного детектора лиц занимает несколько строк Python.

7.3.1. Полный скрипт

import csi
import ml
from ml.postprocessing.mediapipe import BlazeFace

csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.VGA)
csi0.window((400, 400))

model = ml.Model("/rom/blazeface_front_128.tflite",
                 postprocess=BlazeFace(threshold=0.4))

while True:
    img = csi0.snapshot()
    for (x, y, w, h), score, keypoints in model.predict([img]):
        img.draw_rectangle((x, y, w, h), color=(0, 255, 0))
        ml.utils.draw_keypoints(img, keypoints, color=(255, 0, 0))

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

7.3.2. Что делает каждая строка

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

Следующие пять строк настраивают датчик. Камера сбрасывается в известное состояние, переводится в цвет RGB565, устанавливается в разрешение VGA, а затем кадрируется в квадрат 400 на 400. Окно имеет значение: BlazeFace обучалась на квадратных обрезках, и подача ей квадратного входа согласует ожидаемое соотношение сторон сети с тем, что она видит в захваченном кадре.

Строка загрузки модели открывает файл модели:

model = ml.Model("/rom/blazeface_front_128.tflite",
                 postprocess=BlazeFace(threshold=0.4))

ml.Model читает файл по указанному пути – /rom/ – это файловая система во флеш-памяти, рассматриваемая позже – и возвращает объект модели, на котором скрипт будет выполнять выводы. Ключевое слово postprocess= регистрирует постпроцессор BlazeFace; без него predict возвращал бы сырые выходные тензоры сети, и приложению пришлось бы декодировать их вручную. С ним predict возвращает декодированный результат напрямую. Аргумент threshold=0.4 постпроцессора задаёт минимальную уверенность, которую сеть должна сообщить, прежде чем обнаружение будет сохранено; меньшие значения улавливают более слабые лица ценой большего числа ложных срабатываний.

Остальные четыре строки – это главный цикл. Каждый его проход захватывает один кадр и спрашивает модель, что она видит:

img = csi0.snapshot()
for (x, y, w, h), score, keypoints in model.predict([img]):
    img.draw_rectangle((x, y, w, h), color=(0, 255, 0))
    ml.utils.draw_keypoints(img, keypoints, color=(255, 0, 0))

predict() принимает список входов (здесь – одно захваченное изображение) и возвращает список кортежей обнаружений. Каждый кортеж содержит ограничивающий прямоугольник (x, y, w, h), уверенность score между нулём и единицей, и ndarray формы (6, 2) с координатами точек – правый глаз, левый глаз, нос, рот, правое ухо и левое ухо в этом порядке. Вызов рисования использует draw_rectangle() – тот же примитив, которым заканчивался каждый классический детектор в главе об изображениях – чтобы обвести лицо. ml.utils.draw_keypoints() – это небольшой помощник из утилит ml, который отмечает каждую ключевую точку крестиком в её позиции (x, y).

7.3.3. Что скрипт не говорит

Скрипт содержит семь исполняемых строк работы вывода после импортов и настройки датчика, но внутри этих семи строк происходит огромное количество арифметики. Захваченный кадр 400 на 400 в формате RGB565 становится квантованным 8-битным тензором 128 на 128, прежде чем достигнуть сети; сеть выполняет сотни операций над десятками тысяч весов; получившиеся тензоры оценок уверенности и смещений рамок становятся ранжированным списком неперекрывающихся ограничивающих рамок с присоединёнными точками, прежде чем predict вернёт результат. Каждое из этих преобразований – это то, чем приложение может управлять, если нужно, и несколько из них приходится настраивать для любой нестандартной модели.

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

  • Модуль ml – что ml.Model предоставляет после загрузки модели и где файл модели на самом деле находится на камере.

  • Конвейер вывода – четыре этапа каждого вызова predict().

  • Движки вывода – пути CPU и NPU, выполняющие арифметику сети.

  • Декодирование вывода – постпроцессоры, которые преобразуют сырые выходные тензоры в обнаружения, по которым проходил этот скрипт.

К концу главы читатель сможет написать эквивалентный скрипт для модели, которая не поставлялась с камерой, декодировать тензор, для которого постпроцессора ещё не существует, и рассуждать о том, почему конкретная модель работает на 30 FPS на одной камере и на 3 FPS на другой.