C#联合Openvino实现PP-TinyPose检测

1、配置好paddlepaddle环境,详见PaddleDetection环境搭建测试

2、下载PaddleDetection源码,并配置好PaddleDetection环境,测试paddle运行正常

3、下载vs2019或vs2022

4、下载cmake,https://cmake.org/download/

5、安装opencv,https://opencv.org/releases/

6、以上这些都是构建项目的必须组件,现在开始openvino的安装
在这里插入图片描述
python下使用就直接PIP安装,安装Development Tools版本,如果是C++使用就安装runtime离线版本,后面C++内配置openvino需要用到runtime版本的dll和lib:
在这里插入图片描述
在这里插入图片描述
安装完成后,可以看到在C:\Program Files (x86)\Intel\openvino_2022.1.0.643文件夹中已经有相关的文件,python也可以把离线包内的Python包拷贝到conda环境使用,但建议还是直接Pip方便

7、在vs内配置好opencvopenvino的库,都一样配置好三个地方就行,用属性表配置方便,用release还是debug版本根据需要选择就行:
包含目录、库目录、附加依赖项
openvino:
在这里插入图片描述
在这里插入图片描述
opencv:
在这里插入图片描述
在这里插入图片描述

8.增加OpenVinoAPI.cpp文件,代码引用自https://mp.weixin.qq.com/s/gOoeMPeAcvMSeGVqToKWuQ

// OpenVINO C++ dll code for C#
// 该项目支持模型格式:
//   1. paddlepaddle飞桨模型(.pdmodel)
//   2. onnx中间格式(.onnx)
//   3. OpenVINO的IR格式(.xml)
// 针对该项目所使用测试网络:
//   1. PaddleClas 图像分类模型 花卉种类识别网络的(.pdmodel)、(.onnx)、(.xml)格式;
//   2. Paddledetection 目标检测模型 车辆识别网络的(.pdmodel)格式;
// ONLY support batchsize = 1


#include<time.h>

#include<iostream>
#include<map>
#include<string>
#include<vector>

#include "openvino/openvino.hpp"
#include "opencv2/opencv.hpp"
#include <opencv2/core/utils/logger.hpp>

#include<windows.h>

// @brief 将wchar_t*字符串指针转换为string字符串格式
// @param wchar 输入字符指针
// @return 转换出的string字符串 
std::string wchar_to_string(const wchar_t* wchar) {
    // 获取输入指针的长度
    int path_size = WideCharToMultiByte(CP_OEMCP, 0, wchar, wcslen(wchar), NULL, 0, NULL, NULL);
    char* chars = new char[path_size + 1];
    // 将双字节字符串转换成单字节字符串
    WideCharToMultiByte(CP_OEMCP, 0, wchar, wcslen(wchar), chars, path_size, NULL, NULL);
    chars[path_size] = '\0';
    std::string pattern = chars;
    delete chars; //释放内存
    return pattern;
}

// @brief 将图片的矩阵数据转换为opencv的mat数据
// @param data 图片矩阵
// @param size 图片矩阵长度
// @return 转换后的mat数据
cv::Mat data_to_mat(uchar* data, size_t size) {
    //将图片数组数据读取到容器中
    std::vector<uchar> buf;
    for (int i = 0; i < size; i++) {
        buf.push_back(*data);
        data++;
    }
    // 利用图片解码,将容器中的数据转换为mat类型
    return cv::imdecode(cv::Mat(buf), 1);
}

