OpenVINO運行Tensorflow模型

  • 2019 年 11 月 20 日
  • 筆記

請先閱讀我的上一篇文章《Visual Studio 2017 配置OpenVINO開發環境》,在VS2017中配置好OpenVINO環境。

1 模型轉換

1.1安裝模型轉換工具

打開conda控制台,創建虛擬環境vino

conda create -n vino python=3.6

創建完成後,執行activate vino。然後安裝OpenVINO模型轉換工具,具體命令如下:

> activate vino  > cd E:OpenVINOopenvino_2019.3.334deployment_toolsmodel_optimizer  > pip install -r requirements_tf.txt

1.2 模型轉換

MobileNet為例,前往https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet_v1.md下載MobileNet_v1_1.0_224模型,解壓到目錄E:model後,對mobilenet_v1_1.0_224_frozen.pb執行如下命令完成模型轉換:

python E:OpenVINOopenvino_2019.3.334deployment_toolsmodel_optimizermo_tf.py --input_model mobilenet_v1_1.0_224_frozen.pb --input_shape [1,224,224,3] --output MobilenetV1/Logits/Conv2d_1c_1x1/Conv2D --mean_values [127.5,127.5,127.5] --scale_values [127.5,127.5,127.5]

參數介紹:

--input_model :指定輸入模型路徑  --input_shape :指定模型的輸入Tensor的shape,如果不指定,則會自動從pb中讀取  --output :指定輸出節點名稱,如果不指定,會自動從圖中提取。注意,這裡由於openVINO不支援squeeze層,所以我們主動指定squeeze的上一層即:MobilenetV1/Logits/Conv2d_1c_1x1/Conv2D,獲取每一層名稱的方法:可以先不指定output,會自動導出xml,從xml中即可看到每一層名稱。  --scale_values :指定數據預處理的scale係數  --mean_values: 指定數據預處理的mean係數

除了上面參數外,還有一些其他常用的參數:

--data_type: 指定計算類型,可以選擇全浮點和半浮點,可選參數為:{FP16,FP32,half,float}

注意,scale_values參數和mean_values參數一般用於輸入Tensor預處理,更常見的就是歸一化。假設輸入Tensor名稱為in_tensor,經過預處理後,輸出Tensor為out_tensor,其計算公式如下:

out_tensor = (in_tensor-mean_values)/scale_values 

例如,需要將輸入歸一化為-1,1,則mean_values取值為127.5,127.5,127.5且scale_values取值為127.5,127.5,127.5

完成後,在E:model目錄中生成如下三個文件:

模型轉換

其中bin文件是模型參數,xml文件是網路結構,mapping文件是模型轉換前後計算節點映射關係。我們主要用binxml文件。

注意,如果轉換過程中出錯了,可以嘗試卸載Tenorflow,可能是因為Tensorflow版本問題,改為Tensorflow1.14-cpu版本,筆者這邊使用1.14-cpu版本沒有問題。

2 VS2017運行

2.1 環境配置

主要用到OpenVINO和OpenCV環境,OpenCV用於讀取圖片,OpenVINO用於運行模型。

參考我的上一篇文章【Visual Studio 2017 配置OpenVINO開發環境】配置好openVINO環境。 參考我的另一篇文章【OpenCV 3.2.0 + opencv_contrib+VS2017】配置好OpenCV環境。

注意:如果懶得配置,可以從附件中下載筆者已經搭建好的環境,可直接用VS2017打開運行

2.2 程式碼編寫

E:model拷貝到項目根目錄,輸入以下程式碼。

