windows下编译tensorflow2.5.0 c++库并调用

近期使用python+keras+unet网络训练了一个图像分割模型(.h5),因为最终需要在C++中使用,所以需要2步转换:

  1. 模型转换(h5 —> pb):需要的朋友可以参考一下之前写的文章。模型转换
  2. 编译tensorflow,生成C++库:本文重点说明的内容。

说明:keras没有c++相关接口,所以不能直接在c++中调用由keras.save生成的h5模型,需要将其转换为pb模型,由tensorflow c++相关接口来调用。

1、开始之前

  • 明确需要编译的tensorflow版本,这个和训练模型时使用的tensorflow版本有关,最好保持编译的版本和训练的版本一致,因为版本不一致可能导致后期使用报错。
  • 保证网络畅通,且能*,编译过程中需要下载大量的依赖库和文件(很多来自git)。网络不畅经常会导致编译过程出错,很让人崩溃呀。
  • 保证磁盘空间充足,我编译完后的文件几十个G。
  • 要有耐心,因为编译过程中可能会出现很多错误,需要自己去动手解决,不过大多数问题都可以在网上找到解决方法。

2、准备工作

1、编译环境说明:

win10 + bazel4.1.0 + msys2 + tensorflow2.5.0

最终成功编译生成tensorflow GPU版本的C++库

2、各软件下载地址及大体流程:

  • step1 安装VS2017
  • step2 安装CUDA11.0
  • step3 下载tensorflow
  • step4 下载tensorflow-windows-build-script
  • step5 安装MSYS2
  • step6 安装bazel
  • step7 使用powershell进行编译
  • step8 编译问题及解决方法
  • step9 整理生成的dll、lib、include

可通过[百度网盘]获取,提取码:0p8t,其中包括VS2017安装包+tensorflow2.5.0+tensorflow-windows-build-scrip+bazel4.1.0+msys2。

3、开始

step1 安装VS2017

相信大部分小伙伴都已经安装过VS,若还没安装,网上资源很多,可自行安装。

安装地址VS2017:其它版本也可通过此链接下载,VS和tensorflow编译过程无关,主要用于后期C++编程。

step2 安装CUDA11.0

早期安装过,具体流程没有记录,网上资源也很多,大家可根据自己显卡型号选择安装。若编译CPU版本,则可忽略此步骤。

附上CUDA下载地址,可参考:
https://developer.nvidia.com/cuda-toolkit-archive
cuDNN下载地址:
https://developer.nvidia.com/rdp/cudnn-archive
安装过程可参考:
https://www.cnblogs.com/farewell-farewell/p/14046076.html
https://blog.csdn.net/atpalain_csdn/article/details/90755764

step3 tensorflow下载

进入tensorflow下载页面,下载所需版本,具体如图所示:
windows下编译tensorflow2.5.0 c++库并调用
windows下编译tensorflow2.5.0 c++库并调用

step4 下载tensorflow-windows-build-script

进入git,下载master分支代码,下载地址:tensorflow-windows-build-script
windows下编译tensorflow2.5.0 c++库并调用
当tensorflow及tensorflow-windows-build-script两个zip均下载完成后,新建一个文件夹命名为tensorflow2.5(根据个人情况,我新建的文件夹放在F:\tensorflow_c++_tools\目录下)

  1. 将下载好的tensorflow-2.5.0.zip解压至新建文件夹下(tensorflow2.5下),并将解压的文件夹重命名为source,此处必须重命名,因为后期编译过程中涉及的路径名称为source,不重新命名会报错没有找到source文件夹。

  2. 将下载好的tensorflow-windows-build-script.zip解压,然后将其中的patches文件夹和build.ps1文件复制到F:\tensorflow_c++_tools\tensorflow2.5目录下。
    windows下编译tensorflow2.5.0 c++库并调用

  3. patches文件夹下的eigen_half.patch复制到F:\tensorflow_c++_tools\tensorflow2.5\source\third_party目录下:
    windows下编译tensorflow2.5.0 c++库并调用

  4. patches下的tf_exported_symbols_msvc.lds复制到F:\tensorflow_c++_tools\tensorflow2.5\source\tensorflow目录下:
    windows下编译tensorflow2.5.0 c++库并调用

  5. 使用文本编辑器打开build.ps1,使用“#”注释掉179行代码