// @brief 对网络的输入为图片数据的节点进行赋值,实现图片数据输入网络
// @param input_tensor 输入节点的tensor
// @param inpt_image 输入图片数据
void fill_tensor_data_image(ov::Tensor& input_tensor, const cv::Mat& input_image) {
    // 获取输入节点要求的输入图片数据的大小
    ov::Shape tensor_shape = input_tensor.get_shape();
    const size_t width = tensor_shape[3]; // 要求输入图片数据的宽度
    const size_t height = tensor_shape[2]; // 要求输入图片数据的高度
    const size_t channels = tensor_shape[1]; // 要求输入图片数据的维度
    // 读取节点数据内存指针
    float* input_tensor_data = input_tensor.data<float>();
    // 将图片数据填充到网络中
    // 原有图片数据为 H、W、C 格式,输入要求的为 C、H、W 格式
    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_tensor_data[c * width * height + h * width + w] = input_image.at<cv::Vec<float, 3>>(h, w)[c];
            }
        }
    }
}
// @brief 对网络的输入为fkloat数据的节点进行赋值,实现float数据输入网络
// @param input_tensor 输入节点的tensor
// @param input_data 输入数据数组
// @param data_size 输入数组长度
void fill_tensor_data_float(ov::Tensor& input_tensor, float* input_data, int data_size) {
    // 读取节点数据内存指针
    float* input_tensor_data = input_tensor.data<float>();
    // 将图片数据填充到网络中
    for (int i = 0; i < data_size; i++) {
        input_tensor_data[i] = input_data[i];
    }
}

// @brief 构建放射变换矩阵
// @param center 中心点
// @param input_size 输入尺寸
// @param rot 角度
// @param output_size 输出尺寸
// @param shift 
// @rrturn 变换矩阵
cv::Mat get_affine_transform(cv::Point center, cv::Size input_size, int rot, cv::Size output_size, cv::Point2f shift = cv::Point2f(0, 0)) {

    // 输入尺寸宽度
    int src_w = input_size.width;

    // 输出尺寸
    int dst_w = output_size.width;
    int dst_h = output_size.height;

    // 旋转角度
    float rot_rad = 3.1715926f * rot / 180.0;
    int pt = (int)src_w * -0.5;
    float sn = std::sin(rot_rad);
    float cs = std::cos(rot_rad);

    cv::Point2f src_dir(-1.0 * pt * sn, pt * cs);
    cv::Point2f dst_dir(0.0, dst_w * -0.5);
    // 输入三个点
    cv::Point2f src[3];
    src[0] = cv::Point2f(center.x + input_size.width * shift.x, center.y + input_size.height * shift.y);
    src[1] = cv::Point2f(center.x + src_dir.x + input_size.width * shift.x, center.y + src_dir.y + input_size.height * shift.y);
    cv::Point2f direction = src[0] - src[1];
    src[2] = cv::Point2f(src[1].x - direction.y, src[1].y - direction.x);
    // 输出三个点
    cv::Point2f dst[3];
    dst[0] = cv::Point2f(dst_w * 0.5, dst_h * 0.5);
    dst[1] = cv::Point2f(dst_w * 0.5 + dst_dir.x, dst_h * 0.5 + dst_dir.y);
    direction = dst[0] - dst[1];
    dst[2] = cv::Point2f(dst[1].x - direction.y, dst[1].y - direction.x);

    return cv::getAffineTransform(src, dst);

}


// @brief 推理核心结构体
typedef struct openvino_core {
    ov::Core core; // core对象
    std::shared_ptr<ov::Model> model_ptr; // 读取模型指针
    ov::CompiledModel compiled_model; // 模型加载到设备对象
    ov::InferRequest infer_request; // 推理请求对象
} CoreStruct;

extern "C" __declspec(dllexport) int __stdcall test_add(int a, int b) {
    return a + b;
}


// @brief 初始化openvino核心结构体,读取本地推理模型,将模型加载到设备,并创建推理请求
// @note 可支持的推理模型的格式为:(.pdmodel)、(.onnx)、(.xml)格式
// @note 可支持设备选择(AUTO)自动选择、(CPU)处理器、(GPU)显卡
// @param model_file_wchar 推理模型本地地址
// @param device_name_wchar 加载设备名称
// @return 推理核心结构体指针
extern "C" __declspec(dllexport) void* __stdcall core_init(const wchar_t* model_file_wchar, const wchar_t* device_name_wchar) {
    cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_ERROR);

    //读取接口输入参数
    std::string model_file_path = wchar_to_string(model_file_wchar);// 推理模型本地地址
    std::string device_name = wchar_to_string(device_name_wchar);// 加载设备名称
    // 初始化推理核心
    CoreStruct* p = new CoreStruct(); // 创建推理引擎指针
    p->model_ptr = p->core.read_model(model_file_path); // 读取推理模型
    p->compiled_model = p->core.compile_model(p->model_ptr, "CPU"); // 将模型加载到设备
    p->infer_request = p->compiled_model.create_infer_request(); // 创建推理请求

    return (void*)p;
}


