Большинство руководств по созданию и использованию моделей нейронных сетей написаны на Python. Однако для некоторых проектов рано или поздно может возникнуть необходимость использовать для этих задач более быстрый и надежный язык, например, C++. В этой статье объясняется, как использовать модель нейронной сети в C++ на примере этой модели. YOLOv8 для распознавания лиц и библиотеки TorchPy.

Библиотеки

PyTorch предоставляет C++ версию своего модуля, библиотеки LibTorch. Он может скачать (cxx11 АБИ) и поместите рядом с будущей программой:

wget 
unzip libtorch-cxx11-abi-shared-with-deps-2.0.0%2Bcpu.zip

Например, вам также понадобится библиотека OpenCV. Это стоит того скачать и установить:

wget 
unzip opencv-4.7.0.zip && cd opencv-4.7.0
mkdir build && cd build
cmake ..
cmake --build .
sudo make install

Масса

Обычный формат масштаба модели PyTorch следует экспортировать как TorchScript. Для большинства моделей достаточно написать следующее:

# model_export.py
import torch

MODEL_PATH = 'model.pt'
MODEL_INPUT_SHAPE = (1,3,640,640)

model = torch.load(MODEL_PATH)
example = torch.randn(MODEL_INPUT_SHAPE)
model(example) # model warmup
model_traced = torch.jit.trace(model, example)
model_traced.save('model_traced.torchscript')

Однако, например, я использую модель YOLOv8 для распознавания лиц от Ultralytics, ее можно экспортировать с помощью готовых модулей:

pip3 install ultralytics
pip3 install gdown
gdown 1jG8_C_P0SbnzYROZORe7CiJO6oMgp7eZ # yolov8n-face.pt
# model_export_yolov8face.py
import torch
from ultralytics import YOLO 
model = YOLO('yolov8n-face.pt')
model.export(format="torchscript")

Использовать

Включает, библиотеки и ссылки

Для компилятора будущей программы нужно указать путь к icnlude и lib файлам библиотек LibTorch и OpenCV, а также связать необходимые lib файлы (они потребуются при компиляции, для автоматизации нужно использовать IDE, например Visual Studio Code):

# ...
-I/usr/local/include/opencv4
-I/path/to/libtorch/include
-L/path/to/libtorch/lib
# ...
-o main.o
# ...
-lopencv_core
-lopencv_highgui
-lopencv_imgcodecs
-lopencv_imgproc
-lopencv_videoio
-ltorch
-ltorch_cpu
-lc10

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

Загрузить модель довольно просто:

// main.cpp
#include <torch/script.h>
#include <opencv2/opencv.hpp>

int main() {

  torch::jit::script::Module
    model = torch::jit::load("yolov8n-face.torchscript");
  
  return 0;
}

Загрузка данных во вход модели

LibTorch принимает на вход вектор моделей torch::jit::IValue и возвращает значения torch::jit::IValueкоторые затем необходимо экспортировать в знакомые типы (tensor .toTensor()кортеж .toTuple() или любой другой доступный).

ЧИТАТЬ   Росимущество стало управлять «Юнипро» и «Фортумом» со смены гендиректора

Пример модели принимает в качестве входных данных тензор формы (B, 3, 640, 640) нормализованное изображение, но возвращает тензор формы (1, 5, 8400)где 5 тензоров содержат 8400 целочисленных значений сх, су, ж, час и настоящий конф для каждого блока обнаружения соответственно. Реализуем загрузку входных данных модели и прием выходов:

// main.cpp
  // ...
  cv::Mat img = cv::imread(source);
  cv::cvtColor(img, img, cv::COLOR_BGR2RGB);
  cv::normalize(img, img, 0.0, 1.0, cv::NORM_MINMAX, CV_32F);
  vector<torch::jit::IValue> inputs = {
    torch::from_blob(
      imgNorm.data,
      {640,640,3},
      torch::kFloat32
    ).permute({2,0,1}).unsqueeze(0)
  };
  at::Tensor outputs = model.forward(inputs).toTensor();
  // ...

Распознавание лиц

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