Copy-Item …\patches\tf_exported_symbols_msvc.lds tensorflow\

windows下编译tensorflow2.5.0 c++库并调用

step5 安装MSYS2

进入MSYS2【官网】,选择msys2-x86_64-20210604.exe下载,具体步骤如图所示,也可在官网上进行查看,有详细的安装及更新教程:
windows下编译tensorflow2.5.0 c++库并调用
下载好后打开exe进行安装,点击 Next:

windows下编译tensorflow2.5.0 c++库并调用
选择安装路径点击Next继续安装,我的安装路径不在C盘:
windows下编译tensorflow2.5.0 c++库并调用
继续Next
windows下编译tensorflow2.5.0 c++库并调用
等待安装完成,点击Finish即可完成安装。若选择了Run MSYS2 64bit now,则会弹出一个类似于cmd的窗口;若没有弹出,则在安装路径下找到msys2.exe,点击运行即可。
windows下编译tensorflow2.5.0 c++库并调用
按照官网提示,依次输入以下命令:

pacman -Syu

提示是否安装时,输入y,回车即可。
windows下编译tensorflow2.5.0 c++库并调用

pacman -S git

此处我已经是最新版本了,输入y,回车即可。
windows下编译tensorflow2.5.0 c++库并调用

 pacman -S patch unzip grep

同上,输入y,回车。
windows下编译tensorflow2.5.0 c++库并调用
至此,MSYS2安装更新完成。接下来需要将其添加至环境变量中。

F:\tensorflow_c++_tools\msys
F:\tensorflow_c++_tools\msys\usr\bin
大家根据自己的安装路径进行添加。

右键“我的电脑(ThisPC)”,选择属性(Properties),选择关于(About),选择系统防护(System protection),选择高级(Advanced),就是以下界面。
windows下编译tensorflow2.5.0 c++库并调用
windows下编译tensorflow2.5.0 c++库并调用
windows下编译tensorflow2.5.0 c++库并调用

step6 安装bazel

进入git下载合适的bazel版本,下载地址:https://github.com/bazelbuild/bazel/tags。

大家可根据自己的tensorflow版本查看bazel对应版本,版本对应查询,此处贴出部分以供参考。
windows下编译tensorflow2.5.0 c++库并调用
windows下编译tensorflow2.5.0 c++库并调用
下载过程如下:

选择所需版本,如图所示为bazel 5.0.0,点击Download
windows下编译tensorflow2.5.0 c++库并调用
页面往下拖,会看到很多系统的安装包,选择windows下exe或者zip下载即可。
windows下编译tensorflow2.5.0 c++库并调用
将下载好的exe文件复制到F:\tensorflow_c++_tools\msys下,具体路径根据自己安装位置决定,并且将名字命名为bazel.exe
windows下编译tensorflow2.5.0 c++库并调用
然后配置bazel环境变量,新建三个系统变量:BAZEL_SH,BAZEL_VC,BAZEL_VS,各路径如图所示:

说明:该环境变量应该是为了指定编译器,tensorflow编译过程中会自动指定编译器,但是最好自己指定。我偷懒并没有设置该步骤。

环境变量 对应值
BAZEL_SH F:\tensorflow_c++_tools\msys\usr\bin\bash.exe
BAZEL_VC C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC
BAZEL_VS C:\Program Files (x86)\Microsoft Visual Studio 12.0

配置如图所示:

