OpenVINO C++ развертывание моделей детектирования объектов
Что такое OpenVINO
OpenVINO - это набор инструментов с открытым исходным кодом для оптимизации и развертывания моделей искусственного интеллекта.
Поддерживаемое оборудование:
- Intel CPU;
- ARM CPU;
- Intel GPU.
Ссылка на git: https://github.com/openvinotoolkit/openvino
Установка в Windows
Не требуется. Достаточно скачать пакет нужной версии, например 2023.3:
https://storage.openvinotoolkit.org/repositories/openvino/packages/2023.3/
И использовать нужные dll для запуска и lib для подключения.
Установка в Linux
Про установка в Linux можете почитать тут https://docs.openvino.ai/ - способов много. Но для C++ самый простой способ это установить из архива. Скачиваем например версию:
Распакуйте в /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.