#include <inference_engine.hpp>  #include <iostream>  #include <string>  #include <vector>  #include <opencv2/opencv.hpp>  using namespace InferenceEngine;  using namespace std;    string inputName;  string outputName;  InferRequest inferReq;  vector<wstring> labels;  //初試化模型相關參數  void initModel(string xml,string bin,string plugin="plugins.xml") {  	try {  		Core ie(plugin);  		CNNNetReader network_reader;  		network_reader.ReadNetwork(xml);  		network_reader.ReadWeights(bin);  		network_reader.getNetwork().setBatchSize(1);  		CNNNetwork network = network_reader.getNetwork();  		InputInfo::Ptr input_info = network.getInputsInfo().begin()->second;  		inputName = network.getInputsInfo().begin()->first;    		input_info->getPreProcess().setResizeAlgorithm(RESIZE_BILINEAR);  		input_info->setLayout(Layout::NCHW);  		input_info->setPrecision(Precision::U8);    		DataPtr output_info = network.getOutputsInfo().begin()->second;  		outputName = network.getOutputsInfo().begin()->first;    		output_info->setPrecision(Precision::FP32);    		ExecutableNetwork executable_network = ie.LoadNetwork(network, "CPU");    		inferReq = executable_network.CreateInferRequest();  	}catch (const std::exception & ex) {  		std::cerr << ex.what() << std::endl;  	}  }  //Mat 轉Blob  void  matU8ToBlob(const cv::Mat& orig_image, InferenceEngine::Blob::Ptr& blob, int batchIndex=0) {  	InferenceEngine::SizeVector blobSize = blob->getTensorDesc().getDims();  	const size_t width = blobSize[3];  	const size_t height = blobSize[2];  	const size_t channels = blobSize[1];  	uint8_t* blob_data = blob->buffer().as<uint8_t*>();    	cv::Mat resized_image(orig_image);  	if (static_cast<int>(width) != orig_image.size().width ||  		static_cast<int>(height) != orig_image.size().height) {  		cv::resize(orig_image, resized_image, cv::Size(width, height));  	}    	int batchOffset = batchIndex * width * height * channels;    	for (size_t c = 0; c < channels; c++) {  		for (size_t h = 0; h < height; h++) {  			for (size_t w = 0; w < width; w++) {  				blob_data[batchOffset + c * width * height + h * width + w] =  					resized_image.at<cv::Vec3b>(h, w)[c];  			}  		}  	}  }  //讀取label  void readLabel(string labelPath)  {  	std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;  	ifstream in(labelPath.c_str());  	string line;  	if (in) { // 有該文件  		while (getline(in, line)) { // line中不包括每行的換行符  			wstring wb = conv.from_bytes(line);  			labels.push_back(wb);  		}  	} else { // 沒有該文件  		cout << "no such file:" << labelPath << endl;  	}  }  //前向計算  wstring infer(cv::Mat rgb,float& rtP) {  	Blob::Ptr imgBlob = inferReq.GetBlob(inputName);  	matU8ToBlob(rgb, imgBlob);  	inferReq.Infer();  	Blob::Ptr output = inferReq.GetBlob(outputName);  	float* logits = output->buffer().as<InferenceEngine::PrecisionTrait<InferenceEngine::Precision::FP32>::value_type*>();    	int maxIdx = 0;  	float maxP = 0;  	int nclasses = labels.size();//1001類  	float sum = 1;  	//softmax  	for (int i = 0; i < nclasses; i++) {  		logits[i] = exp(logits[i]);  		sum = sum + logits[i];  		if (logits[i] > maxP) {  			maxP = logits[i];  			maxIdx = i;  		}  	}    	rtP = maxP / sum;  	return labels[maxIdx];  }  //測試  int main()  {  	string xml = "../model/mobilenet_v1_1.0_224_frozen.xml";  	string bin = "../model/mobilenet_v1_1.0_224_frozen.bin";  	string plugin = "../model/plugins.xml";  	string label = "../model/labels.txt";  	string testImg = "../model/test.png";  	initModel(xml, bin, plugin);  	readLabel(label);    	cv::Mat test = cv::imread(testImg);  	cv::Mat rgb;  	cv::cvtColor(test,rgb, cv::COLOR_BGR2RGB);  	float p;  	wstring cls = infer(rgb, p);  	std::wcout.imbue(std::locale("chs"));  	wcout << "類別:" << cls << ",概率:" << p << endl;  }   

readLabel函數讀取label資訊,用於將模型識別出的最大概率類別對應的中文文字,測試圖片如下:

測試圖片

運行後,結果如下:

軍用飛機,0.927341

3 附件下載

可以從【附件】中下載所有相關文件,直接用VS2017打開即可,注意只能用x64模式運行,openVNO目前不支援x86。另外,如果CSDN下載沒有積分,或者是下載鏈接出錯,可直接加群:824420877,聯繫群主免費獲取程式碼。