首先进入环境变量设置界面,参考step4相关过程,然后点击New,依次输入名称和值,点击ok即可:
windows下编译tensorflow2.5.0 c++库并调用
windows下编译tensorflow2.5.0 c++库并调用

step7 使用powershell进行编译

进入C:\Windows\SysWOW64\WindowsPowerShell\v1.0下,使用管理员运行powershell.exe
windows下编译tensorflow2.5.0 c++库并调用
将路径切换至刚新建的文件夹中下,如F:\tensorflow_c++_tools\tensorflow2.5,注意不是source文件夹:

cd F:\tensorflow_c++_tools\tensorflow2.5

然后输入bazel编译的选项:

$parameterString = "--config=opt --config=cuda --define=no_tensorflow_py_deps=true --copt=-nvcc_options=disable-warnings //tensorflow:libtensorflow_cc.so --verbose_failures"

最后输入以下命令,执行build.ps1脚本:

.\build.ps1 -BazelBuildParameters $parameterString -BuildCppAPI -ReserveSource

windows下编译tensorflow2.5.0 c++库并调用
需要编译GPU版本,在CUDA support选项输入y,需要确保已经正确安装CUDA,不然会报错。
接下来需要输入GPU算力,此处需要查看自己电脑GPU算力,方法如下:

  1. 进入目录 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0\extras\demo_suite
    windows下编译tensorflow2.5.0 c++库并调用
  2. 点击空白处,删除所有东西,然后输入’cmd’,并按下回车键 如下图所示
    windows下编译tensorflow2.5.0 c++库并调用
  3. 输入deviceQuery.exe,我的算力为6.1:
    windows下编译tensorflow2.5.0 c++库并调用
    将6.1填入对应选项中,如图所示:
    windows下编译tensorflow2.5.0 c++库并调用
    编译过程耗时很长,需要耐心等待。。。

至此,所有编译工作都已结束,不出意外的话,只要等到编译完成就ok了。但是,一般情况下会有很多错误,在此总结一下我遇到的一些问题。

当出现Build completed successfully时,说明编译已经完成。
windows下编译tensorflow2.5.0 c++库并调用

step8 编译问题及解决方法

1、执行命令时报错 UnauthorizedAccess

powershell的执行策略受限,输入命令查看当前执行策略:

Get-ExecutionPolicy

若显示为Restricted则说明为受限,需要取消限制,输入,询问是否执行时,输入y回车即可:

Set-ExecutionPolicy Unrestricted

再次输入查询命令查看是否成功取消限制,显示为Unrestricted说明解除限制成功:

Get-ExecutionPolicy

windows下编译tensorflow2.5.0 c++库并调用
2、 The term 'py' is not recognized
windows下编译tensorflow2.5.0 c++库并调用
如图所示,该错误是因为build.ps1脚本引起,打开build.ps1脚本,190行开始,对应位置修改如下:

python  -m venv venv   #将py -3 修改为python
pip                                #将pip3修改为pip
python configure.py     #将py修改为python

windows下编译tensorflow2.5.0 c++库并调用
3、 Build did NOT complete successfully (0 packages loaded)

下载依赖包失败,主要是因为网络不畅导致,此处需要*,因为我是公司内网,不上*基本跑不了,不知道外网可不可以直接跑。
windows下编译tensorflow2.5.0 c++库并调用
4、OpenSSL SSL_connect: Connection was reset in connection to github.com:443

我当时已经上了*,但是还是报这个错误,意思是连接不上443端口。
首先输入以下命令查看配置情况,检查是否有https.proxy及http.proxy项:

 git config --global -l

设置全局代理设置,根据*代理软件端口进行修改,我的是1080端口:

git config --global https.proxy 127.0.0.1:1080

已有设置情况修改代理项:

git config --global --unset http.proxy
git config --global --unset https.proxy

