参考资料:
---------------------- 超长文多图预警!!! ----------------------
vs2015的安装这里就不多说了,Anaconda3、CUDA和cuDNN的安装可参考cmake编译opencv: Win10+cmake3.14.4+cuda10.0+cudnn7.6+opencv-4.0.0+contrib+vs2015。
进入msys2官网,选择msys2-x86_64-20190524.exe,或直接点击此链接: msys2-x86_64-20190524,进行下载。
接下来选择安装路径,我个人为了避免安装在系统盘,将盘符改成了D盘,如下图。然而在之后的编译过程中发现,msys64会在C盘再安装一遍,所以这里推荐使用默认,即安装在 C:\msys64 下。接着点击Next,进入下一步。
pacman -Syu
询问是否进行安装,输入y,回车:
pacman -S git
同样地,询问是否安装输入 y,然后回车:
pacman -S patch unzip grep
是否安装输入 y,回车:
软件装好以后,需要配置环境变量。
将以下路径添加到系统变量 Path 中:
D:\msys64
D:\msys64\usr\bin
注意,如果之前装在C盘,则需要进行相应修改。
如下图所示:
进入github,选择合适的bazel版本进行下载。
如果CUDA和Tensorflow的版本与本文一致,也可点此链接下载:bazel-0.20.0-windows-x86_64.exe。
github上选择下载文件如下图所示:
将下载好的 .exe 文件复制到 D:\msys64 下(路径根据个人安装情况而定),更名为 bazel.exe。
复制完成后,配置bazel环境变量。
新建三个系统变量:BAZEL_SH,BAZEL_VC,BAZEL_VS。相应的路径如以下表格所示:
变量 | 值 |
---|---|
BAZEL_SH | D:\msys64\usr\bin\bash.exe |
BAZEL_VC | D:\Program Files (x86)\Microsoft Visual Studio 14.0\VC |
BAZEL_VS | D:\Program Files (x86)\Microsoft Visual Studio 14.0 |
环境变量配置完成:
bazel安装配置完毕!
进入tensorflow-v1.13.1源码的github的页面 ,点击页面右侧绿色按钮Clone or download,然后点击Download ZIP进行下载,如下图所示。
或者也可直接点此链接:tensorflow-1.13.1 进行下载。
下载时间也许会很长,请耐心等待。
进入tensorflow-windows-build-script-master的github页面,下载tensorflow-windows-build-script-master.zip(此链接可直接下载)。
以上两个 .zip 文件下载完成后,在D盘新建一个文件夹,命名为 tensorflow-1.13.1 (也可根据个人喜好决定)。
Copy-Item …\patches\tf_exported_symbols_msvc.lds tensorflow\
防止编译时出现Copy-Item命令的问题。
位置在 build.ps1 的180行,如下图所示:
在 C:\Windows\SysWOW64\WindowsPowerShell\v1.0 目录下,右键以管理员身份运行 powershell.exe:
在 powershell 窗口中输入以下命令,转到 tensorflow-1.13.1 目录下:
cd D:\tensorflow-1.13.1
输入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
执行命令时出现 UnauthorizedAccess 错误,说明可能是 powershell 的执行策略受限,输入以下命令查看当前执行策略:
Get-ExecutionPolicy
我这里显示的是Restricted(受限的),所以需要输入以下语句来取消限制:
Set-ExecutionPolicy Unrestricted
询问是否改变执行策略,输入 y,回车。
修改好后,可以再次输入:
Get-ExecutionPolicy
查看当前执行策略是否已经取消限制。
执行策略的问题解决以后,重新执行 build.ps1 脚本文件:
.\build.ps1 -BazelBuildParameters $parameterString -BuildCppAPI -ReserveSource
如果一切正常,我们将开始编译前的配置。可参照以下两图进行配置。有些提问可以直接按回车来选择默认配置,括号中出现 default 字眼的,都可以这么做。
注意 :当问到GPU的计算能力(compute capability),即出现 [Default is: 3.5, 7.0]: 时,先不要急着按回车。我们先找到跟自己显卡对应的计算能力,再进行填写,可参考后面的方法来查看显卡的计算能力。
然后进入NVDIA官网的CUDA GPUs页面,点击 CUDA-Enabled GeForce and TITAN Products,找到自己的显卡型号对应的数值,将其填入 [Default is: 3.5, 7.0]: 后面,回车继续配置。
都配置完成后,编译正式开始。注意保持网络通畅,因为编译之前需要下载各种依赖库,网络异常会导致下载失败停止编译。编译时间很长,不要光盯着屏幕看,该吃吃该喝喝该玩玩该睡睡。
编译可能出现无法解析的外部符号的问题,以下面的错误为例:
无法解析的外部符号 “public: virtual __cdecl tensorflow::internal::LogMessage::~LogMessage(void)” (??1LogMessage@internal@tensorflow@@UEAA@XZ),该符号在函数 “public: void __cdecl tensorflow::internal::LogMessage::`vbase destructor’(void)” (??_DLogMessage@internal@tensorflow@@QEAAXXZ) 中被引用
解决方法:
用文本编辑器打开 D:\tensorflow-1.13.1\source\tensorflow\tf_exported_symbols_msvc.lds ,找到前面一个括号中带问号的内容:
??1LogMessage@internal@tensorflow@@UEAA@XZ
将其删除。如果有多个无法解析外部符号的问题,用同样的方法逐一删除即可。
确认全部清除后,重新执行以下命令配置和编译:
.\build.ps1 -BazelBuildParameters $parameterString -BuildCppAPI -ReserveSource
如果一切正常,在漫长的等待之后,将会出现以下结果:
注意:第一个红框部分为编译生成的动态库 libtensorflow_cc.so 所在位置,请记录下来,后面需要用到。
新建一个文件夹,在文件夹中创建以下三个目录:dll,lib,include。
根据编译结束时显示的动态库所在位置,找到生成的库 libtensorflow_cc.so 和 liblibtensorflow_cc.so.ifso。
接下来要做的是填满 include 目录,该步相对繁琐,最终将包含以下文件夹:
1)在include目录中新建名为 _bin 的文件夹。参考以下路径,打开 _embedded_binaries 目录,将下图红框中的文件复制到 _bin 文件夹中:
C:\Users\xxx_bazel_xxx\install\d5b1be53d8db6a1e2d160364df2e7ef6_embedded_binaries
2-1) 参考以下路径,将下图红框中的 bazel-out 文件夹复制到 include 目录下:
C:\Users\xxx_bazel_xxx\y46qiod6\execroot\org_tensorflow
2-2) 参考以下路径,将 protobuf_archive 文件夹复制到 include 下的 bazel-out\x64_windows-opt\genfiles\external 目录中:
C:\Users\xxx_bazel_xxx\y46qiod6\external
3) 将 D:\tensorflow-1.13.1\source 下的 tensorflow 和 third_party 复制到 include 目录下:
4-1) 参照以下路径,将下图红框中的 external 文件夹复制到 include 目录下:
C:\Users\xxx_bazel_xxx\y46qiod6
4-2) 参照以下路径,将 embedded_tools 文件夹下的所有文件复制到 include 下的 external\bazel_tools 目录中(如果 external 里面没有该文件夹,需新建并命名为 bazel_tools):
C:\Users\xxx_bazel_xxx\install\d5b1be53d8db6a1e2d160364df2e7ef6_embedded_binaries\embedded_tools
4-3) 将 D:\tensorflow-1.13.1\source 下的 tensorflow 复制到 include 下的 external\org_tensorflow 目录中(如果 external 里面没有该文件夹,需新建并命名为 rg_tensorflow):
至此,调用tensorflow所需的库及包含的目录准备完毕!
新建测试工程,在 .cpp 文件中写入以下代码:
#define COMPILER_MSVC
#define NOMINMAX
#define PLATFORM_WINDOWS // 指定使用tensorflow/core/platform/windows/cpu_info.h
#include "stdafx.h"
#include
#include
#include"tensorflow/core/public/session.h"
#include "tensorflow/core/platform/env.h"
#include
using namespace tensorflow;
using namespace cv;
using std::cout;
using std::endl;
int main() {
const std::string model_path = "frozen_inference_graph.pb";// tensorflow模型文件,注意不能含有中文
const std::string image_path = "image1.jpg"; // 待inference的图片grace_hopper.jpg
// 设置输入图像
cv::Mat img = cv::imread(image_path);
cv::cvtColor(img, img, cv::COLOR_BGR2RGB);
int height = img.rows;
int width = img.cols;
int depth = img.channels();
// 取图像数据,赋给tensorflow支持的Tensor变量中
tensorflow::Tensor input_tensor(DT_UINT8, TensorShape({ 1, height, width, depth }));
const uint8* source_data = img.data;
auto input_tensor_mapped = input_tensor.tensor();
for (int i = 0; i < height; i++) {
const uint8* source_row = source_data + (i * width * depth);
for (int j = 0; j < width; j++) {
const uint8* source_pixel = source_row + (j * depth);
for (int c = 0; c < depth; c++) {
const uint8* source_value = source_pixel + c;
input_tensor_mapped(0, i, j, c) = *source_value;
}
}
}
// 初始化tensorflow session
Session* session;
Status status = NewSession(SessionOptions(), &session);
if (!status.ok()) {
std::cerr << status.ToString() << endl;
return -1;
}
else {
cout << "Session created successfully" << endl;
}
// 读取二进制的模型文件到graph中
tensorflow::GraphDef graph_def;
status = ReadBinaryProto(Env::Default(), model_path, &graph_def);
if (!status.ok()) {
std::cerr << status.ToString() << endl;
return -1;
}
else {
cout << "Load graph protobuf successfully" << endl;
}
// 将graph加载到session
status = session->Create(graph_def);
if (!status.ok()) {
std::cerr << status.ToString() << endl;
return -1;
}
else {
cout << "Add graph to session successfully" << endl;
}
// 输入inputs,“ x_input”是我在模型中定义的输入数据名称
std::vector> inputs = {
{ "image_tensor:0", input_tensor },
};
// 输出outputs
std::vector outputs;
//批处理识别
double start = clock();
std::vector output_nodes;
output_nodes.push_back("num_detections");
output_nodes.push_back("detection_boxes");
output_nodes.push_back("detection_scores");
output_nodes.push_back("detection_classes");
// 运行会话,最终结果保存在outputs中
status = session->Run(inputs, { output_nodes }, {}, &outputs);
if (!status.ok()) {
std::cerr << status.ToString() << endl;
return -1;
}
else {
cout << "Run session successfully" << endl;
}
double finish = clock();
double duration = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "spend time:" << duration << endl;
cv::imshow("image", img);
cv::waitKey();
return 0;
}
先别急着编译工程,首先我们需要把环境配置好:
opencv库配置就不多赘述,这里主要说明tensorflow库的配置。
1)包含目录:
D:\libtensorflow-gpu-windows-x86_64-1.13.1-avx2cuda10cudnn76;D:\libtensorflow-gpu-windows-x86_64-1.13.1-avx2cuda10cudnn76\include;D:\libtensorflow-gpu-windows-x86_64-1.13.1-avx2cuda10cudnn76\include\external\org_tensorflow;D:\libtensorflow-gpu-windows-x86_64-1.13.1-avx2cuda10cudnn76\include\external\protobuf_archive\src;D:\libtensorflow-gpu-windows-x86_64-1.13.1-avx2cuda10cudnn76\include\external\com_google_absl;D:\libtensorflow-gpu-windows-x86_64-1.13.1-avx2cuda10cudnn76\include\external\eigen_archive;D:\libtensorflow-gpu-windows-x86_64-1.13.1-avx2cuda10cudnn76\include\bazel-out\x64_windows-opt\genfiles;
2)库目录:
D:\libtensorflow-gpu-windows-x86_64-1.13.1-avx2cuda10cudnn76\lib;
3)链接器输入附加依赖项:
tensorflow_cc.lib;
环境配置好后,将之前生成的动态库放入应用程序目录,即复制 tensorflow_cc.dll 到与工程文件 .sln 同级的 x64\Release 文件夹下,如下图所示:
接下来,从网盘下载模型文件 frozen_inference_graph.pb 和测试图片 image1.jpg(点此链接)
把它们放到与 .cpp 文件同级的目录下,如下图所示:
编译前的准备工作完成,现在我们可以编译生成项目。生成之前,注意选择 Release 和 x64,如下图红框所示:
无法解析的外部符号 “public: virtual __cdecl tensorflow::internal::LogMessage::~LogMessage(void)” (??1LogMessage@internal@tensorflow@@UEAA@XZ),该符号在函数 “public: void __cdecl tensorflow::internal::LogMessage::`vbase destructor’(void)” (??_DLogMessage@internal@tensorflow@@QEAAXXZ) 中被引用
解决方法:
用文本编辑器打开 D:\tensorflow-1.13.1\source\tensorflow\tf_exported_symbols_msvc.lds ,将前面一个括号中带问号的内容:
??1LogMessage@internal@tensorflow@@UEAA@XZ
复制到该文件的末尾,如下图所示。
如果有多个无法解析外部符号的问题,用同样的方法逐一添加。全部添加完成后保存关闭文件,然后重新编译tensorflow,也就是说从运行脚本文件开始,后面的流程要重新走一遍,想想有点心累,不过都已经走到这里了,半途而废有点说不过去呀,咬咬牙重来一遍吧。
上面的问题已经成功解决,没有意外,还会出现max问题:
解决办法如下:
双击错误提示,跳转到有max问题的文件: logging.h 和 tensor_shape.h,分别进行以下修改:
1)logging.h:
将第250行的
if (TF_PREDICT_FALSE(v2 >= std::numeric_limits::max())) { \
改为
if (TF_PREDICT_FALSE(v2 >= (std::numeric_limits::max)())) { \
修改后如下图所示:
2)tensor_shape.h:
将108-111行的
static const int64 kMaxRep16 = std::numeric_limits::max() - 1;
static const int64 kMaxRep32 = std::numeric_limits::max() - 1;
static const uint16 kUnknownRep16 = std::numeric_limits::max();
static const uint32 kUnknownRep32 = std::numeric_limits::max();
改为:
static const int64 kMaxRep16 = (std::numeric_limits::max)() - 1;
static const int64 kMaxRep32 = (std::numeric_limits::max)() - 1;
static const uint16 kUnknownRep16 = (std::numeric_limits::max)();
static const uint32 kUnknownRep32 = (std::numeric_limits::max)();
修改后如下图所示:
修改好后我们重新生成项目,如果上面的步骤都没有问题,将会出现下面的结果:
到这里还没有结束,将程序运行起来,你将看到以下结果:
红框部分说明GPU和模型调用没毛病,妥妥地成功!小腿再次抖起来小曲儿再次唱起来!
tensorflow C++库的生成和调用全部结束!
注意,这是作业,不是彩蛋。
测试程序结尾出现了以下神秘图片:
蓝狗?!
还别说,狗狗蓝色的美瞳还真是洋气,蓝色的皮鞋更显骚气。
灵魂三问:小狗为什么是蓝色?定位框在哪?识别的结果在哪?
这就需要骚年们好好的研究和琢磨了。
Happy Ending: