Explicación detallada y demostración de la API común de OpenVINOSharp

Autor del artículo: Yan Guojin, embajador de innovación en Intel Edge Computing

Orientación técnica: Wu Zhuo, Li Yiwei

El conjunto de herramientas OpenVINO™ puede acelerar el desarrollo de aplicaciones de visión de aprendizaje profundo y ayudar a los usuarios a implementar modelos de IA en sistemas de producción de manera más conveniente y rápida en varias plataformas Intel, desde el borde hasta la nube.

C# es un lenguaje de programación orientado a objetos seguro, estable, simple y elegante derivado de C y C++, que combina las operaciones visuales simples de VB y la alta eficiencia operativa de C++, lo que lo convierte en la primera opción para el lenguaje de desarrollo .NET. Como desarrollador de inteligencia artificial, si desea utilizar OpenVINO™ en el lado de C#, OpenVINOSharp será su primera opción y se ha producido un paquete NuGet para realizar la instalación y el uso de OpenVINO en el lado de C# en un solo lugar .

OpenVINOSharp GitHub: https://github.com/guojin-yan/OpenVinoSharp

OpenVINOSharp hace referencia a la API OpenVINO™ C++ al crearlo, por lo que es muy amigable para las personas que han usado OpenVINO™ antes. La siguiente tabla nos muestra la relación correspondiente entre C# y C++ API

Clase

API C++

API C#

ilustrar

clase principal

ov::Núcleo

Centro

Clases de entidades centrales en tiempo de ejecución de OpenVINO

clase modelo

ov::Modelo

Modelo

clase de modelo definida por el usuario

Clase de modelo compilado

ov::Modelo compilado

Modelo compilado

Clase de modelo compilado

Clase de salida

ov:: Salida<ov::Nodo>

Producción

Clase de manejo para salida de nodo

Clase de entrada

ov:: Entrada <ov::Nodo>

Aporte

Clase de manejo para entrada de nodo

Clase InferRequest

ov::InferRequest

ov::InferRequest

Clases para ejecutar solicitudes de inferencia de forma asincrónica o sincrónica

clase tensorial

ov::Tensor

Tensor

tensor

Clase de forma

ov::Forma

Forma

clase de forma tensorial

En este artículo, basándose en los pasos generales de la implementación del modelo, se demostrará y comparará el uso de funciones de método desde la carga del modelo hasta la inferencia con la API de C++.

1.1 Instalar OpenVINOSharp

OpenVINOSharp admite el método de instalación del paquete NuGet, que es más simple que el proceso de instalación en C++, y el paquete contiene la versión de lanzamiento OpenVINO™ 2023.0 , que se puede usar directamente después de la instalación a través de NuGet.

Si usa Visual Studio para compilar el proyecto, puede instalarlo directamente a través de la función de administración de paquetes NuGet:

Si lo instala mediante el comando dotnet, puede instalarlo mediante la siguiente declaración:

dotnet add package OpenVinoSharp.win

1.2 montaje de importación

Todos los ensamblados de OpenVINOSharp están bajo el espacio de nombres OpenVinoSharp, por lo que si desea utilizar OpenVINOSharp, primero debe introducir el espacio de nombres:

using OpenVinoSharp;

1.3 Inicialice el kernel de tiempo de ejecución de OpenVINO

La clase Core representa una entidad central de tiempo de ejecución de OpenVINO. Los métodos posteriores, como leer el modelo y cargar el modelo, deben crearse a través de la clase Core. Al encapsular la API de C#, para corresponder a la API de C++, la clase Core también es encapsulado y encapsulado El método correspondiente en la API de C ++

Método de inicialización en C#:

Core core = new Core();

Método de inicialización en C++:

ov::Core core;

1.4 Cargar y obtener información del modelo.

1.4.1 Modelo de carga

Una vez actualizada y cargada la versión OpenVINO™ 2022.1, los siguientes son los métodos API utilizados:

API

efecto

núcleo.read_model ()

Cargue el modelo desde el disco duro a la memoria y devuelva el objeto Modelo.

Cómo cargar un modelo en C#:

Model model = core.read_model(model_path);

