tensorflow的c++接口编译以及调用图像分类模型代码

一、编译TensorFlow的c++接口

在调用TensorFlow的c++接口之前,首先要安装bazel、protobuf、Eigen等软件,然后下载TensorFlow源码进行编译,整体过程还是比较麻烦。

1、配置C++版tensorflow使用时的第三方依赖

(1)protobuf下载及安装

Protobuf这玩意儿是重中之重,它的版本与tensorflow的版本密切相关,它的版本错了就无法work。

下载地址:https://github.com/google/protobuf/releases ,我下载的是3.5.0版本,如果你是下载新版的tensorflow,请确保protobuf版本也是最新的,安装步骤:
 将下载的压缩包解压出来,获得一个protobuf-3.5.0的文件夹 
     cd prtobuf-3.5.0 
     ./configure 
     sudo make -j8 
     make check -j8 
     sudo make install 
     sudo ldconfig 
     在protobuf-3.5.0文件中执行: 
     ./configure --prefix=/home/xxx/Anaconda/protobuf-3.5.0
 以上步骤可以完成Protubuf的源码的编译和安装 
 如果遇到什么问题,建议去看Protobuf的官方的编译安装指南:https://github.com/google/protobuf/blob/master/src/README.md

(2)Eigen下载与安装

Eigen是一个C++端的矩阵运算库,这个库只要下载压缩包,解压到某个自己知道的路径下即可
	 先下载eigen的压缩包 
	 wget http://bitbucket.org/eigen/eigen/get/3.3.4.tar.bz2 
	 下载之后解压,重新命名为eigen3,放到某个路径下,安装就好 
	 mkdir build 
	 cd build 
	 cmake .. 
	 sudo make install

2、编译安装Tensorflow

(1)下载安装编译工具bazel

    先下载Bazel的安装包 https://github.com/bazelbuild/bazel/releases,我下载的是bazel-0.24.1-installer-linux-x86_64.sh 
    然后执行安装 ./bazel-0.24.1-installer-linux-x86_64.sh --user
    安装完成后,需要添加环境变量:
    sudo gedit ~/.bashrc
    在文本最后添加语句: export PATH=/home/xxx/bin:$PATH
    source ~/.bashrc
    注意:bazel版本不能过高,否则会报错

(2)Tensorflow下载与编译

     # 先下载tensorflow源码 ,我下载的是tensorflow1.14
	 git clone https://github.com/tensorflow/tensorflow.git 
	 # 进入tensorflow文件夹 
	 cd tensorflow
	 # 执行configure 
	 ./configure 
	 这一步需要你指定python路径,需要有各种y/N的选择 
	 建议如下: python路径用anaconda的路径:/home/xxx/anaconda3/bin/python 
	 pyhon路径选择默认路径就可以了
	 cuda要选择y,然后会自动搜索cudnn版本 
	 nccl选择默认的1.3, 
	 后面的不是选择N就是默认 
       利用bazel进行编译
       bazel build --config=opt //tensorflow:libtensorflow_cc.so // 无显卡,cpu版本
 	 bazel build --config=opt --config=cuda //tensorflow:libtensorflow_cc.so // 有显卡
 
	 ....漫长的等待编译,大约20分钟 
	 # 最后显示类似如下的信息,说明编译成功了:
	 .... Target //tensorflow:libtensorflow_cc.so up-to-date: 
	 bazel-bin/tensorflow/libtensorflow_cc.so 
	 INFO: Elapsed time: 1192.883s, Critical Path: 174.02s 
	 INFO: 654 processes: 654 local. 
	 INFO: Build completed successfully, 656 total actions
     然后回到tensorflow目录下执行:
	./tensorflow/contrib/makefile/download_dependencies.sh
	#完成后会有一个downloads文件夹在makefile文件夹中
	
	需要先安装相应依赖:
	sudo apt-get install autoconf automake libtool
	然后在tensorflow/contrib/makefile下执行
	bash ./build_all_linux.sh文件,成功后会出现一个gen文件夹。
        编译完成后,整理库文件和头文件
		库文件:
			mkdir -p ../tf_test/lib
			cp bazel-bin/tensorflow/libtensorflow_cc.so ../tf_test/lib/
			cp bazel-bin/tensorflow/libtensorflow_framework.so ../tf_test/lib/  # 之前编译r0.12和r1.3版本的库,只需要
			libtensorflow_cc.so,1.4版本的似乎分成了两个so文件,即还需要libtensorflow_framework.so
			cp /tmp/proto/lib/libprotobuf.a ../tf_test/lib/
		头文件:
			mkdir -p ../tf_test/include/tensorflow
			cp -r bazel-genfiles/* ../tf_test/include/
			cp -r tensorflow/cc ../tf_test/include/tensorflow
			cp -r tensorflow/core ../tf_test/include/tensorflow
			cp -r third_party ../tf_test/include
			cp -r /tmp/proto/include/* ../tf_test/include
			cp -r /tmp/eigen/include/eigen3/* ../tf_test/include
			cp -r tensorflow/contrib/makefile/downloads/nsync/public ../tf_test/include/external/nsync/public

		不需要的.cc文件可以删掉:
			cd ../tf_test/
			find . -name "*.cc" -type f -delete

至此,TensorFlow编译工作已经全部结束,接下来就是如何使用TensorFlow的c++调用Python环境下训练的模型。

二、tensorflow的c++接口调用图像分类模型

通过上面的的过程,已经编译好了c++版本的TensorFlow,接下来,将利用TensorFlow的c++接口来调用训练好的图像分类模型。图像分类网络选用的是inception_v3,其pb模型可以直接从网上下载,地址为:https://pan.baidu.com/s/11aQR4u1FF7V95pfm9YlIMw,提取码:21uc

下面的c++代码是在eclipse中进行编写运行,在运行运行之前,需要将tensorflow、opencv等动态链接库(.so)包含进去。以下是main函数,亲测可用。

/*
 * main.cpp
 *
 *  Created on: 2020年3月31日
 *      Author: xxx
 */