// @brief 为输入为图片数据的tensor设置新形状,如果新的总大小大于前一个,则取消之前的设置
// @param inference_engine 推理核心指针
// @param input_node_name_wchar 输入节点名
// @param input_size 输入形状数据数组
// @return 推理核心结构体指针
extern "C"  __declspec(dllexport) void* __stdcall set_input_image_sharp(void* core_ptr, const wchar_t* input_node_name_wchar, size_t * input_size) {
    // 读取推理模型地址
    CoreStruct* p = (CoreStruct*)core_ptr;
    std::string input_node_name = wchar_to_string(input_node_name_wchar);
    // 获取指定节点的tensor
    ov::Tensor input_image_tensor = p->infer_request.get_tensor(input_node_name);
    // 设置节点输入数据的形状
    input_image_tensor.set_shape({ input_size[0],input_size[1],input_size[2],input_size[3] });
    return (void*)p;
}

// @brief 为输入为float数据的tensor设置新形状,如果新的总大小大于前一个,则取消之前的设置
// @param inference_engine 推理核心指针
// @param input_node_name_wchar 输入节点名
// @param input_size 输入形状数据数组
// @return 推理核心结构体指针
extern "C"  __declspec(dllexport) void* __stdcall set_input_data_sharp(void* core_ptr, const wchar_t* input_node_name_wchar, size_t * input_size) {
    // 读取推理模型地址
    CoreStruct* p = (CoreStruct*)core_ptr;
    std::string input_node_name = wchar_to_string(input_node_name_wchar);
    // 获取指定节点的tensor
    ov::Tensor input_image_tensor = p->infer_request.get_tensor(input_node_name);
    // 重新设置数据长度
    input_image_tensor.set_shape({ input_size[0] , input_size[1] });
    return (void*)p;
}