Método de inicialización en C++:

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

1.4.2Obtener información del modelo

Tanto el objeto Modelo obtenido mediante el método Core.read_model () como el objeto CompiledModel obtenido mediante el método Core.compile_model () admiten el acceso directo a los atributos para obtener información de las capas de entrada y salida. Tomando el objeto Modelo para obtener información del modelo como ejemplo, los siguientes son los métodos API utilizados:

API

efecto

Modelo.get_friendly_name()

Obtenga el nombre descriptivo del modelo.

Modelo.entrada()

Obtiene la capa de entrada del modelo y devuelve el objeto de entrada.

Modelo.salida()

Obtiene la capa de salida del modelo y devuelve el objeto Salida.

La entrada/salida encapsula principalmente la capa de red del modelo y puede obtener información detallada del modelo a través de la siguiente API:

API

efecto

Salida.get_any_name()

Obtenga el nombre de la capa de red del modelo.

Salida.get_element_type()

Obtenga el tipo de datos de la capa de red modelo y devuelva el objeto OvType. OvType encapsula principalmente el tipo de datos básico de la red.

Salida.get_shape()

Obtenga la forma de la capa de red del modelo y devuelva el objeto Forma. La forma encapsula la matriz de formas de la capa de red.

En C#, puede obtener directamente la entrada, la capa de entrada y el nombre descriptivo del modelo a través del siguiente código:

string model_name = model.get_friendly_name();

Input input = model.input();

Output output = model.output();

Luego imprima la información específica del modelo en la página de la consola:

Console.WriteLine("Model name: {0}", model_name);

Console.WriteLine("/------- [In] -------/");

Console.WriteLine("Input name: {0}", input.get_any_name());

Console.WriteLine("Input type: {0}", input.get_element_type().to_string());

Console.WriteLine("Input shape: {0}", input.get_shape().to_string());

Console.WriteLine("/------- [Out] -------/");

Console.WriteLine("Output name: {0}", output.get_any_name());

Console.WriteLine("Output type: {0}", output.get_element_type().to_string());

Console.WriteLine("Output shape: {0}", output.get_shape().to_string());

Obtenga información de la capa de red del modelo de la siguiente manera:

Model name: torch_jit

/------- [In] -------/

Input name: data

Input type: float

Input shape: [1,3,224,224]

/------- [Out] -------/

Output name: prob

Output type: float

Output shape: [1,1000]

Usamos la API de C++ para implementar la misma información de salida de la siguiente manera:

std::cout << "Model name: " << model->get_friendly_name() << std::endl;

ov::Output<ov::Node> input = model->input();

std::cout << "/------- [In] -------/" << std::endl;

std::cout << "Input name: " << input.get_any_name() << std::endl;

std::cout << "Input type: " << input.get_element_type().c_type_string() << std::endl;

std::cout << "Input shape: " << input.get_shape().to_string() << std::endl;

ov::Output<ov::Node> output = model->output();

std::cout << "/------- [Out] -------/" << std::endl;

std::cout << "Output name: " << output.get_any_name() << std::endl;

std::cout << "Output type: " << output.get_element_type().c_type_string() << std::endl;

std::cout << "Output shape: " << output.get_shape().to_string() << std::endl;

1.5Compile el modelo y cree una solicitud de inferencia.

Después de leer el modelo local, llame al método de compilación del modelo para compilar el modelo en un objeto compile_model que se pueda ejecutar en el dispositivo de destino y cree un objeto de solicitud de inferencia utilizado para inferir el modelo compilado a través de este objeto. Los siguientes son los métodos API utilizados:

API

efecto

Núcleo.compile_model()

Compila el modelo en un objeto compile_model que se puede ejecutar en el dispositivo de destino.

Modelo compilado.create_infer_request()

Crea un objeto de solicitud de inferencia utilizado para inferir el modelo compilado. La solicitud creada tiene tensores de entrada y salida asignados.

Cómo compilar el modelo y crear solicitudes de inferencia en C#:

CompiledModel compiled_model = core.compile_model(model, "AUTO");

InferRequest infer_request = compiled_model.create_infer_request();

