近期使用python+keras+unet网络训练了一个图像分割模型(.h5),因为最终需要在C++中使用,所以需要2步转换:
- 模型转换(h5 —> pb):需要的朋友可以参考一下之前写的文章。模型转换
- 编译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下载页面,下载所需版本,具体如图所示:
step4 下载tensorflow-windows-build-script
进入git,下载master分支代码,下载地址:tensorflow-windows-build-script
当tensorflow及tensorflow-windows-build-script两个zip均下载完成后,新建一个文件夹命名为tensorflow2.5(根据个人情况,我新建的文件夹放在F:\tensorflow_c++_tools\
目录下)
-
将下载好的tensorflow-2.5.0.zip解压至新建文件夹下(tensorflow2.5下),并将解压的文件夹重命名为
source
,此处必须重命名,因为后期编译过程中涉及的路径名称为source,不重新命名会报错没有找到source文件夹。 -
将下载好的tensorflow-windows-build-script.zip解压,然后将其中的
patches
文件夹和build.ps1
文件复制到F:\tensorflow_c++_tools\tensorflow2.5
目录下。 -
将
patches
文件夹下的eigen_half.patch
复制到F:\tensorflow_c++_tools\tensorflow2.5\source\third_party
目录下: -
将
patches
下的tf_exported_symbols_msvc.lds
复制到F:\tensorflow_c++_tools\tensorflow2.5\source\tensorflow
目录下: -
使用文本编辑器打开
build.ps1
,使用“#”注释掉179行代码
:
Copy-Item …\patches\tf_exported_symbols_msvc.lds tensorflow\
step5 安装MSYS2
进入MSYS2【官网】,选择msys2-x86_64-20210604.exe下载,具体步骤如图所示,也可在官网上进行查看,有详细的安装及更新教程:
下载好后打开exe进行安装,点击 Next
:
选择安装路径点击Next
继续安装,我的安装路径不在C盘:
继续Next
:
等待安装完成,点击Finish
即可完成安装。若选择了Run MSYS2 64bit now,则会弹出一个类似于cmd的窗口;若没有弹出,则在安装路径下找到msys2.exe,点击运行即可。
按照官网提示,依次输入以下命令:
pacman -Syu
提示是否安装时,输入y,回车即可。
pacman -S git
此处我已经是最新版本了,输入y,回车即可。
pacman -S patch unzip grep
同上,输入y,回车。
至此,MSYS2安装更新完成。接下来需要将其添加至环境变量中。
F:\tensorflow_c++_tools\msys
F:\tensorflow_c++_tools\msys\usr\bin
大家根据自己的安装路径进行添加。
右键“我的电脑(ThisPC)”
,选择属性(Properties)
,选择关于(About)
,选择系统防护(System protection)
,选择高级(Advanced)
,就是以下界面。
step6 安装bazel
进入git下载合适的bazel版本,下载地址:https://github.com/bazelbuild/bazel/tags。
大家可根据自己的tensorflow版本查看bazel对应版本,版本对应查询,此处贴出部分以供参考。
下载过程如下:
选择所需版本,如图所示为bazel 5.0.0,点击Download
页面往下拖,会看到很多系统的安装包,选择windows下exe或者zip下载即可。
将下载好的exe文件复制到F:\tensorflow_c++_tools\msys
下,具体路径根据自己安装位置决定,并且将名字命名为bazel.exe
然后配置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即可:
step7 使用powershell进行编译
进入C:\Windows\SysWOW64\WindowsPowerShell\v1.0
下,使用管理员运行powershell.exe
:
将路径切换至刚新建的文件夹中下,如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
需要编译GPU版本,在CUDA support
选项输入y,需要确保已经正确安装CUDA,不然会报错。
接下来需要输入GPU算力,此处需要查看自己电脑GPU算力,方法如下:
- 进入目录
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0\extras\demo_suite
- 点击空白处,删除所有东西,然后输入’cmd’,并按下回车键 如下图所示
- 输入deviceQuery.exe,我的算力为6.1:
将6.1填入对应选项中,如图所示:
编译过程耗时很长,需要耐心等待。。。
至此,所有编译工作都已结束,不出意外的话,只要等到编译完成就ok了。但是,一般情况下会有很多错误,在此总结一下我遇到的一些问题。
当出现Build completed successfully
时,说明编译已经完成。
step8 编译问题及解决方法
1、执行命令时报错 UnauthorizedAccess
powershell的执行策略受限,输入命令查看当前执行策略:
Get-ExecutionPolicy
若显示为Restricted
则说明为受限,需要取消限制,输入,询问是否执行时,输入y回车即可:
Set-ExecutionPolicy Unrestricted
再次输入查询命令查看是否成功取消限制,显示为Unrestricted
说明解除限制成功:
Get-ExecutionPolicy
2、 The term 'py' is not recognized
如图所示,该错误是因为build.ps1
脚本引起,打开build.ps1脚本,190行开始,对应位置修改如下:
python -m venv venv #将py -3 修改为python
pip #将pip3修改为pip
python configure.py #将py修改为python
3、 Build did NOT complete successfully (0 packages loaded)
下载依赖包失败,主要是因为网络不畅导致,此处需要*,因为我是公司内网,不上*基本跑不了,不知道外网可不可以直接跑。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
设置好全局代理基本就能保证网络畅通,我的设置如下:
查看代理打开情况,或者通过代理软件进行查看:5、其它错误说明
一些其它的错误,当时忘记记录了,比如提示bazel版本错误
、***.gz压缩包下载失败
、无法解析的外部符号
、其它错误
等,不过这种错误可以通过错误信息找出来,很容易进行修改。刚开始我在尝试编译tf2.4.1版本时遇到某个压缩包下载失败的,结果发现那个压缩包现在已经没有了…换了其它版本的压缩包还是报错,所以果断换了tf2.5.0版本。tf2.4.1版本有个包下载不到:
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
2、include文件夹中所包含的文件:
我一共往里边放了8个文件夹,其中有重复文件夹。
拷贝路径说明:
- absl:
C:\Users\ding_bazel_ding\4vuxoxbl\external\com_google_absl\absl
- Eigen:
C:\Users\ding_bazel_ding\4vuxoxbl\external\eigen_archive\Eigen
- eigen_archive:
C:\Users\ding_bazel_ding\4vuxoxbl\external\eigen_archive
- eigen3:
F:\tensorflow_c++_tools\tensorflow2.5\source\third_party
- nsync:
C:\Users\ding_bazel_ding\4vuxoxbl\external\nsync
- protobuf:为自己创建文件夹,里边分别放入 google和solaris两个文件夹
C:\Users\ding_bazel_ding\4vuxoxbl\external\com_google_protobuf\src
- tensorflow:
C:\Users\ding_bazel_ding\4vuxoxbl\execroot\org_tensorflow\bazel-out\x64_windows-opt\bin\tensorflow
- third_party:
C:\Users\ding_bazel_ding\4vuxoxbl\execroot\org_tensorflow\bazel-out\x64_windows-opt\bin
所有include、lib和dll准备完毕。
4、测试
首先需要配置环境,选择Relese x64
,刚开始选择Debug
老是报错,不知道为啥。
(1)配置opencv路径;
(2)配置tensorflow路径;
此处重点说明tensorflow配置,opencv类似。
根据自己的路径来填,我将include和lib文件统一放在了tensorflow-1.5.0文件夹下(此处文件见命名错误,应该是2.5.0,小问题),并且移入项目中,使用了相对路径。
测试代码,我使用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图像
(2)线识别结果