设置好全局代理基本就能保证网络畅通,我的设置如下:
windows下编译tensorflow2.5.0 c++库并调用
查看代理打开情况,或者通过代理软件进行查看:
windows下编译tensorflow2.5.0 c++库并调用
windows下编译tensorflow2.5.0 c++库并调用
5、其它错误说明

一些其它的错误,当时忘记记录了,比如提示bazel版本错误***.gz压缩包下载失败无法解析的外部符号其它错误等,不过这种错误可以通过错误信息找出来,很容易进行修改。刚开始我在尝试编译tf2.4.1版本时遇到某个压缩包下载失败的,结果发现那个压缩包现在已经没有了…换了其它版本的压缩包还是报错,所以果断换了tf2.5.0版本。tf2.4.1版本有个包下载不到:
windows下编译tensorflow2.5.0 c++库并调用

step9 整理生成的文件

如果编译完成,那么恭喜你,已经完成了一大步。接下来,只需要整理生成的文件,取出需要的dll、lib、include放入项目中即可。
参考了很多教程,有的需要的头文件特别多导致文件很大,我在使用的过程中,只提取了以下内容:
说明:在编译完成时,会显示当前生成的.so路径,可以根据提供的路径找到对应文件(由于当时没有截图,所以此处就不放图了)。

1、dll和lib文件:lib是否需要改名,看vs配置情况,我在此处将其改为tensorflow.lib,dll不需要改名

我的在路径为:

C:\Users\ding_bazel_ding\4vuxoxbl-1\execroot\org_tensorflow\bazel-out\x64_windows-opt\bin\tensorflow

windows下编译tensorflow2.5.0 c++库并调用
2、include文件夹中所包含的文件:

我一共往里边放了8个文件夹,其中有重复文件夹。
windows下编译tensorflow2.5.0 c++库并调用
拷贝路径说明:

  • absl:

C:\Users\ding_bazel_ding\4vuxoxbl\external\com_google_absl\absl

windows下编译tensorflow2.5.0 c++库并调用

  • Eigen:

C:\Users\ding_bazel_ding\4vuxoxbl\external\eigen_archive\Eigen

windows下编译tensorflow2.5.0 c++库并调用

  • eigen_archive:

C:\Users\ding_bazel_ding\4vuxoxbl\external\eigen_archive

windows下编译tensorflow2.5.0 c++库并调用

  • eigen3:

F:\tensorflow_c++_tools\tensorflow2.5\source\third_party

windows下编译tensorflow2.5.0 c++库并调用

  • nsync:

C:\Users\ding_bazel_ding\4vuxoxbl\external\nsync

windows下编译tensorflow2.5.0 c++库并调用

  • protobuf:为自己创建文件夹,里边分别放入 google和solaris两个文件夹

C:\Users\ding_bazel_ding\4vuxoxbl\external\com_google_protobuf\src

windows下编译tensorflow2.5.0 c++库并调用

  • tensorflow:

C:\Users\ding_bazel_ding\4vuxoxbl\execroot\org_tensorflow\bazel-out\x64_windows-opt\bin\tensorflow

windows下编译tensorflow2.5.0 c++库并调用

  • third_party:

C:\Users\ding_bazel_ding\4vuxoxbl\execroot\org_tensorflow\bazel-out\x64_windows-opt\bin

windows下编译tensorflow2.5.0 c++库并调用
所有include、lib和dll准备完毕。

4、测试

首先需要配置环境,选择Relese x64,刚开始选择Debug老是报错,不知道为啥。
(1)配置opencv路径;
(2)配置tensorflow路径;
此处重点说明tensorflow配置,opencv类似。
windows下编译tensorflow2.5.0 c++库并调用
根据自己的路径来填,我将include和lib文件统一放在了tensorflow-1.5.0文件夹下(此处文件见命名错误,应该是2.5.0,小问题),并且移入项目中,使用了相对路径。
windows下编译tensorflow2.5.0 c++库并调用
windows下编译tensorflow2.5.0 c++库并调用
windows下编译tensorflow2.5.0 c++库并调用
windows下编译tensorflow2.5.0 c++库并调用
测试代码,我使用unet训练的图像分割模型,转换为pb模型后,在C++中测试通过,由于网上现在大多数编译的为1.x版本,所以拷贝过来的代码可能会报错,有的API已经更新,需要自行修改。

