Узнайте, как использовать модель MobileNet SSD, предварительно обученную на наборе данных COCO в Python, для работы на периферийных устройствах с полным кодом и не максимальным подавлением.

Tensorflow недавно выпустил свой API обнаружения объектов для Tensorflow 2, который имеет очень большой зоопарк моделей. Однако они предоставили только одну модель SSD MobileNet v1 с Tensorflow lite, которая описана здесь. В этом сообщении в блоге они предоставили коды для его запуска на устройствах Android и IOS, но не на периферийных устройствах. С ростом популярности периферийных устройств и выпуском OpenCV наборов пространственного ИИ я хотел восполнить недостающий пробел. Так что именно это мы и увидим здесь.

Требования

Для запуска этого не требуется установка API обнаружения объектов Tensorflow. Для его запуска достаточно простой установки Tensorflow вместе с OpenCV для обработки изображений.

pip install tensorflow
pip install opencv

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

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

Этикетки

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

Примечание. - Ярлыки с «???» необходимо игнорировать, и эти индексы пропускаются, кроме первого. Таким образом, каждая метка сдвигается на одну позицию назад. Например, метка «человек» находится в первой строке, но ей будет присвоена метка 0 и так далее.

Это можно найти в комментариях в его реализации на Java.

// Модель SSD Mobilenet V1 предполагает, что класс 0 является фоновым классом

// в файле меток и класса метки начинаются с 1 до number_of_classes + 1,

// пока outputClasses соответствует индексу класса от 0 до number_of_classes

Это можно сделать с помощью следующего кода:

def create_category_index(label_path='path_to/labelmap.txt'):
    f = open(label_path)
    category_index = {}
    for i, val in enumerate(f):
        if i != 0:
            val = val[:-1]
            if val != '???':
                category_index.update({(i-1): {'id': (i-1), 'name': val}})
            
    f.close()
    return category_index

Здесь, чтобы игнорировать первую строку, я использую оператор if и сохраняю i-1. Это создает словарь, как показано ниже.

В нем будет 80 строк с ключами до 89.

Переводчик TfLite

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

Инициализировать интерпретатор

import tensorflow as tf
interpreter = tf.lite.Interpreter(model_path="path/detect.tflite")
interpreter.allocate_tensors()

Просто загрузите правильный путь к вашей модели tflite и назначьте тензоры.

Детали ввода и вывода

Чтобы получить информацию о вводе и выводе, напишите:

input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()

Теперь давайте изучим их, чтобы увидеть, какие типы входов дать и какие выходы мы получим.

Детали ввода представляют собой список только из 1 элемента, который является словарем, как показано ниже.

Здесь мы видим фигуру ввода [1, 300, 300, 3]. Кроме этого, требуется, чтобы входное изображение имело тип данных np.uint8.

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

Чтобы получить выходные данные, нам нужно прочитать изображение, преобразовать его в RGB, если используется OpenCV, соответствующим образом изменить его размер и вызвать интерпретатор после установки тензора с входным кадром. Затем требуемые значения могут быть достигнуты с помощью get_tensor функции интерпретатора.

import cv2
img = cv2.imread('image.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_rgb = cv2.resize(img_rgb, (300, 300), cv2.INTER_AREA)
img_rgb = img_rgb.reshape([1, 300, 300, 3])
interpreter.set_tensor(input_details[0]['index'], img_rgb)
interpreter.invoke()
de_boxes = interpreter.get_tensor(output_details[0]['index'])[0]
det_classes = interpreter.get_tensor(output_details[1]['index'])[0]
det_scores = interpreter.get_tensor(output_details[2]['index'])[0]
num_det = interpreter.get_tensor(output_details[3]['index'])[0]

Использование кода обнаружения объектов для рисования

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

from object_detection.utils import visualization_utils as vis_util
vis_util.visualize_boxes_and_labels_on_image_array(
    img,
    output_dict['detection_boxes'],
    output_dict['detection_classes'],
    output_dict['detection_scores'],
    category_index,
    use_normalized_coordinates=True,
    min_score_thresh=0.6,
    line_thickness=3)

Полученные результаты

Результат будет примерно таким, как показано ниже.

Однако есть еще одна проблема, которую необходимо решить, как показано ниже.

Это можно сделать с помощью не максимального подавления.

Не максимальное подавление

Я не собираюсь объяснять это, поскольку это уже было подробно освещено в различных статьях в Интернете. Один из таких примеров - эта статья. Чтобы реализовать это, я собираюсь использовать combined_non_max_suppression Tensorflow Image для выполнения этой задачи, поскольку он позволяет нам работать с несколькими классами одновременно. Он принимает выходные данные и возвращает прогнозы, оставшиеся после порога.

def apply_nms(output_dict, iou_thresh=0.5, score_thresh=0.6):
q = 90 # no of classes
    num = int(output_dict['num_detections'])
    boxes = np.zeros([1, num, q, 4])
    scores = np.zeros([1, num, q])
    # val = [0]*q
    for i in range(num):
        # indices = np.where(classes == output_dict['detection_classes'][i])[0][0]
        boxes[0, i, output_dict['detection_classes'][i], :] = output_dict['detection_boxes'][i]
        scores[0, i, output_dict['detection_classes'][i]] = output_dict['detection_scores'][i]
    nmsd = tf.image.combined_non_max_suppression(
    boxes=boxes,
    scores=scores,    
    max_output_size_per_class=num,                                            
    max_total_size=num,
    iou_threshold=iou_thresh,
    score_threshold=score_thresh,
    pad_per_class=False,
    clip_boxes=False)
    
    valid = nmsd.valid_detections[0].numpy()
    output_dict = {
                   'detection_boxes' : nmsd.nmsed_boxes[0].numpy()[:valid],
                   'detection_classes' : nmsd.nmsed_classes[0].numpy().astype(np.int64)[:valid],
                   'detection_scores' : nmsd.nmsed_scores[0].numpy()[:valid],
                   }
    return output_dict

Полный код приведен ниже, или вы посетите мое репо на Github, которое также содержит visualization_utils.py и модели.

Прежде чем закончить, я хотел бы прояснить одну вещь: если вы попытаетесь запустить его в Windows с процессором Intel, вы получите ужасный fps. У меня на i5 было ~ 2, и для сравнения та же модель Tensorflow без tflite дала мне ~ 8 кадров в секунду. Это объясняется здесь. Однако на периферийных устройствах это не будет проблемой, а значительно меньший объем памяти принесет пользу их ограничениям в памяти.

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