Давайте создадим класс Box для боксов обнаружения:

// detect.h
// ...
class Box {
    public:
        int x1, y1, x2, y2;
        float conf;
        Box(int x1, int y1, int x2,  int y2, float conf) {
            this->x1 = x1;
            this->y1 = y1;
            this->x2 = x2;
            this->y2 = y2;
            this->conf = conf;
        }
};
// ...

Давайте создадим функцию getBoxesкоторый вернет ящики, отфильтрованные по достоверности конф и с помощью будущего Без максимального удаления (НМС):

// detect.h
// ...
vector<Box> getBoxes(
    at::Tensor &outputs,
    float confThres = 0.25,
    float iouThres = 0.15
) {
    vector<Box> candidates;
    for (unsigned short ibatch = 0; ibatch < outputs.sizes()[0]; ibatch++) {
        for (unsigned short ibox = 0; ibox < outputs.sizes()[2]; ibox++) {
            float conf = outputs[ibatch][4][ibox].item<float>();
            if (conf >= confThres) {
                unsigned short
                    cx = outputs[ibatch][0][ibox].item<int>(),
                    cy = outputs[ibatch][1][ibox].item<int>(),
                    w = outputs[ibatch][2][ibox].item<int>(),
                    h = outputs[ibatch][3][ibox].item<int>();
                unsigned short
                    x1 = cx - w / 2,
                    y1 = cy - h / 2,
                    x2 = cx + w / 2,
                    y2 = cy + h / 2;
                candidates.push_back(Box(x1,y1,x2,y2,conf));
            }
        }
    }
    sort(candidates.begin(), candidates.end(), [](Box b1, Box b2){return b1.conf > b2.conf;});
    vector<Box> boxes = nms(candidates, iouThres);
    return boxes;
}
// ...

Давайте создадим функцию nms который будет фильтровать поля-кандидаты и iouкоторый рассчитает для них метрику IoU (Перекресток на Юнион):

// detect.h
// ...
float iou(Box &fb, Box &sb) {
    float inter = max(min(fb.x2, sb.x2) - min(fb.x1, sb.x1), 0) * max(min(fb.y2, sb.y2) - min(fb.y1, sb.y1), 0);
    float union_ = (fb.x2-fb.x1)*(fb.y2-fb.y1) + (sb.x2-sb.x1)*(sb.y2-sb.y1) - inter;
    return inter / union_;
}

vector<Box> nms(vector<Box> &boxes, float iouThres) {
    vector<Box> supBoxes;
    for (Box box: boxes) {
        bool valid = true;
        for (Box supBox: supBoxes) {
            if (iou(box, supBox) > iouThres) {
                valid = false;
                break;
            }
        }
        if (valid == true) {
            supBoxes.push_back(box);
        }
    }
    return supBoxes;
}
// getBoxes

Наконец, мы получаем поля обнаружения:

// main.cpp
  // ...
  vector<Box> boxes = getBoxes(outputs);
  // ...

А также напишите процедуру выбора ящиков на исходном изображении:

// detect.h
// ...
void highlightBoxes(cv::Mat &img, vector<Box> &boxes) {
  cv::Scalar rectColor(0,192,0);
  unsigned short fontScale = 2, confPrecis = 2;

  for (Box box: boxes) {
    string text = to_string(box.conf);
    cv::rectangle(img, {box.x1,box.y1}, {box.x2,box.y2}, rectColor, 2);
    cv::rectangle(
      img,
      {box.x1, box.y1 - fontScale * 12},
      {box.x1 + (unsigned short)text.length() * fontScale * 9, box.y1},
      rectColor,
      -1
    );
    cv::putText(img, text, {box.x1,box.y1}, cv::FONT_HERSHEY_PLAIN, fontScale, {255,255,255}, 2);
  }
}
// ...

И вывести результат на экран:

// main.cpp
  // ...
  highlightBoxes(img, boxes);
  cv::imshow("Result", img);
  // return

Пример результата

Заключение

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

ЧИТАТЬ   Турция выразила позицию по зерновой сделке
9b2ff71378cf667458346316aba104e4

Source

От admin