测试的图像及pb文件可以通过百度网盘下载:下载链接,提取码:n0mu。

#include "stdafx.h"
#define COMPILER_MSVC
#define NOMINMAX


#include <fstream>
#include <utility>
#include <vector>
#include <Eigen/Core>
#include <Eigen/Dense>

#include "tensorflow/cc/ops/const_op.h"
#include "tensorflow/cc/ops/image_ops.h"
#include "tensorflow/cc/ops/standard_ops.h"
#include "tensorflow/core/framework/graph.pb.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/graph/default_device.h"
#include "tensorflow/core/graph/graph_def_builder.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/lib/core/stringpiece.h"
#include "tensorflow/core/lib/core/threadpool.h"
#include "tensorflow/core/lib/io/path.h"
#include "tensorflow/core/lib/strings/stringprintf.h"
#include "tensorflow/core/platform/env.h"
#include "tensorflow/core/platform/init_main.h"
#include "tensorflow/core/platform/logging.h"
#include "tensorflow/core/platform/types.h"
#include "tensorflow/core/public/session.h"
#include "tensorflow/core/util/command_line_flags.h"

#include "tensorflow/cc/client/client_session.h"
#include "tensorflow/cc/framework/gradients.h"

#include<opencv2\opencv.hpp>

using namespace cv;
using namespace std;
using namespace tensorflow;
using namespace tensorflow::ops;
using tensorflow::Flag;
using tensorflow::Tensor;
using tensorflow::Status;
using tensorflow::string;
using tensorflow::int32;

void Mat_to_Tensor(Mat img, Tensor* output_tensor, int input_rows, int input_cols)
{
	//图像进行resize处理
	resize(img, img, cv::Size(input_cols, input_rows));
	//imshow("resized image",img);

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

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

	cv::Mat tempMat(input_rows, input_cols, CV_32FC3, p);   //注意转换的图像为彩色图还是灰度图
	img.convertTo(tempMat, CV_32FC3);
}

int Tensor_to_Mat(const tensorflow::Tensor& inputTensor, cv::Mat& output)
{
	tensorflow::TensorShape inputTensorShape = inputTensor.shape();
	if (inputTensorShape.dims() != 4)
	{
		return -1;
	}

	int height = inputTensorShape.dim_size(1);
	int width = inputTensorShape.dim_size(2);
	int depth = inputTensorShape.dim_size(3);

	output = cv::Mat(height, width, CV_32FC(depth));
	auto inputTensorMapped = inputTensor.tensor<float, 4>();
	float* data = (float*)output.data;
	for (int y = 0; y < height; ++y)
	{
		float* dataRow = data + (y * width * depth);
		for (int x = 0; x < width; ++x)
		{
			float* dataPixel = dataRow + (x * depth);
			for (int c = 0; c < depth; ++c)
			{
				float* dataValue = dataPixel + c;
				*dataValue = inputTensorMapped(0, y, x, c);
			}
		}
	}
	return 0;
}