// @brief 将图片数据加载到tensor中的数据内存上
// @param inference_engine 推理核心指针
// @param input_node_name_wchar 输入节点名
// @param image_data 输入图片数据矩阵
// @param image_size 图片矩阵长度
// @return 推理核心结构体指针
extern "C"  __declspec(dllexport) void* __stdcall load_image_input_data(void* core_ptr, const wchar_t* input_node_name_wchar, uchar * image_data, size_t image_size, int type) {
    // 读取推理模型地址
    CoreStruct* p = (CoreStruct*)core_ptr;
    std::string input_node_name = wchar_to_string(input_node_name_wchar);
    // 获取输入节点tensor
    ov::Tensor input_image_tensor = p->infer_request.get_tensor(input_node_name);
    int input_H = input_image_tensor.get_shape()[2]; //获得"image"节点的Height
    int input_W = input_image_tensor.get_shape()[3]; //获得"image"节点的Width

    // 对输入图片进行预处理
    cv::Mat input_image = data_to_mat(image_data, image_size); // 读取输入图片
    cv::Mat blob_image;
    cv::cvtColor(input_image, blob_image, cv::COLOR_BGR2RGB); // 将图片通道由 BGR 转为 RGB

    if (type == 0) {
        // 对输入图片按照tensor输入要求进行缩放
        cv::resize(blob_image, blob_image, cv::Size(input_W, input_H), 0, 0, cv::INTER_LINEAR);
        // 图像数据归一化,减均值mean,除以方差std
        // PaddleDetection模型使用imagenet数据集的均值 Mean = [0.485, 0.456, 0.406]和方差 std = [0.229, 0.224, 0.225]
        std::vector<float> mean_values{ 0.485 * 255, 0.456 * 255, 0.406 * 255 };
        std::vector<float> std_values{ 0.229 * 255, 0.224 * 255, 0.225 * 255 };
        std::vector<cv::Mat> rgb_channels(3);
        cv::split(blob_image, rgb_channels); // 分离图片数据通道
        for (auto i = 0; i < rgb_channels.size(); i++) {
            //分通道依此对每一个通道数据进行归一化处理
            rgb_channels[i].convertTo(rgb_channels[i], CV_32FC1, 1.0 / std_values[i], (0.0 - mean_values[i]) / std_values[i]);
        }
        cv::merge(rgb_channels, blob_image); // 合并图片数据通道
    }
    else if (type == 1) {
        // 对输入图片按照tensor输入要求进行缩放
        cv::resize(blob_image, blob_image, cv::Size(input_W, input_H), 0, 0, cv::INTER_LINEAR);
        // 图像数据归一化
        std::vector<float> mean_values{ 0.5 * 255, 0.5 * 255, 0.5 * 255 };
        std::vector<float> std_values{ 0.5 * 255, 0.5 * 255, 0.5 * 255 };
        std::vector<cv::Mat> rgb_channels(3);
        cv::split(blob_image, rgb_channels); // 分离图片数据通道
        for (auto i = 0; i < rgb_channels.size(); i++) {
            //分通道依此对每一个通道数据进行归一化处理
            rgb_channels[i].convertTo(rgb_channels[i], CV_32FC1, 1.0 / std_values[i], (0.0 - mean_values[i]) / std_values[i]);
        }
        cv::merge(rgb_channels, blob_image); // 合并图片数据通道
    }
    else if (type == 2) {
        // 获取仿射变换信息
        cv::Point center(blob_image.cols / 2, blob_image.rows / 2); // 变换中心
        cv::Size input_size(blob_image.cols, blob_image.rows); // 输入尺寸
        int rot = 0; // 角度
        cv::Size output_size(input_W, input_H); // 输出尺寸

        // 获取仿射变换矩阵
        cv::Mat warp_mat(2, 3, CV_32FC1);
        warp_mat = get_affine_transform(center, input_size, rot, output_size);
        // 仿射变化
        cv::warpAffine(blob_image, blob_image, warp_mat, output_size, cv::INTER_LINEAR);
        // 图像数据归一化
        std::vector<float> mean_values{ 0.485 * 255, 0.456 * 255, 0.406 * 255 };
        std::vector<float> std_values{ 0.229 * 255, 0.224 * 255, 0.225 * 255 };
        std::vector<cv::Mat> rgb_channels(3);
        cv::split(blob_image, rgb_channels); // 分离图片数据通道
        for (auto i = 0; i < rgb_channels.size(); i++) {
            //分通道依此对每一个通道数据进行归一化处理
            rgb_channels[i].convertTo(rgb_channels[i], CV_32FC1, 1.0 / std_values[i], (0.0 - mean_values[i]) / std_values[i]);
        }
        cv::merge(rgb_channels, blob_image); // 合并图片数据通道
    }
    // 将图片数据填充到tensor数据内存中
    fill_tensor_data_image(input_image_tensor, blob_image);

    return (void*)p;
}
// @brief 将其他数据加载到tensor中的数据内存上
// @param inference_engine 推理核心指针
// @param input_node_name_wchar 输入节点名
// @param input_data 输入数据数组
// @return 推理核心结构体指针
extern "C"  __declspec(dllexport) void* __stdcall load_input_data(void* core_ptr, const wchar_t* input_node_name_wchar, float* input_data) {
    // 读取推理模型地址
    CoreStruct* p = (CoreStruct*)core_ptr;
    std::string input_node_name = wchar_to_string(input_node_name_wchar);
    // 读取指定节点tensor
    ov::Tensor input_image_tensor = p->infer_request.get_tensor(input_node_name);
    int input_size = input_image_tensor.get_shape()[1]; //获得输入节点的长度
    // 将数据填充到tensor数据内存上
    fill_tensor_data_float(input_image_tensor, input_data, input_size);

    return (void*)p;
}