Utilice la API de C++ para compilar el modelo y crear una solicitud de inferencia:

CompiledModel compiled_model = core.compile_model(model, "AUTO");

InferRequest infer_request = compiled_model.create_infer_request();

1.6 TensorTensor _

1.6.1 Obtención y configuración de tensores

Después de crear una solicitud de inferencia, el sistema creará y asignará automáticamente tensores de entrada y salida. Los tensores se pueden obtener a través del objeto InferRequest, y los tensores se pueden personalizar y cargar en el nodo especificado del modelo; el número de secuencia y el nombre pueden se generará de acuerdo con la entrada del tensor. Además de obtener y configurar objetos de nodo de modelo, la API principal de C# es la siguiente:

API

efecto

InferRequest.set_tensor()

Configure los tensores de entrada/salida para inferir.

InferRequest.set_input_tensor()

Establezca el tensor de entrada para inferir.

InferRequest.set_output_tensor()

Establecer el tensor de salida para inferir

InferRequest.get_tensor()

Obtenga tensores de entrada/salida para inferencia.

InferRequest.get_input_tensor()

Obtenga un tensor de entrada para la inferencia.

InferRequest.get_output_tensor()

Obtenga el tensor de salida para realizar inferencias.

1.6.2 Adquisición y configuración de información tensorial

La información principal contenida en un tensor incluye la forma del tensor (Shape), el formato de datos del tensor (OvType-> element.Type) y los datos de memoria en el tensor. Los parámetros de un tensor se pueden manipular mediante los siguientes métodos API:

API

efecto

tensor.set_shape ()

Establece una nueva forma para un tensor.

tensor.get_shape()

Consigue la forma del tensor.

Tensor.get_element_type()

Obtenga el tipo de datos del tensor.

tensor.get_size()

Obtenga la longitud de los datos del tensor.

tensor.get_byte_size()

Obtenga el tamaño de bytes del tensor.

Tensor.datos()

Obtenga la dirección de memoria del tensor.

Tensor.set_data<T>()

Cargue datos del tipo especificado en la memoria del tensor.

tensor.get_data<T>()

Leer datos del tipo especificado del tensor.

Los métodos anteriores son algunas operaciones básicas en tensores. Excepto set_data y get_data, que son exclusivos de OpenVINOSharp, otras interfaces son consistentes con la API de C ++.

1.7 Cargar datos de inferencia

1.7.1 获取输入张量

对于单输入的模型可以直接通过get_input_tensor()方法获得,并调用Tensor的相关方法获取Tensor的相关信息,C# 代码如下所示:

Tensor input_tensor = infer_request.get_input_tensor();

Console.WriteLine("/------- [Input tensor] -------/");

Console.WriteLine("Input tensor type: {0}", input_tensor.get_element_type().to_string());

Console.WriteLine("Input tensor shape: {0}", input_tensor.get_shape().to_string());

Console.WriteLine("Input tensor size: {0}", input_tensor.get_size());

获取输出结果为:

/------- [Input tensor] -------/

Input tensor type: f32

Input tensor shape: Shape : {1, 3, 224, 224}

Input tensor size: 150528

对于上述的同样输出内容,我们也可以通过C++ API 实现,C++ 代码如下:

ov::Tensor input_tensor = infer_request.get_input_tensor();
std::cout << "/------- [Input tensor] -------/" << std::endl;
std::cout << "Input tensor type: " << input_tensor.get_element_type().c_type_string() << std::endl;
std::cout << "Input tensor shape: " << input_tensor.get_shape().to_string() << std::endl;
std::cout << "Input tensor size: " << input_tensor.get_size() << std::endl;

1.7.2 添加推理数据

这一步主要是将处理好的图片数据加载到Tensor数据内存中,OpenVINO的API中提供了访问内存地址的接口,可以获取数据内存首地址,不过为了更好的加载推理数据,我们此处封装了set_data<T>()方法,可以实现将处理后的图片数据加载到数据内存上。在C#中的代码为:

Mat input_mat = new Mat();

Shape input_shape = input_tensor.get_shape();

long channels = input_shape[1];

long height = input_shape[2];

long width = input_shape[3];