vector<tensorflow::Tensor> image_to_vec(Mat img, string model_path)
{
	clock_t start, finish;
	start = clock();
	string input_tensor_name1 = "Input:0";
	string output_tensor_name = "Identity:0";
	/*--------------------------------创建session------------------------------*/
	Session* session;
	SessionOptions opts;

	session = NewSession(opts);

	google::protobuf::MessageLite* mess = NULL;
	tensorflow::GraphDef graphdef;

	Status status_load = ReadBinaryProto(tensorflow::Env::Default(), model_path, &graphdef);
	if (!status_load.ok())
	{
		cout << "ERROR: Loading model failed..." << model_path << std::endl;
		cout << status_load.ToString() << "\n";
		exit(-1);
	}
	for (int i = 0; i < graphdef.node_size(); i++)
	{
		std::string name = graphdef.node(i).name();
		std::cout << name << std::endl;
	}

	Status status_create = session->Create(graphdef); //将模型导入会话Session中;
	if (!status_create.ok())
	{
		cout << "ERROR: Creating graph in session failed..." << status_create.ToString() << std::endl;
		exit(-1);
	}
	//cout << "<----Successfully created session and load graph.------->" << endl;

	/*---------------------载入测试图片--------------------------------*/
	if (!img.data)
	{
		cout << "loading test_image failed" << endl;
		exit(-1);
	}
	//namedWindow("picture", 0);
	//imshow("picture", img);

	if (img.empty())
	{
		cout << "can't open the image!!!!!!!" << endl;
		exit(-1);
	}

	int input_height = 512;
	int input_width = 512;
	Tensor input_tensor1(DT_FLOAT, TensorShape({ 1,input_height,input_width,3 }));

	//将Opencv的Mat格式的图片存入tensor
	Mat_to_Tensor(img, &input_tensor1, input_height, input_width);

	cout << input_tensor1.DebugString() << endl;
	cout << endl << "<-------------Running the model with test_image--------------->" << endl;

	//前向运行,输出结果一定是一个tensor的vector
	vector<tensorflow::Tensor> outputs;
	string output_node = output_tensor_name;
	//cout << "Session Running......" << endl;
	Status status_run = session->Run({ { input_tensor_name1, input_tensor1 } }, { output_node }, {}, &outputs);
	//cout << "Session complet......" << endl;
	if (!status_run.ok()) {
		cout << "ERROR: RUN failed..." << std::endl;
		cout << status_run.ToString() << "\n";
		exit(-1);
	}
	session->Close();

	Mat show_image;
	//vector<tensorflow::Tensor> outputs;
	for (int i = 0; i < outputs.size(); ++i)
	{
		cout << outputs[i].DebugString() << endl;

		Tensor_to_Mat(outputs[i], show_image);

	}
	finish = clock();

	cout << "time:" << (double)(finish - start) / CLOCKS_PER_SEC << endl;
	namedWindow("show_image", 0);
	imshow("show_image", show_image);

	waitKey();
	return outputs;
}

//读取文件夹下所有图像
vector<Mat> ReadImagesInFolderOfIntersection(const String& pattern)
{
	string model_path = "model.pb";
	vector<String> fn;
	glob(pattern, fn, false);

	vector<Mat> images;
	const auto count = fn.size(); //number of png files in images folder
	for (size_t i = 0; i < count; i++)
	{
		images.push_back(imread(fn[i]));
		cout << "-----------------------------------------------" << endl;
		cout << "第" << i + 1 << " " << fn[i] << endl;

		//const auto start = static_cast<double>(getTickCount());
		//处理
		Mat image;
		images[i].copyTo(image);
		//Mat image_copy;
		//image.copyTo(image_copy);

		vector<tensorflow::Tensor> outputs;
		outputs = image_to_vec(image, model_path);

		waitKey(1500);
	}
	return images;
}

int main(int argc, char** argv)
{
	const String pattern = "G:\\Desktop\\20210607image\\test_image\\";
	ReadImagesInFolderOfIntersection(pattern);
	getchar();
	return 1;

	getchar();
	return 0;
}

测试结果:
(1)resize图像
windows下编译tensorflow2.5.0 c++库并调用
(2)线识别结果
windows下编译tensorflow2.5.0 c++库并调用

上一篇:入手MacBook Pro Retina 15,U盘安装64位或32位Windows 7方法及步骤


下一篇:webservice的TTP 响应时发生错误,可能是由于服务终结点绑定未使用 HTTP 协议造成的,解决办法