// @brief 对加载好的推理模型进行推理
// @param inference_engine 推理核心指针
// @return 推理核心结构体指针
extern "C"  __declspec(dllexport) void* __stdcall core_infer(void* core_ptr) {
    // 读取推理模型地址
    CoreStruct* p = (CoreStruct*)core_ptr;
    // 模型预测
    p->infer_request.infer();

    return (void*)p;
}

// @brief 查询float类型的推理结果
// @param inference_engine 推理核心指针
// @param output_node_name_wchar 输出节点名
// @param data_size 数据长度
// @param [out]  inference_result 推理结果数组
extern "C"  __declspec(dllexport) void __stdcall read_infer_result_F32(void* core_ptr, const wchar_t* output_node_name_wchar, int data_size, float* infer_result) {
    // 读取推理模型地址
    CoreStruct* p = (CoreStruct*)core_ptr;
    std::string output_node_name = wchar_to_string(output_node_name_wchar);
    // 读取指定节点的tensor
    const ov::Tensor& output_tensor = p->infer_request.get_tensor(output_node_name);
    // std::cout << " output_tensor.get_shape() :" << output_tensor.get_shape() << std::endl;
    // 获取网络节点数据地址
    const float* results = output_tensor.data<const float>();
    // 将输出结果复制到输出地址指针中
    for (int i = 0; i < data_size; i++) {
        *infer_result = results[i];
        infer_result++;
    }
}

// @brief 查询int类型的推理结果
// @param inference_engine 推理核心指针
// @param output_node_name_wchar 输出节点名
// @param data_size 数据长度
// @param [out]  inference_result 推理结果数组
extern "C"  __declspec(dllexport) void __stdcall read_infer_result_I32(void* core_ptr, const wchar_t* output_node_name_wchar, int data_size, int* infer_result) {
    // 读取推理模型地址
    CoreStruct* p = (CoreStruct*)core_ptr;
    std::string output_node_name = wchar_to_string(output_node_name_wchar);
    // 读取指定节点的tensor
    const ov::Tensor& output_tensor = p->infer_request.get_tensor(output_node_name);
    std::cout << " output_tensor.get_shape() :" << output_tensor.get_shape() << std::endl;
    // 获取网络节点数据地址
    const int* results = output_tensor.data<const int>();
    // 将输出结果赋值到输出地址指针中
    for (int i = 0; i < data_size; i++) {
        *infer_result = results[i];
        infer_result++;
    }
}

// @brief 查询long long类型的推理结果
// @param inference_engine 推理核心指针
// @param output_node_name_wchar 输出节点名
// @param data_size 数据长度
// @param [out]  inference_result 推理结果数组
extern "C"  __declspec(dllexport) void __stdcall read_infer_result_I64(void* core_ptr, const wchar_t* output_node_name_wchar, int data_size, long long* infer_result) {
    // 读取推理模型地址
    CoreStruct* p = (CoreStruct*)core_ptr;
    std::string output_node_name = wchar_to_string(output_node_name_wchar);
    // 读取指定节点的tensor
    const ov::Tensor& output_tensor = p->infer_request.get_tensor(output_node_name);
    // std::cout << " output_tensor.get_shape() :" << output_tensor.get_shape() << std::endl;
    // 获取网络节点数据地址
    const long long* results = output_tensor.data<const long long>();
    // 将输出结果赋值到输出地址指针中
    for (int i = 0; i < data_size; i++) {
        *infer_result = results[i];
        infer_result++;
    }
}


// @brief 删除推理核心结构体指针,释放占用内存
// @param inference_engine 推理核心指针
extern "C"  __declspec(dllexport) void __stdcall core_delet(void* core_ptr) {
    //读取推理模型地址
    CoreStruct* p = (CoreStruct*)core_ptr;
    // 删除占用内存
    delete p;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值