float[] input_data = new float[channels * height * width];

Marshal.Copy(input_mat.Ptr(0), input_data, 0, input_data.Length);

input_tensor.set_data(input_data);

下面是在C++中实现上述功能的代码:

cv::Mat input_mat;

float* input_data = input_tensor.data<float>();

ov::Shape input_shape = input_tensor.get_shape();

size_t channels = input_shape[1];

size_t height = input_shape[2];

size_t width = input_shape[3];

for (size_t c = 0; c < channels; ++c) {

    for (size_t h = 0; h < height; ++h) {

        for (size_t w = 0; w < width; ++w) {

            input_data[c * height * width + h * width + w] = input_mat.at<cv::Vec<float, 3>>(h, w)[c];

        }

    }

}

1.8 ​​​​​​​ 模型推理

在加载完推理数据后,就可以调用模型推理的API方法推理当前数据,主要使用到的API方法为:

API

作用

InferRequest.infer()

在同步模式下推断指定的输入。

调用该方法也较为简单,只需要调用该API接口即可,在C#中的代码为:

infer_request.infer();

C++中的代码与C++中一致。

1.9  获取推理结果

对于单输出的模型可以直接通过get_output_tensor()方法获得,并调用Tensor的相关方法获取Tensor的相关信息,C# 代码如下所示:

Tensor output_tensor = infer_request.get_output_tensor();

Console.WriteLine("/------- [Output tensor] -------/");

Console.WriteLine("Output tensor type: {0}", output_tensor.get_element_type().to_string());

Console.WriteLine("Output tensor shape: {0}", output_tensor.get_shape().to_string());

Console.WriteLine("Output tensor size: {0}", output_tensor.get_size());

获取输出output_tensor信息为:

/------- [Output tensor] -------/

Output tensor type: f32

Output tensor shape: Shape : {1, 1000}

Output tensor size: 1000

对于输出Tensor,我们只需要读取输出内存上的数据即可,此处我们封装了get_data<T>()方法,可以直接获取输出内存上的数据,在C#中的代码为:

float[] result = output_tensor.get_data<float>(1000);

同样获取推理结果,在C++中的代码为:

const float* output_data = output_tensor.data<const float>();

float result[1000];

for (int i = 0; i < 1000; ++i) {

result[i] = *output_data;

output_data++;

}

在获取结果后,后续的处理需要根据模型的输出类型做相应的处理。

1.10 释放分配的内存

由于C#在封装时采用的C API 接口实现的,因此在C#中会产生较多的 非托管内存,若该对象出现循环重复创建,会导致过多的内存未释放导致内存泄漏,因此对于临时创建的对象在使用后要即使销毁,销毁方式也较为简单,只需要调用对象的dispose()方法即可。

output_tensor.dispose();

input_shape.dispose();

infer_request.dispose();

compiled_model.dispose();

input.dispose();

output.dispose();

model.dispose();

core.dispose();

1.11  Ejemplo de modelo de clasificación Yolov8

El siguiente código muestra el código completo del modelo de clasificación Yolov8 utilizando el método API OpenVINOSharp para implementar el modelo:

using OpenCvSharp;

using OpenCvSharp.Dnn;

using OpenVinoSharp;

using System.Data;

using System.Runtime.InteropServices;



namespace test_openvinosharp_api

{

    internal class Program

    {

        static void Main(string[] args)

