近期使用python+keras+unet网络训练了一个图像分割模型(.h5),因为最终需要在C++中使用,所以需要2步转换:
说明:keras没有c++相关接口,所以不能直接在c++中调用由keras.save生成的h5模型,需要将其转换为pb模型,由tensorflow c++相关接口来调用。
1、编译环境说明:
win10 + bazel4.1.0 + msys2 + tensorflow2.5.0
最终成功编译生成tensorflow GPU版本的C++库
2、各软件下载地址及大体流程:
可通过[百度网盘]获取,提取码:0p8t,其中包括VS2017安装包+tensorflow2.5.0+tensorflow-windows-build-scrip+bazel4.1.0+msys2。
相信大部分小伙伴都已经安装过VS,若还没安装,网上资源很多,可自行安装。
安装地址VS2017:其它版本也可通过此链接下载,VS和tensorflow编译过程无关,主要用于后期C++编程。
早期安装过,具体流程没有记录,网上资源也很多,大家可根据自己显卡型号选择安装。若编译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
进入tensorflow下载页面,下载所需版本,具体如图所示:
进入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\
进入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)
,就是以下界面。
进入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即可:
进入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
至此,所有编译工作都已结束,不出意外的话,只要等到编译完成就ok了。但是,一般情况下会有很多错误,在此总结一下我遇到的一些问题。
当出现Build completed successfully
时,说明编译已经完成。
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版本有个包下载不到:
如果编译完成,那么恭喜你,已经完成了一大步。接下来,只需要整理生成的文件,取出需要的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个文件夹,其中有重复文件夹。
拷贝路径说明:
C:\Users\ding_bazel_ding\4vuxoxbl\external\com_google_absl\absl
C:\Users\ding_bazel_ding\4vuxoxbl\external\eigen_archive\Eigen
C:\Users\ding_bazel_ding\4vuxoxbl\external\eigen_archive
F:\tensorflow_c++_tools\tensorflow2.5\source\third_party
C:\Users\ding_bazel_ding\4vuxoxbl\external\nsync
C:\Users\ding_bazel_ding\4vuxoxbl\external\com_google_protobuf\src
C:\Users\ding_bazel_ding\4vuxoxbl\execroot\org_tensorflow\bazel-out\x64_windows-opt\bin\tensorflow
C:\Users\ding_bazel_ding\4vuxoxbl\execroot\org_tensorflow\bazel-out\x64_windows-opt\bin
所有include、lib和dll准备完毕。
首先需要配置环境,选择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)线识别结果