#include <fstream>
#include <utility>
#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>
#include "tensorflow/core/public/session.h"
#include "tensorflow/core/graph/default_device.h"
#include "tensorflow/cc/client/client_session.h"

// 定义一个函数将Opencv的Mat数据转化为tensorflow的tensor,在python里面只要对cv2.imread()读进来的矩阵进行np.reshape之后,数据类型就成了一个tensor,即tensor与矩阵一样,然后就连可以输入到网络的入口了
// 而C++版本,网络的输入也需要是tensor数据类型,因此需要将输入图片转换成一个tensor,若使用Opencv读取图片,格式是一个Mat,需要考虑怎样将一个Mat转换为tensor
void CVMat_to_Tensor(cv::Mat img,tensorflow::Tensor * output_tensor,int input_rows, int input_cols)
{
	imshow("input image",img);
	// 对输入图像进行resize处理
	resize(img,img,cv::Size(input_cols,input_rows));
	imshow("resizes image",img);

	// 归一化
	img.convertTo(img,CV_32FC1);
	img = 1 - img/255;

	//创建一个指向tensor的内容指针
	float * p = output_tensor->flat<float>().data();

	// 创建一个Mat,与tensor的指针进行绑定,改变这个Mat的值,就相当于改变tensor的值
	cv::Mat tempMat(input_rows,input_cols,CV_32FC1,p);
	img.convertTo(tempMat,CV_32FC1);

	cv::waitKey(1000);
	cv::destroyAllWindows();
}

int main(int argc, char ** argv)
{
	/* --------------------配置关键信息------------------------------------*/
	std::string model_path = "./model/inception_v3_2016_08_28_frozen.pb"; // pb模型地址
	std::string image_path = "./model/cat.jpg"; // 测试图片
	int input_height = 299; // 输入网络的图片高度
	int input_width = 299; // 输入网络的图片宽度
	std::string input_tensor_name = "input"; // 网络的输入节点的名称
	std::string output_tensor_name = "InceptionV3/Predictions/Reshape_1"; // 网络的输出节点的名称

	/* --------------------创建session------------------------------------*/
	tensorflow::Session * session;
	tensorflow::Status status = tensorflow::NewSession(tensorflow::SessionOptions(), &session); // 创建新会话Session

	/* --------------------从pb文件中读取模型------------------------------------*/
	tensorflow::GraphDef graphdef; //为当前的模型定义一张图
	tensorflow::Status status_load = tensorflow::ReadBinaryProto(tensorflow::Env::Default(),model_path,&graphdef); // 从pb文件中读取图模型
	if (!status_load.ok()) // 判断读取模型是否正确,错误的话则打印出错误的信息
	{
		std::cout << "ERROR: Loading model failed..." << model_path << std::endl;
		std::cout << status_load.ToString() << "\n";
		return -1;
	}
	tensorflow::Status status_create = session->Create(graphdef); // 将模型导入会话Session中
	if (!status_create.ok()) // 判断将模型导入会话中是否成功,错误的话打印出错误信息
	{
		std::cout << "ERROR: Creating graph in session failed..." << status_create.ToString() << std::endl;
		return -1;
	}
	std::cout << "<------Sucessfully created session and load graph------>" << std::endl;

	/* --------------------载入测试图片------------------------------------*/
	cv::Mat img = cv::imread(image_path,0); // 读取图片,读取灰度图
	if (img.empty())
	{
		std::cout << "can't open the image!!!!!" << std::endl;
		return -1;
	}
	// 创建一个tensor作为输入网络的接口
	tensorflow::Tensor resized_tensor(tensorflow::DT_FLOAT,tensorflow::TensorShape({1,input_height,input_width,3}));
	// 将opencv读取的Mat格式的图片存入tensor
	CVMat_to_Tensor(img,&resized_tensor,input_height,input_width);
	std::cout << resized_tensor.DebugString() << std::endl;

	/* --------------------用网络进行测试------------------------------------*/
	std::cout << std::endl << "<------------------Runing the model with test_image------------------->" << std::endl;
	// 前向运行,输出结果一定是一个tensor的vector
	std::vector<tensorflow::Tensor> outputs;
	std::string output_node = output_tensor_name; // 输出节点名
	tensorflow::Status status_run = session->Run({
   
   {input_tensor_name,resized_tensor}},{output_node},{},&outputs);
	if (!status_run.ok())
	{
		std::cout << "ERROR: Run failed..." << std::endl;
		std::cout << status_run.ToString() << std::endl;
		return -1;
	}

	// 把输出值提取出来
	std::cout << "Output tensor size: " << outputs.size() << std::endl;
	for (std::size_t i = 0; i < outputs.size();i++)
	{
		std::cout << outputs[i].DebugString() << std::endl;
	}
	tensorflow::Tensor t = outputs[0];
	auto tmap = t.tensor<float,2>();
	int output_dim = t.shape().dim_size(1);

	int output_class_id = -1;
	double output_prob = 0.0;
	for (int j = 0; j < output_dim; j++)
	{
		std::cout << "Class " << j << " prob: " << tmap(0,j) << "," << std::endl;
		if (tmap(0,j) >= output_prob)
		{
			output_class_id = j;
			output_prob = tmap(0,j);
		}
	}
	// 输出结果
	std::cout << "Final class id : " << output_class_id << std::endl;
	std::cout << "Final class prob : " << output_prob << std::endl;
}


猜你喜欢

转载自blog.csdn.net/qq_40716944/article/details/105255438