        {

            string model_path = "E:\\GitSpace\\OpenVinoSharp\\model\\yolov8\\yolov8s-cls.xml";

            Core core = new Core(); // 初始化推理核心

            Model model = core.read_model(model_path); // 读取本地模型

            CompiledModel compiled_model = core.compile_model(model, "AUTO"); // 便哟模型到指定设备



            // 获取模型的输入输出信息

            Console.WriteLine("Model name: {0}", model.get_friendly_name());

            Input input = compiled_model.input();

            Console.WriteLine("/------- [In] -------/");

            Console.WriteLine("Input name: {0}", input.get_any_name());

            Console.WriteLine("Input type: {0}", input.get_element_type().to_string());

            Console.WriteLine("Input shape: {0}", input.get_shape().to_string());

            Output output = compiled_model.output();

            Console.WriteLine("/------- [Out] -------/");

            Console.WriteLine("Output name: {0}", output.get_any_name());

            Console.WriteLine("Output type: {0}", output.get_element_type().to_string());

            Console.WriteLine("Output shape: {0}", output.get_shape().to_string());

            // 创建推理请求

            InferRequest infer_request = compiled_model.create_infer_request();

            // 获取输入张量

            Tensor input_tensor = infer_request.get_input_tensor();

            Console.WriteLine("/------- [Input tensor] -------/");

            Console.WriteLine("Input tensor type: {0}", input_tensor.get_element_type().to_string());

            Console.WriteLine("Input tensor shape: {0}", input_tensor.get_shape().to_string());

            Console.WriteLine("Input tensor size: {0}", input_tensor.get_size());

            // 读取并处理输入数据

            Mat image = Cv2.ImRead(@"E:\GitSpace\OpenVinoSharp\dataset\image\demo_7.jpg");

            Mat input_mat = new Mat();

            input_mat = CvDnn.BlobFromImage(image, 1.0 / 255.0, new Size(224, 224), 0, true, false);

            // 加载推理数据

            Shape input_shape = input_tensor.get_shape();

            long channels = input_shape[1];

            long height = input_shape[2];

            long width = input_shape[3];

            float[] input_data = new float[channels * height * width];

            Marshal.Copy(input_mat.Ptr(0), input_data, 0, input_data.Length);

            input_tensor.set_data(input_data);

            // 模型推理

            infer_request.infer();

            // 获取输出张量

            Tensor output_tensor = infer_request.get_output_tensor();

            Console.WriteLine("/------- [Output tensor] -------/");

            Console.WriteLine("Output tensor type: {0}", output_tensor.get_element_type().to_string());

            Console.WriteLine("Output tensor shape: {0}", output_tensor.get_shape().to_string());

            Console.WriteLine("Output tensor size: {0}", output_tensor.get_size());

            // 获取输出数据

            float[] result = output_tensor.get_data<float>(1000);

            List<float[]> new_list = new List<float[]> { };

            for (int i = 0; i < result.Length; i++)

            {

                new_list.Add(new float[] { (float)result[i], i });

            }

            new_list.Sort((a, b) => b[0].CompareTo(a[0]));



            KeyValuePair<int, float>[] cls = new KeyValuePair<int, float>[10];

            for (int i = 0; i < 10; ++i)

            {

                cls[i] = new KeyValuePair<int, float>((int)new_list[i][1], new_list[i][0]);

            }

            Console.WriteLine("\n Classification Top 10 result : \n");

            Console.WriteLine("classid probability");

            Console.WriteLine("------- -----------");

            for (int i = 0; i < 10; ++i)

            {

                Console.WriteLine("{0}     {1}", cls[i].Key.ToString("0"), cls[i].Value.ToString("0.000000"));

            }

            // 销毁非托管内存

            output_tensor.dispose();

            input_shape.dispose();

            infer_request.dispose();

            compiled_model.dispose();

            input.dispose();

            output.dispose();

            model.dispose();

            core.dispose();



        }

    }

}

1.12  Resumen

En este artículo, demostramos el uso de la API OpenVINOSharp basado en el proceso de inferencia del modelo y lo comparamos con la API OpenVINO C++ para mostrar la diferencia entre el uso de la API OpenVINOSharp y la API de C++, lo que también tiene un impacto positivo en el desarrollo de C++. API El operador es muy amigable y será muy fácil comenzar.

En este artículo, solo mostramos el código del proceso de inferencia del modelo básico y también probamos cada API. Para otros métodos API más avanzados, continuaremos probando otros métodos API en el futuro para mostrar a los desarrolladores su uso.

En general, OpenVINOSharp actualmente es totalmente compatible con la instalación y el uso en el entorno Windows . Los desarrolladores pueden instalarlo y usarlo. Si tiene preguntas relevantes o métodos de optimización, también puede brindar comentarios y orientación.

Supongo que te gusta

Origin blog.csdn.net/gc5r8w07u/article/details/132580418
Recomendado
Clasificación