OpenVINO C++ развертывание моделей детектирования объектов

Что такое OpenVINO

OpenVINO - это набор инструментов с открытым исходным кодом для оптимизации и развертывания моделей искусственного интеллекта. 

Поддерживаемое оборудование:

  • Intel CPU;
  • ARM CPU;
  • Intel GPU.
 Установка в Windows

Не требуется. Достаточно скачать пакет нужной версии, например 2023.3:

https://storage.openvinotoolkit.org/repositories/openvino/packages/2023.3/

И использовать нужные dll для запуска и lib для подключения.

Установка в Linux

Про установка в Linux можете почитать тут https://docs.openvino.ai/ - способов много. Но для C++ самый простой способ это установить из архива. Скачиваем например версию:

https://storage.openvinotoolkit.org/repositories/openvino/packages/2023.3/li nux/l_openvino_toolkit_ubuntu20_2023.3.0.13775.ceeafaf64f3_x86_64.tgz -- output openvino_2023.3.0.tgz 

Распакуйте в /opt/intel/

Установите дополнительные требования:

cd /opt/intel/openvino_2023.3.0

sudo -E ./install_dependencies/install_openvino_dependencies.sh

Установка ссылки для простоты:

cd /opt/intel

sudo ln -s openvino_2023.3.0 openvino_2023

Конфигурация окружения:

source /opt/intel/openvino_2023/setupvars.sh

 Развертывание модели

Заголовок:

#include <openvino/openvino.hpp>

Модель можно подключить просто указывая путь:

ov::Core core;

std::shared_ptr<ov::Model> model = core.read_model(pathToModel);

При этом это может быть и путь до ONNX файла, и путь для модели формата OpenVINO и других фреймворков. Однако эта функция может принимать не только путь до файла, но и массив байт, что позволяет провернуть такую вещь:

std::pair<std::string, std::vector<uint8_t>> p = DecryptFileToString(pathToModel);

auto model = core.read_model(p.first, ov::Tensor(ov::element::u8, { p.second.size() }, p.second.data()));

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

std::pair<std::string, std::vector<uint8_t>> DecryptFileToString(std::string path)

{

       std::ifstream modelFile(path, std::ios::in | std::ios::binary);

       std::vector<uint8_t> data((std::istreambuf_iterator<char>(modelFile)), std::istreambuf_iterator<char>());     

       Ваш алгоритм расшифровки

       std::string strModel(data.begin(), data.end()); 

       return make_pair(strModel,data);

}

После загрузки модели надо ее настроить. Пример настройки:

ov::preprocess::PrePostProcessor ppp = ov::preprocess::PrePostProcessor(model);

ppp.input().tensor().set_element_type(ov::element::u8).set_layout("NHWC").set_color_format(ov::preprocess::ColorFormat::BGR);

ppp.input().preprocess().convert_element_type(ov::element::f32).convert_color(ov::preprocess::ColorFormat::RGB);

ppp.input().model().set_layout("NCHW");

ppp.output().tensor().set_element_type(ov::element::f32);

model = ppp.build();

Здесь NHWC - формат входного слоя, batch N, channels C, depth D, height H, width W.

ColorFormat::BGR - графический формат OpenCV (чтобы не преобразовывать изображение). Если у вас модель одноканальная, то параметр ColorFormat::GRAY не надо использовать, и вообще не надо вызывать функцию set_color_format(), т.к. она нужна для установки цветового формата, а в сером изображении его нет.

Далее компиляция модели и создание запроса inference:

ov::CompiledModel compiledModel = core.compile_model(model, "CPU");

ov::InferRequest inferRequest = compiledModel.create_infer_request();

Для синхронного вызова используется infer():

float* inputBuffer = (float*)image.data;

inputTensor = ov::Tensor(compiledModel.input().get_element_type(), compiledModel.input().get_shape(), inputBuffer);

inferRequest.set_input_tensor(inputTensor);

inferRequest.infer();

Здесь image - это 8битное 3-х канальное изображение OpenCV (cv::Mat). Для асинхронного infer() заменяется парой методов start_async() и wait(), в остальном синтаксис прежний.

Тут однако, если вы используете динамический размер (для разного batch_size в модели), то размер нужно будет задать, т.к. иначе будет ошибка:

ov::Shape inputShape =  {{ ваш размер}};

inputTensor = ov::Tensor(compiledModel.input().get_element_type(), inputShape, inputBuffer);

Получение результата распознавания следующим образом:

const ov::Tensor& outputTensor = inferRequest.get_output_tensor();

ov::Shape outputShape = outputTensor.get_shape();

float* detections = outputTensor.data<float>();

Где detections - это выходная матрица, в которой нужно брать результаты распознавания.

 

Обработка ошибок 

В отличии от, к примеру TensorRT, все функции не возвращают напрямую ошибок, поэтому нужно использовать исключения try {} catch(const std::exception& ex){} для обработки ошибок.

В остальном  - OpenVINO легко развертывается на процессорах x86_64 и ARM на любых операционных системах без каких-либо проблем, при этом может использовать напрямую распознавание из модели ONNX.