Робот-танк на Raspberry Pi с Intel Neural Computer Stick 2

Вот и наступил новый этап в развии Raspberry-танка.

В предыдущей серии оказалось, что семантическая сегментация из коробки не по зубам Raspberry.

Мозговой штурм и комментарии позволили определить следующие направления развития:

  • обучить собственную E-net сеть под нужный размер картинок
  • передать запуск нейросети с самой Raspberry на специальную железку, из которых наиболее часто упоминался Intel Movidius (он же Neural Compute Stick aka NCS).


Приделать к роботу новую железку — это же самое интересное в роботехнике, поэтому кропотливая работа по обучению нейросети оказалась отложенной до лучших времен.

Несколько дней — и интеловская чудо-железка у меня в руках.

Она довольно большая, и в нижний USB разъем малинки ее не воткнешь. Учитывая, что правые USB порты были заслонены штативом камеры, а верхний левый занят GPS модулем, вариантов оставалось не то, чтобы много.

В итоге, GPS был посажен на кабель, переведен вниз, и кабель обернут вокруг штатива, а на его место зашел NCS.

На этом hardware часть была завершена.

rrana1u2e0uwupybfu4txj6hmmk.jpeg

Intel NCS


Интел недавно выпустил вторую версию NCS, причем API оказался полностью несовместим с предыдущей версией, о чем пользователи излили немало боли в интернете.

Как следствие, вся база знаний о предыдущей версии в настоящее время является просто информационным мусором.

В новой редакции предлагается фреймворк OpenVino, который включает в себя OpenCV и много всякого другого, в том числе и различные инструменты для работы с нейросетями.

Вот несколько вводных статей по NCS2 и OpenVino:


Начало работы с NCS оказалось вполне себе гладким.

Интел изначально сделал поддержку Raspbian, так что танцевать с бубном не пришлось.
Вводный документ оказался также весьма четким и установка фреймворка OpenVino никаких проблем не вызвала.

Приятным бонусом оказалось, что в OpenVino входит OpenCV 4.1, это экономит время, так как предыдущие версии OpenCV мне пришлось собирать на Raspberry самостоятельно.

Вот как выглядит NCS2 сам по себе:

wwdcbyyaogekp33qbtehz6zp3mo.jpeg

Дальше оказалось интереснее.

NCS поддерживает только свой собственный формат нейросетей, а Интел предоставляет инструмент Model Optimizer в составе OpenVino для конвертации графов самых популярных фреймворков: Tensorflow, Caffe, Torch. Подробнее про это будет дальше.

Кроме того, Интел также предоставляет model zoo — набор готовых моделей на многие случаи жизни.

Среди них были две модели для дорожной сегментации:


Нейросети на NCS


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

Инициализировать девайс


Название MYRIAD, идея плагина и динамическая загрузка его либы, путь к которой надо указать в программе — явно тянутся из темного прошлого.

from openvino.inference_engine import IENetwork, IEPlugin

ncs_plugin = IEPlugin(device="MYRIAD", plugin_dirs="/opt/intel/openvino/inference_engine/lib/armv7l")


Загрузить модель


Дальше надо загрузить модель на девайс.

Это тяжелая операция. Та небольшая модель, которую я использовал для сегментации, грузится порядка 15 секунд.

Хорошие новости в том, что загружать модель нужно только раз и можно загрузить несколько моделей.

        model = IENetwork(model=xml_path, weights=bin_path)
        net = ncs_plugin.load(network=model)


Запустить расчет


Теперь модель можно использовать.

    input_blob = next(iter(model.inputs))
    out_blob = next(iter(model.outputs))
    n, c, h, w = model.inputs[input_blob].shape
    images = np.ndarray(shape=(n, c, h, w))
    images[0] = image
    res = net.infer(inputs={input_blob: images})
    res = res[out_blob]


Однопроцессность


Внезапно оказалось, что нельзя использовать NCS из двух разных процессов одновременно.
Тот кто опоздал, не может загрузить модель:

E: [ncAPI] [    684447] resetAll:348     Failed to connect to stalled device, rc: X_LINK_ERROR
E: [ncAPI] [    691700] ncDeviceOpen:672        Failed to find suitable device, rc: X_LINK_DEVICE_NOT_FOUND
Traceback (most recent call last):
    net = ncs_plugin.load(network=model)
  File "ie_api.pyx", line 395, in openvino.inference_engine.ie_api.IEPlugin.load
  File "ie_api.pyx", line 406, in openvino.inference_engine.ie_api.IEPlugin.load
RuntimeError: Can not init USB device: NC_ERROR


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

Сегментация OpenVino


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

Результаты тестов несколько противоречивы. Иногда распознается криво, но в большинстве нормально.

Enet работал лучше, но запустить Enet под NCS надо еще постараться, так что попробуем с тем что есть.

gkllsmwznf3smcft_gyxk_gdaos.jpeg

Что интересно, узнать больше о модели из OpenVino и переобучить ее не так то просто.

Пользователи интересуются, но человек из Интела строго сказал, что код и данные модели закрыты, а желающие могут взять подобную нейросеть на PyTorch, обучить, конвертировать и использовать.

По скорости преимущество очень значительно:
Если у Enet сегментация занимала 6 секунд, то у этой модели на обработку одной картинки уходило 0.8 секунд (при этом 14 секунд занимала загрузка модели на девайс, но это делается единовременно).

Классификация направлений


Для принятие решений о направлении движения танк использует простую нейросеть, как описано в соотвествующей статье.

Нейросеть обучена на Keras и работает на Raspberry через Tensorflow, который имеет встроенный адаптер для этого формата.

Модель очень простая и даже на Raspberry показывает приемлемые по скорости результаты.
(0.35 секунд на картинку).

Тем не менее, имея интеловскую железку, можно рассчитывать добиться лучших результатов.
Среди форматов, которые принимает интеловский Model Optimizer для конвертации, есть Tensorflow, но нет Keras.

Преобразование Keras в TF вещь довольно популярная, материала на эту тему хватает, я руководствовался этой статьей.

У этого же автора есть более обширная статья, как раз на тему, как модель Keras запустить на OpenVino.

Еще можно воспользоваться руководством от Интела.

В общем, скомпилировав источники, я получил такой скрипт для преобразования модели Keras в TF:

import tensorflow as tf
from tensorflow.python.framework.graph_util import convert_variables_to_constants
from keras import backend as K
from keras.models import load_model
from keras.models import model_from_json

def load_keras_model(json_file, model_file):
    jf = open(json_file, 'r')
    loaded_model_json = jf.read()
    jf.close()
    loaded_model = model_from_json(loaded_model_json)
    loaded_model.load_weights(model_file)
    return loaded_model

def freeze_session(session, keep_var_names=None, output_names=None, clear_devices=True):
    graph = session.graph
    with graph.as_default():
        freeze_var_names = list(set(v.op.name for v in tf.global_variables()).difference(keep_var_names or []))
        output_names = output_names or []
        output_names += [v.op.name for v in tf.global_variables()]
        # Graph -> GraphDef ProtoBuf
        input_graph_def = graph.as_graph_def()
        if clear_devices:
            for node in input_graph_def.node:
                node.device = ""
        frozen_graph = convert_variables_to_constants(session, input_graph_def,
                                                      output_names, freeze_var_names)
        return frozen_graph


model = load_keras_model('./model.json', './model.h5')
frozen_graph = freeze_session(K.get_session(),
                              output_names=[out.op.name for out in model.outputs])
tf.train.write_graph(frozen_graph, ".", "ktf_model.pb", as_text=False)


Этот же код лежит на гитхабе.

Получившуюся TF-модель дальше перегоняем в OpenVino формат:

python mo_tf.py --input_model "model/ktf_model.pb" --log_level=DEBUG -b1 --data_type FP16


Тесты показали, что классификация картинки проходит за 0.007 секунд.
Такой результат очень радует.

Все обученные модели (Keras, TF, OpenVino) также выложены на гитхаб.

Распознавание объектов


Задача сегментации — не единственная, которую приходится решать роботу в своей нелегкой жизни.

В начале был детектор кота, который потом вырос в универсальный детектор на основе MobileSSD и OpenCV-DNN.

Теперь пришла пора покрутить эту же задачу на NCS.

В интеловском model_zoo достаточно детекторов более узкой специфики на основе MobileSSD, но точный аналог отсутствует.

Тем не менее, эта сеть указана как совместимая в списке поддерживаемых моделей TF.

Что интересно, на момент написания статьи здесь указана версия MobileSSD 2018_01_28.

Однако, читать эту модель OpenCV отказывается:

cv2.error: OpenCV(4.1.0-openvino) /home/jenkins/workspace/OpenCV/OpenVINO/build/opencv/modules/dnn/src/tensorflow/tf_importer.cpp:530: 
error: (-2:Unspecified error) Const input blob for weights not found in function 'getConstBlob'


(Зато мы узнали, что они используют Jenkins).

При этом, конвертация в OpenVino проходит успешно.

Если же попробовать сконвертировать версию Mobile SSD, совместимую с OpenCV-DNN (11_06_2017), то получаем такое:

[E0919 main.py:317] Unexpected exception happened during extracting attributes for node FeatureExtractor/MobilenetV1/Conv2d_13_pointwise_1_Conv2d_2_1x1_256/Relu6.
Original exception message: operands could not be broadcast together with remapped shapes [original->remapped]: (0,) and
 requested shape (1,0,10,256)


Как-то так, технически OpenVino и OpenCV-DNN в одной поставке, но несовместимы по версиям используемых нейросетей.

То есть, если хочется использовать одновременно оба подхода, надо тащить две версии MobileSSD.

По скорости сравнение конечно же в пользу NCS: 0.1 секунды против 1.7.

По качеству…(Хотя это вопрос не к NCS, а к эволюции Mobile SSD).

rz58zzzllyd5i3gubv99nz5-b6m.jpeg

Классификация картинок


Танк умеет классифицировать картинки через Tensorflow, используя Inception на Imagenet.
Причем я использовал Inception 2015–12–05, когда она была еще одна.

Оказалось, я сильно отстал от жизни, ибо ребята из Гугла не даром едят свой хлеб и уже наплодили аж 4 версии!

Но ребята из Интела от них не отстают и все 4 версии поддержали в OpenVino.

Вот статья с описанием различных версий Inception.
Но мы не будем мелочиться, качаем сразу последнюю, четвертую.

Классифицируем картинку с котом и лаптопом на столе.

Запоминаем результаты работы текущей версии:

  • laptop, laptop computer 62%
  • notebook, notebook computer 11%
  • 13 секунд
  • где кот?


Теперь читаем инструкцию по конвертацию Inception в OpenVino.

Конвертация проходит успешно, запускаем классификатор на NCS:

  • laptop, laptop computer 85%
  • notebook, notebook computer 8%
  • 0.2 секунды
  • кота опять нет


Заключение


Таким образом, все сценарии, требовавшие Tensorflow, были воспроизведены с помощью NCS, и это означает что от использования Tensorflow можно отказаться.

Все таки этот фрейморк тяжеловат для Raspberry.

Скорость, с которой NCS переваривает нейросети позволяет его расширить горизонты его применения.

Есть задачи, которые робот уже делает, например, семантическая сегментация и классификация, но есть и другие вроде объектной сегментации или передачи видео с детектированными объектами в реальном времени. (о чем нельзя было и помыслить на голой Raspberry).

Несколько смущают проблемы с многопроцессностью, но даже если их не удастся решить, то всегда остается выход в виде оборачивания NCS отдельным сервисом.

Ссылки


© Habrahabr.ru