在调用TensorFlow的c++接口之前,首先要安装bazel、protobuf、Eigen等软件,然后下载TensorFlow源码进行编译,整体过程还是比较麻烦。
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
Eigen是一个C++端的矩阵运算库,这个库只要下载压缩包,解压到某个自己知道的路径下即可
先下载eigen的压缩包
wget http://bitbucket.org/eigen/eigen/get/3.3.4.tar.bz2
下载之后解压,重新命名为eigen3,放到某个路径下,安装就好
mkdir build
cd build
cmake ..
sudo make install
先下载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版本不能过高,否则会报错
# 先下载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环境下训练的模型。
通过上面的的过程,已经编译好了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
#include
#include
#include
#include
#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().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 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();
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;
}