1.1 項目概述
1.1.1 項目介紹
C#調用OpenVINO工具套件部署Al模型項目開發項目,簡稱OpenVinoSharp,這是一個示例項目,該項目實現在C#編程語言下調用Intel推出的 OpenVINO 工具套件,進行深度學習等Al項目在C#框架下的部署。該項目由C++語言編寫OpenVINO dll庫,并在C#語言下實現調用。
項目可以實現在C#編程語言下調用Intel推出的 OpenVINO 工具套件,進行深度學習等Al項目在C#框架下的部署,目前可以支持的Al模型格式:
■ Paddlepaddle 飛槳模型 (.pdmodel)
■ ONNX 開放式神經網絡交換模型 (.onnx)
■ IR 模型 (.xml, .bin)
目前該項目針對 Paddlepaddle 飛槳現有模型進行了測試,主要有:
■ PaddleClas 飛槳圖像識別套件
1.1.2 OpenVINO
OpenVINO 工具套件是英特爾基于自身現有的硬件平臺開發的一種可以加快高性能計算機視覺和深度學習視覺應用開發速度工具套件,支持各種英特爾平臺的硬件加速器上進行深度學習,并且允許直接異構執行。支持在Windows與Linux系統,官方支持編程語言為Python與C++語言,但不直接支持C#。
OpenVINO 工具套件2022.1版于2022年3月22日正式發布,根據官宣《OpenVINO 迎來迄今為止最重大更新,2022.1新特性搶先看》,OpenVINO 2022.1將是迄今為止最大變化的版本。從開發者的角度來看,對于提升開發效率或運行效率有用的特性有:
■ 提供預處理API函數
■ ONNX前端API
■ AUTO設備插件
■ 支持直接讀入飛槳模型
該項目開發環境為OpenVINO 2022.1最新版本,因此使用者需在使用時將自己電腦上的OpenVINO 版本升級到2022.1版,不然會有較多的問題。
1.1.3 項目方案
該項目主要通過調用dll文件方式實現。通過C++調用OpenVINO ,編寫模型推理接口,將我們所用到的推理方法在C++中實現,并將其生成dll文件,在C#調用dll文件,重寫dll文件接口,并重新組建Core類,用于在C#中進行模型的推理,其方案如圖1- 1所示。
圖1- 1 項目解決方案
1.1.4 安裝方式
該項目所有文件已經上傳到Github和Gitee遠程代碼倉,大家可以通過Gi在本地進行克隆。
● 系統平臺:
Windows
● 軟件要求:
Visual Studio 2022 / 2019 / 2017
OpenCV 4.5.5
OpenVINO 2022.1
● 安裝方式
在Github上克隆下載:
git clone https://github.com/guojin-yan/OpenVinoSharp.git
在Gitee上克隆下載:
git clone https://gitee.com/guojin-yan/OpenVinoSharp.git
1.2 軟件安裝
1.2.1 Microsoft Visual Studio 2022安裝
Microsoft Visual Studio(簡稱VS)是美國微軟公司的開發工具套件系列產品。VS是一個基本完整的開發工具集,它包括了整個軟件生命周期中所需要的大部分工具,如UML工具、代碼管控工具、集成開發環境(IDE)等等。其支持C、C++、C#、F#、J#等多門編程語言。
本次項目所使用的編程語言為C++與C#兩門編程語言,在VS中完全可以實現,可選擇安裝版本VS2017、VS2019或VS2022版本。對于VS不同版本的選擇,該項目不做較多要求,就筆者使用來說,VS2017版本推出時間較久,不建議使用,其一些編程語言規范有一些變動,對于該項目所提供的范例可能會有部分不兼容;VS2019和VS2022版本相對更新,是由起來差異不大,建議選擇這兩個版本,并且新版OpenVINO 支持VS2022版本Cmake。
筆者電腦安裝的為Microsoft Visual Studio Community 2022 版本,其安裝包可由VS官網直接下載,下載時選擇社區版,按照一般安裝步驟進行安裝即可。在安裝中,工作負荷的選擇圖1- 2所示。
圖1- 2 Visual Studio 2022安裝負荷
安裝完成后,可以參照網上相關教程,進行學習VS的使用。
1.2.2 OpenVINO 安裝
該項目所使用的OpenVINO 版本為2022.1版本,是Intel公司在2022年第一季度發布的最新版本。該版本基于之前版本有了較大變動,不在默認包含OpenCV工具;其次,對代碼做了更進一步的優化,使得代碼在使用時更加靈活。其具體安裝方式,參考
https://www.intel.com/content/www/us/en/developer/tools/openvino-toolkit/download.html
1.2.3 OpenCV安裝
由于最新版的OpenVINO 2022.1 版本不在默認附帶OpenCV工具,所以我們需要額外安裝OpenCV工具。
01
下載并安裝OpenCV
訪問OpenCV
圖1- 3 OpenCV-4.5.5 版本頁面
根據負載使用情況,選擇Windows版本,如圖1- 3所示,跳轉頁面后,下載文件名為:opencv-4.5.5-vc14_vc15.exe。下載完成后,直接雙擊打開安裝文件,安裝完成后,打開安裝文件夾,該文件夾下 build、sources文件夾以及LICENSE相關文件,我們所使用的文件在build文件夾中。
02
配置Path環境變量
右擊我的電腦,進入屬性設置,選擇高級系統設置進入系統屬性,點擊環境變量,進入到環境變量設置,編輯系統變量下的Path變量,增加以下地址變量:
E:OpenCV Sourceopencv-4.5.5uildx64vc15in
E:OpenCV Sourceopencv-4.5.5uildx64vc15lib
E:OpenCV Sourceopencv-4.5.5uildinclude
E:OpenCV Sourceopencv-4.5.5uildincludeopencv2
其中
1.3 OpenVINO 推理模型
與測試數據集
1.3.1 模型種類與下載方式
為了測試該項目,我們提供并整合了訓練好的 Paddlepaddle 模型,主要針對 PaddleClas 以及 PaddleDetection 現有的模型,提供了 PaddleClas 下的花卉分類模型以及 PaddleDetection 中的 Vehicle Detection 模型,并針對該模型,提供了pdmodel、onnx以及IR格式。
該項目所使用的測試模型以及數據集,均可以在本文下的gitee上下載,下載鏈接為:
https://gitee.com/guojin-yan/OpenVinoSharp
1.3.2 PaddleDetection 模型
PaddleDetection 為飛槳 PaddlePaddle 的端到端目標檢測套件,提供多種主流目標檢測、實例分割、跟蹤、關鍵點檢測算法,配置化的網絡模塊組件、數據增強策略、損失函數等。該項目在此處主要使用的為PaddleDetection應用中的目標檢測功能,使用的網絡為YOLOv3網絡,表1- 1 給出了YOLOv3網絡輸出與輸入的相關信息。
表1- 1 YOLOv3模型輸入與輸出節點信息
注:None表示batch維度,H、W 分別為圖片的高和寬,Num表示識別結果的數量。
本次測試使用的為 PaddleDetection 中 Vehicle Detection 模型,我們可以在 PaddleDetection gitee 上下載。該模型輸入圖片要求為3×608×608大小,輸出為預測框信息,其信息組成為[class_id, score, x1, y1, x2, y2],分別代表分類編號、分類得分以及預測框對角頂點坐標。
1.3.3 PaddleClas 模型
飛槳圖像識別套件 PaddleClas 是飛槳為工業界和學術界提供的的一個圖像識別任務的工具集,該模型經過數據集訓練,可以識別多種物品。在該項目中,我們使用flower數據集,使用ResNet50網絡訓練識別102種花卉,關于該模型的輸入與輸出節點信息如所示
表1- 2 ResNet50模型輸入與輸出節點信息
注:None表示batch維度,H、W 分別為圖片的高和寬,Class表示分類數量。
花卉訓練模型要求圖片輸入為3×224×224大小,輸出結果為102中預測結果概率。
1.4 創建OpenVINO 方法
C++動態鏈接庫
1.4.1 新建解決方案以及項目文件
打開vs2022,首先新建一個C++空項目文件,并將同時新建一個解決方案命名為:OpenVinoSharp,用于存放后續其他項目文件。將C++項目命名為:CppOpenVinoAPI。
進入項目后,右擊源文件,選擇添加→新建項→C++文件(cpp),進行的文件的添加。具體操作如圖1- 4所示。
圖1- 4 新建項目解決方案及C++項目
本次我們需要添加OpenVinoAPIcpp以及Source.def兩個文件,如圖1- 5所示。
圖1- 5 CppOpenVinoAPIl函數方法所需文件
1.4.2 配置C++項目屬性
右擊項目,點擊屬性,進入到屬性設置,此處需要設置項目的配置類型包含目錄、庫目錄以及附加依賴項,本次項目選擇Release模式下運行,因此以Release情況進行配置。
01
(1)設置配置與平臺
進入屬性設置后,在最上面,將配置改為Release,平臺改為x64。具體操作如圖1- 6所示。
圖1- 6 C++項目屬性配置與平臺設置
02
設置常規屬性
常規設置下,點擊輸出目錄,將輸出位置設置為,即將生成文件放置在項目文件夾下的dll文件夾下;其次將目標文件名修改為:OpenVinoSharp;最后將配置類型改為:動態庫(.dll),讓其生成dll文件。具體操作如圖1- 7所示。
圖1- 7 C++項目常規屬性設置
03
設置包含目錄
點擊VC++目錄,然后點擊包含目錄,進行編輯,在彈出的新頁面中,添加以下路徑:
E:OpenCV Sourceopencv-4.5.5uildinclude
E:OpenCV Sourceopencv-4.5.5uildincludeopencv2
C:Program Files (x86)Intelopenvino_2022.1.0.643 untimeinclude
C:Program Files (x86)Intelopenvino_2022.1.0.643 untimeincludeie
其中路徑
圖1- 8 C++項目屬性庫目錄設置
04
設置庫目錄
同樣的方式,在VC++目錄下,點擊下方的庫目錄,點擊編輯,在彈出來的頁面中增加以下路徑:
E:OpenCV Sourceopencv-4.5.5uildx64vc15lib
C:Program Files (x86)Intelopenvino_2022.1.0.643 untimelibintel64Release
如果時配置Debug模式,則需要將Release文件路徑改為Debug即可。
05
設置附加依賴項
點擊展開鏈接器,點擊輸入,在附加依賴項中點擊編輯,在彈出來的新的頁面,添加以下文件名:
opencv_world455.lib
openvino.lib
具體操作步驟參考如圖1-9所示。新版OpenCV與OpenVINO 都將依賴庫文件合成到了一個文件中,這極大地簡化了使用,如果使用老版本的,需要將所有的.lib文件放置在此處即可。
圖1- 9 C++項目屬性附加依賴項設置
1.4.3 編寫C++代碼
01
推理引擎結構體
Core是OpenVINO 工具套件里的推理核心類,該類下包含多個方法,可用于創建推理中所使用的其他類。在此處,需要在各個方法中傳遞的僅僅是所使用的幾個變量,因此選擇構建一個推理引擎結構體,用于存放各個變量。
// @brief 推理核心結構體
typedef struct openvino_core {
ov::Core core; // core對象
std::shared_ptr model_ptr; // 讀取模型指針
ov::CompiledModel compiled_model; // 模型加載到設備對象
ov::InferRequest infer_request; // 推理請求對象
} CoreStruct;
其中Core是OpenVINO 工具套件里的推理機核心,該模塊只需要初始化;shared_ptr
02
接口方法規劃
經典的OpenVINO 進行模型推理,一般需要八個步驟,主要是:初始化Core對象、讀取本地推理模型、配置模型輸入&輸出、載入模型到執行硬件、創建推理請求、準備輸入數據、執行推理計算以及處理推理計算結果。我們根據原有的八個步驟,對步驟進行重新整合,并根據推理步驟,調整方法接口。
對于方法接口,主要設置為:推理初始化、配置輸入數據形狀、配置輸入數據、模型推理、讀取推理結果數據以及刪除內存地址六個大類,其中配置輸入數據形狀要細分為配置圖片數據形狀以及普通數據形狀,配置輸入數據要細分為配置圖片輸入數據與配置普通數據輸入,讀取推理結果數據細分為讀取float數據和int數據,因此,總共有6類方法接口,9個方法接口。
03
初始化推理模型
OpenVINO 推理引擎結構體是聯系各個方法的橋梁,后續所有操作都是在推理引擎結構體中的變量上操作的,為了實現數據在各個方法之間的傳輸,因此在創建推理引擎結構體時,采用的是創建結構體指針,并將創建的結構體地址作為函數返回值返回。推理初始化接口主要整合了原有推理的初始化Core對象、讀取本地推理模型、載入模型到執行硬件和創建推理請求步驟,并將這些步驟所創建的變量放在推理引擎結構體中。
初始化推理模型接口方法為:
extern "C" __declspec(dllexport) void* __stdcall core_init(const wchar_t* model_file_wchar, const wchar_t* device_name_wchar);
該方法返回值為CoreStruct結構體指針,其中model_file_wchar為推理模型本地地址字符串指針,device_name_wchar為模型運行設備名指針,在后面使用上述變量時,需要將其轉換為string字符串,利用wchar_to_string()方法可以實現將其轉換為字符串格式:
std::string model_file_path = wchar_to_string(model_file_wchar);
std::string device_name = wchar_to_string(device_name_wchar);
模型初始化功能主要包括:初始化推理引擎結構體和對結構體里面定義的其他變量進行賦值操作,其主要是利用InferEngineStruct中創建的Core類中的方法,對各個變量進行初始化操作:
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(); // 創建推理請求
04
配置輸入數據形狀
在新版OpenVINO 2022.1 中,新增加了對Paddlepaddle 模型以及onnx模型的支持,Paddlepaddle 模型不支持指定指定默認bath通道數量,因此需要在模型使用時指定其輸入;其次,對于onnx模型,也可以在轉化時不指定固定形狀,因此在配置輸入數據前,需要配置輸入節點數據形狀。其方法接口為:
extern "C" __declspec(dllexport) void* __stdcall set_input_image_sharp(void* core_ptr, const wchar_t* input_node_name_wchar, size_t * input_size);
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結構體指針,但該指針所對應的數據中已經包含了對輸入形狀的設置。第一個輸入參數core_ptr是CoreStruct指針,在當前方法中,我們要讀取該指針,并將其轉換為CoreStruct類型:
CoreStruct* p = (CoreStruct*)core_ptr;
input_node_name_wchar 為待設置網絡節點名,input_size 為形狀數據數組,對圖片數據,需要設置 [batch, dim, height, width] 四個維度大小,所以input_size數組傳入4個數據,其設置在形狀主要使用Tensor類下的set_shape()方法:
std::string input_node_name = wchar_to_string(input_node_name_wchar); // 將節點名轉為string類型
ov::Tensor input_image_tensor = p->infer_request.get_tensor(input_node_name); // 讀取指定節點Tensor
input_image_tensor.set_shape({ input_size[0],input_size[1],input_size[2],input_size[3] }); // 設置節點數據形狀
05
配置輸入數據
在新版OpenVINO 中,Tensor類的T* data()方法,其返回值為當前節點Tensor的數據內存地址,通過填充Tensor的數據內存,實現推理數據的輸入。對于圖片數據,其最終也是將其轉為一維數據進行輸入,不過為方便使用,此處提供了配置圖片數據和普通數據的接口,對于輸入為圖片的方法接口:
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);
該方法返回值是CoreStruct結構體指針,但該指針所對應的數據中已經包含了加載的圖片數據。第一個輸入參數core_ptr是CoreStruct指針,在當前方法中,我們要讀取該指針,并將其轉換為CoreStruct類型;第二個輸入參數input_node_name_wchar為待填充節點名,先將其轉為string字符串:
std::string input_node_name = wchar_to_string(input_node_name_wchar);
在該項目中,我們主要使用的是以圖片作為模型輸入的推理網絡,模型主要的輸入為圖片的輸入。其圖片數據主要存儲在矩陣image_data和矩陣長度image_size兩個變量中。需要對圖片數據進行整合處理,利用創建的data_to_mat () 方法,將圖片數據讀取到OpenCV中:
cv::Mat input_image = data_to_mat(image_data, image_size);
接下來就是配置網絡圖片數據輸入,對于節點輸入是圖片數據的網絡節點,其配置網絡輸入主要分為以下幾步:
首先,獲取網絡輸入圖片大小。
使用InferRequest類中的get_tensor ()方法,獲取指定網絡節點的Tensor,其節點要求輸入大小在Shape容器中,通過獲取該容器,得到圖片的長寬信息:
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
其次,按照輸入要求,處理輸入圖片。
在這一步,我們除了要按照輸入大小對圖片進行放縮之外,還要根據 PaddlePaddle 對模型輸入的要求進行處理。因此處理圖片其主要分為交換RGB通道、放縮圖片以及對圖片進行歸一化處理。在此處我們借助OpenCV來實現。
OpenCV讀取圖片數據并將其放在Mat類中,其讀取的圖片數據是BGR通道格式,PaddlePaddle 要求輸入格式為RGB通道格式,其通道轉換主要靠一下方式實現:
cv::cvtColor(input_image, blob_image, cv::COLOR_BGR2RGB);
接下來就是根據網絡輸入要求,對圖片進行壓縮處理:
cv::resize(blob_image, blob_image, cv::Size(input_H, input_W), 0, 0, cv::INTER_LINEAR);
最后就是對圖片進行歸一化處理,其主要處理步驟就是減去圖像數值均值,并除以方差。查詢PaddlePaddle模型對圖片的處理,其均值mean = [0.485, 0.456, 0.406],方差std = [0.229, 0.224, 0.225],利用OpenCV中現有函數,對數據進行歸一化處理:
std::vectormean_values{ 0.485 * 255, 0.456 * 255, 0.406 * 255 };
std::vectorstd_values{ 0.229 * 255, 0.224 * 255, 0.225 * 255 };
std::vectorrgb_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); // 合并圖片數據通道
最后,將圖片數據輸入到模型中。
在此處,我們重寫了網絡賦值方法,并將其封裝到 fill_tensor_data_image(ov::Tensor& input_tensor, const cv::Mat& input_image)方法中,input_tensor為模型輸入節點Tensor類,input_image為處理過的圖片Mat數據。因此節點賦值只需要調用該方法即可:
fill_tensor_data_image(input_image_tensor, blob_image);
對于普通數據的輸入,其方法接口如下:
extern "C" __declspec(dllexport) void* __stdcall load_input_data(void* core_ptr, const wchar_t* input_node_name_wchar, float* input_data);
與配置圖片數據不同點,在于輸入數據只需要輸入input_data數組即可。其數據處理哦在外部實現,只需要將處理后的數據填充到輸入節點的數據內存中即可,通過調用自定義的fill_tensor_data_float(ov::Tensor& input_tensor, float* input_data, int data_size) 方法即可實現:
std::string input_node_name = wchar_to_string(input_node_name_wchar);
ov::Tensor input_image_tensor = p->infer_request.get_tensor(input_node_name); // 讀取指定節點tensor
int input_size = input_image_tensor.get_shape()[1]; //獲得輸入節點的長度
fill_tensor_data_float(input_image_tensor,input_data, input_size); // 將數據填充到tensor數據內存上
06
模型推理
上一步中我們將推理內容的數據輸入到了網絡中,在這一步中,我們需要進行數據推理,這一步中我們留有一個推理接口:
extern "C" __declspec(dllexport) void* __stdcall core_infer(void* core_ptr)
進行模型推理,只需要調用CoreStruct結構體中的infer_request對象中的infer()方法即可:
CoreStruct* p = (CoreStruct*)core_ptr;
p->infer_request.infer();
07
讀取推理數據
上一步我們對數據進行了推理,這一步就需要查詢上一步推理的結果。對于我們所使用的模型輸出,主要有float數據和int數據,對此,留有了兩種數據的查詢接口,其方法為:
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);
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);
其中data_size為讀取數據長度,infer_result 為輸出數組指針。讀取推理結果數據與加載推理數據方式相似,依舊是讀取輸出節點處數據內存的地址:
const ov::Tensor& output_tensor = p->infer_request.get_tensor(output_node_name);
const float* results = output_tensor.data();
針對讀取整形數據,其方法一樣,只是在轉換類型時,需要將其轉換為整形數據即可。我們讀取的初始數據為二進制數據,因此要根據指定類型轉換,否則數據會出現錯誤。將數據讀取出來后,將其放在數據結果指針中,并將所有結果賦值到輸出數組中:
for (int i = 0; i < data_size; i++) {
*inference_result = results[i];
inference_result++;
}
08
刪除推理核心結構體指針
推理完成后,我們需要將在內存中創建的推理核心結構地址刪除,防止造成內存泄露,影響電腦性能,其接口該方法為:
extern "C" __declspec(dllexport) void __stdcall core_delet(void* core_ptr);
在該方法中,我們只需要調用delete命令,將結構體指針刪除即可。
1.4.4 編寫模塊定義文件
我們在定義接口方法時,在原有方法的基礎上,增加了extern "C" 、 __declspec(dllexport) 以及__stdcall 三個標識,其主要原因是為了讓編譯器識別我們的輸出方法。其中,extern ?C“是指示編譯器這部分代碼按C語言(而不是C++)的方式進行編譯;__declspec(dllexport)用于聲明導出函數、類、對象等供外面調用;__stdcall是一種函數調用約定。通過上面三個標識,我們在C++種所寫的接口方法,會在dll文件中暴露出來,并且可以實現在C#中的調用。
不過上面所說內容,我們在編輯器中可以通過模塊定義文件(.def)所實現,在模塊定義文件中,添加以下代碼:
LIBRARY
"OpenVinoSharp"
EXPORTS
core_init
set_input_image_sharp
set_input_data_sharp
load_image_input_data
load_input_data
core_infer
read_infer_result_F32
read_infer_result_I32
core_delet
LIBRARY后所跟的為輸出文件名,EXPORTS后所跟的為輸出方法名。僅需要以上語句便可以替代extern "C" 、 __declspec(dllexport) 以及__stdcall的使用。
1.4.5 生成dll文件
前面我們將項目配置輸出設置為了生成dll文件,因此該項目不是可以執行的exe文件,只能生成不能運行。右鍵項目,選擇重新生成/生成。在沒有錯誤的情況下,會看到項目成功的提示。可以看到dll文件在解決方案同級目錄下x64Release文件夾下。
使用dll文件查看器打開dll文件,如圖1- 10所示;可以看到,我們創建的四個方法接口已經暴露在dll文件中。
圖1- 10 dll文件方法輸出目錄
1.5 C#構建Core類
1.5.1 新建C#類庫
右擊解決方案,添加->新建項目,選擇添加C#類庫,項目名命名為OpenVinoSharp,項目框架根據電腦中的框架選擇,此處使用的是.NET 5.0。新建完成后,然后右擊項目,選擇添加->新建項,選擇類文件,添加Core.cs和NativeMethods.cs兩個類文件。
1.5.2 引入dll文件中的方法
在NativeMethods.cs文件下,我們通過[DllImport()]方法,將dll文件中所有的方法讀取到C#中。讀取方式如下:
[DllImport(openvino_dll_path, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public extern static IntPtr core_init(string model_file, string device_name);
其中openvino_dll_path為dll文件路徑,CharSet = CharSet.Unicode代表支持中文編碼格式字符串,CallingConvention = CallingConvention.Cdecl指示入口點的調用約定為調用方清理堆棧。
上述所列出的為初始化推理模型,dlii文件接口在匹配時,是通過方法名字匹配的,因此,方法名要保證與dll文件中一致。其次就是方法的參數類型要進行對應,在上述方法中,函數的返回值在C++中為void* ,在C#中對應的為IntPtr類型,輸入參數中,在C++中為wchar_t* 字符指針,在C#中對應的為string字符串。通過方法名與參數類型一一對應,在C#可以實現對方法的調用。其他方法的引用類似,在此處不在一一贅述,具體可以參照項目提供的源代碼。
1.5.3 創建Core類
為了更方便地調用我們通過dll引入的OpenVINO 方法,減少使用時的函數方法接口,我們在C#中重新組建我們自己的推理類,命名為Class Core,其主要成員變量和方法如圖1- 11所示。
圖1- 11 Core類圖
在Core.類中,我們只需要創建一個地址變量,作為Core類的成員變量,用于接收接口函數返回的推理核心指針,該成員變量我們只需要在當前類下訪問,因此將其設置為私有變量:
private IntPtr ptr = new IntPtr();
接下來,構建類的構造函數,在類的初始化時,我們需要輸入模型地址以及設備類型,通過掉用dll文件中引入的方法,獲取初始化指針,對成員變量進行賦值,實現類的初始化:
public Core(string model_file, string device_name) {
ptr = NativeMethods.core_init(model_file, device_name);
}
然后構其中的方法,在構建設置數據輸入形狀時,我們需要提供的為節點名以及形狀數據,為了簡化該方法,我們合并了圖片形狀設置與普通數據形狀設置接口,通過判斷輸入數組的長度,來確定是對那一個形狀的設置:
public void set_input_sharp(string input_node_name, ulong[] input_size);
對于該類中方法的構建,可以參考源碼文件,在此處不做詳述。并且其他方法構建方式基本相似,此處不在一一贅述,具體可以參考源碼文檔。
1.5.4 編譯Core類庫
右擊項目,點擊生成/重新生成,出現如下圖1- 12所示,表示編譯成功。
圖1- 12 Core類編譯輸出
1.6 C#實現OpenVINO
方法的調用
1.6.1 新建C#項目
右擊解決方案,添加->新建項目,選擇添加C#控制臺項目,項目框架根據電腦中的框架選擇,此處使用的是.NET 5.0。
圖1- 13 C#項目設置
1.6.2 添加OpenCVsharp
右擊項目,選擇管理NuGet程序包,在新頁面中選擇瀏覽,在搜索框中輸入opencvsharp3,在搜索結果中,找到OpenCvSharp3-AnyCPU,然后右側點擊安裝,具體操作步驟如圖1- 14所示。
圖1- 14 NuGet程序包安裝
1.6.3添加項目引用
上一步中我們將dll文件中的方法引入到C#中,并組建了Core類,在這一步中,我們主要通過調用Core類,進行Al模型的部署,所以需要引入上一步的項目。
右擊當前項目,選擇添加,選擇項目引用,在出現的窗體中,選擇上一步中創建的項目OpenVinoSharp,點擊確定;然后在當前項目下,添加using OpenVinoSharp命名空間。具體操作如圖1- 15所示。
圖1- 15 添加項目引用
1.6.4 編寫代碼測試花卉分類模型
在該項目中,我們提供了兩種推理模型,此處我們以花卉分類模型為例,簡介如何通過C#調用OpenVINO 進行Al模型的部署。
01
引入相關變量
string device_name = "CPU";
string model_file = "E:/Text_Model/flowerclas/flower_rec.onnx";
string image_file = "E:/Text_dataset/flowers102/jpg/image_00001.jpg";
string input_node_name = "x";
string output_node_name = "softmax_1.tmp_0";
為了讓大家更加清晰的看懂后續代碼,在此處對引入的相關變量進行解釋:
device_name:設備類型名稱,可為CPU、GPU以及AUTO(均可);
model_file:模型地址,可以為onnx、pdmodel或者xml格式;
image_file:測試圖片地址;
input_node_name:輸入模型節點名,當多輸入時,可以為數組;
output_node_name:輸出模型節點名,當多輸出時,可以為數組。
02
初始化Core類
在此處我們直接調用Core類的構造函數,進行初始化:
Core ie = new Core(model_file_paddle, device_name);
03
配置模型輸入
花卉分類模型輸入只有一個,即待分類花卉圖片。如果我們調用的模型未指定輸入大小,需要在輸入數據前,調用模型輸入數據形狀設置方法,設置節點輸入數據形狀。圖片數據為三維數組,再加一個batchsize,最終為四維數據,將形狀數據放在數組中,調用set_input_sharp()方法:
ulong[] image_sharp = new ulong[] { 1, 3, 224, 224 };
ie.set_input_sharp(input_node_name, image_sharp);
對于圖片數據,需要將其轉為轉為矩陣數據,在此處,我們可以直接使用opencvsharp中的編解碼方法,將圖片數據放置在byte數組中:
Mat image = new Mat(image_file);
byte[] image_data = new byte[2048 * 2048 * 3];
ulong image_size = new ulong();
image_data = image.ImEncode(".bmp");
image_size = Convert.ToUInt64(image_data.Length);
就最后調用Core類中的load _input_data()方法,將數據加載到推理網絡中:
ie.load_input_data(input_node_name, image_data, image_size);
在配置完輸入數據后,調用模型推理方法,對輸入數據進行推理:
ie.infer();
接下來就是讀取推理結果,對于模型的推理結果輸出一般為數組數據,可以通過調用Core類中讀取推理數據結果的方法,對與花卉分類模型的輸出,其結果為長度為102的浮點型數據,所以直接調用read_inference_result
float[] result = new float[102];
result = ie.read_infer_result(output_node_name, 102);
在讀取推理數據時,我們一定要根據模型的書名讀取正確的結果數據,因為如果超出實際輸出長度,其結果數據會摻雜其他干擾數據。
最后一步就是處理輸出數據。對于不同的推理模型,其結果處理方式是不同的,對于花卉分類模型,其輸出為102種分類情況打分,因此,在處理數據時,需要找出得分最高的哪一類即可。在此處,我們提供了一個方法,該方法可以實現提取數組中前N個max數據的位置,通過調用該方法,我們可以獲取分類結果中分數最高的幾個結果,并將結果打印輸出:
int[] index = find_array_max(result,5);
for (int i = 0; i < 5; i++){
Console.WriteLine("the index is {0} , the score is {1} ", index[i], result[index[i]]);
}
最終輸出結果如圖1- 16所示,該頁面打印出來了推理結果預測分數最大的前五個分數和其對應的索引值,最后可以通過索引值查詢flowers102_label_list.txt文件中對應的花卉名稱。
圖1- 16 花卉分類結果
在程序最后,我們該需要將前面在內存上創建推理引擎結構體進行刪除,只需要調用Core類下的delet()即可。
1.6.5 編寫代碼測試車輛識別模型
對于車輛識別模型此處不再進行詳細講解,具體實現可以參考源碼文件,此處只對一些不同點進行分析。
在配置輸入時,除了需要配置圖片數據輸入,還需要配置圖片長寬數據以及長寬縮放比例數據,在配置時,只需要將數據放置在數組中,通過調用load_input_data()方法實現,對于設置縮放比例數據輸入,如下所示:
float scale_h = 608.00f / image.Height;
float scale_w = 608.00f / image.Width;
float[] scale_factor = new float[] { scale_h, scale_w };
ie.load_input_data(input_node_name[1], scale_factor);
對于該模型推理結果數據,總共有兩個節點輸出,一個是識別結果數量,一個為識別結果信息。對于識別結果數量,其數據類型為整形數據,對于單圖片輸入,只需要讀取一位即可,利用該數據,確定識別結果信息長度。識別結果信息為6列N行數據,在數據讀取時,我們將其轉化為一維數據,所以在處理數據時,以6位數據為一組,進行處理。
識別結果信息數據中,第1位為識別標簽,第2位為識別得分,第3位到第6位四個數據為位置矩形框對角點坐標,通過每6位讀取一次數據,獲取識別結果。在此處,我們提供了專門的結果處理方法,通過該方法們可以實現直接將結果繪制在原圖片上:
image = draw_image_resule(image, resule_num[0], result, lable, 0.2f);
其中image為原圖片,resule_num[0]為識別結果數量,result為識別結果數組,lable為結果標簽,0.2f為評價得分下限。通過結果處理,將識別結果標注在圖片中,并把識別結果以及得分情況打印在圖片中,最終識別結果如圖1- 17所示。
圖1- 17 車輛類型識別結果輸出
1.7 程序時間分析
為了對比C++、C#以及Python這三個平臺下調用OpenVINO 所使用的時間,我們通過測試flower_clas以及vehicle_yolov3_darknet模型運行時間進行對比,在同一臺電腦相同運行環境之下,以及對模型的處理方式在不同編程語言下盡量做到相同,在程序測試100次之后,得到結果表1- 3如所示。
表1- 3 程序運行時間
在本次檢測中,我們通過C++、C#以及Python分別調用OpenVINO 進行模型的部署與推理,通過上述表格,一方面可以看出,C#通過調用C++的dll,實現模型的部署與推理,并沒有太大的影響程序的運行速度;另一方面,C++與C#部署模型推理,在總時間上來看,運行速度是優于Python的。
測試模型運行時間所使用的測試代碼,已同步到遠程成代碼托管倉庫gitee與github中,具體在https://gitee.com/guojin-yan/OpenVinoSharp/tree/master/openvino_run_time文件夾下,使用人員可以根據自己的設備對C++、C#和Python三個平臺進行測試。
1.8 項目總結
該項目通過C++調用OpenVINO ,創建推理方法接口,并通過調用dll文件的方式,在C#中進行重新構建Core模型推理類,并測試了花卉分類模型以及車輛識別模型,在預測結果精度以及預測時間上,和C++相比,并沒有較大的差異。
該項目所提供的方法,證實了C#平臺調用OpenVINO 的可行性,為后續在C#部署OpenVINO 模型提供了一個技術途徑。本文所有源代碼參見:
https://gitee.com/guojin-yan/OpenVinoSharp
審核編輯 :李倩
-
C++
+關注
關注
22文章
2104瀏覽量
73487 -
深度學習
+關注
關注
73文章
5492瀏覽量
120975
原文標題:在C#中調用OpenVINO? 模型 | 開發者實戰
文章出處:【微信號:英特爾物聯網,微信公眾號:英特爾物聯網】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論