要在C语言中调用训练好的TensorFlow模型,需要使用TensorFlow C API。
https://tensorflow.google.cn/install/lang_c?hl=zh-cnten
TensorFlow 提供了一个 C API,该 API 可用于为其他语言构建绑定。该 API 在 c_api.h 中定义,旨在实现简洁性和一致性,而不是便利性。

下载后解压。得到文件夹结构,lib是tensorflow的动态链接库和对应的lib文件目录。include是头文件目录
├─include
│ └─tensorflow
│ ├─c
│ │ └─eager
│ └─core
│ └─platform
└─lib
python tensorflow2.6需要把训练好的模型保存为saved_model格式
tensorflow2.6在模型训练完成后保存,默认就是saved_model格式
查看保存的模型目录
MODELPB
│ saved_model.pb
│
├─assets
└─variables
variables.data-00000-of-00001
variables.index
我们需要得到输入和输出层的名字,使用tensorflow自带的saved_model_cli工具,下面的命令三条命令最后得到了输入和输出层的名字serving_default_conv2d_input、StatefulPartitionedCall
>python saved_model_cli.py show --dir ./modelpb
The given SavedModel contains the following tag-sets:
'serve'
>python saved_model_cli.py show --dir ./modelpb --tag_set serve
The given SavedModel MetaGraphDef contains SignatureDefs with the following keys:
SignatureDef key: "__saved_model_init_op"
SignatureDef key: "serving_default"
>python saved_model_cli.py show --dir ./modelpb --tag_set serve --signature_def serving_default
The given SavedModel SignatureDef contains the following input(s):
inputs['conv2d_input'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 224, 224, 3)
name: serving_default_conv2d_input:0
The given SavedModel SignatureDef contains the following output(s):
outputs['dense_1'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 9)
name: StatefulPartitionedCall:0
Method name is: tensorflow/serving/predict
使用VS2013建立一个C++控制台项目。
添加配置tensorflow的c语言库到VS项目的三个步骤:
把tf库的include目录加入到附加包含目录
tf库的lib目录附加库目录
tensorflow.lib添加到链接项
需要用到opencv库加载图片,并且将图片数据转化为张量Tensor。opencv库的安装办法不讲了。
opencv将mat转换为tensor数据的函数
//将图片mat转换为tensor数据
void mat_2_tensor(cv::Mat img, TF_Tensor* tensor, int width, int height, int channels){
// 调整图片大小
cv::resize(img, img, cv::Size(width, height));
// 转换图片为RGB格式(Opencv图像数据是BGR格式,如果训练时使用的是RGB图像数据就需要转换)
cv::cvtColor(img, img, cv::COLOR_BGR2RGB);
// 转换为3通道浮点图像,将像素值归一化到0-1之间
//如果是PNG图像,有alpha通道(即透明度通道),转换为CV_32FC3类型后就会丢失该通道信息,图片识别只用到了RGB 3通道,因此没有影响
img.convertTo(img, CV_32FC3, 1.0 / 255);
//获取tensor数据指针
float* tensordata = (float*)TF_TensorData(tensor);
//将图像数据拷贝到tensor
memcpy(tensordata, reinterpret_cast<float*>(img.data), sizeof(float)* width * height * channels);
//下面的办法也可以
//cv::Mat imageTemp(height, width, CV_32FC1, tensordata);
//img.convertTo(imageTemp, CV_32FC1);
}
下面的代码,实现了使用c++加载并使用一个SavedModel形式的TensorFlow模型,训练的模型是使用244*244的3通道rgb图像作为输入,并且预测输出9个类别
//
#include <stdio.h>
#include <tensorflow/c/c_api.h>
#include <opencv2/opencv.hpp>
#include <fstream>
#include <cstdint>
//将图片mat转换为tensor数据
void mat_2_tensor(cv::Mat img, TF_Tensor* tensor, int width, int height, int channels){
// 调整图片大小
cv::resize(img, img, cv::Size(width, height));
// 转换图片为RGB格式(Opencv图像数据是BGR格式,如果训练时使用的是RGB图像数据就需要转换)
cv::cvtColor(img, img, cv::COLOR_BGR2RGB);
// 将像素值缩放到0-1之间
img.convertTo(img, CV_32FC3, 1.0 / 255);
//获取tensor数据指针
float* tensordata = (float*)TF_TensorData(tensor);
//将图像数据拷贝到tensor
memcpy(tensordata, reinterpret_cast<float*>(img.data), sizeof(float)* width * height * channels);
//下面的办法也可以
//cv::Mat imageTemp(height, width, CV_32FC1, tensordata);
//img.convertTo(imageTemp, CV_32FC1);
}
int main() {
//图片大小
int width = 224;
int height = 224;
//图片通道数
int channel = 3;
//分类数量
int number = 9;
// 定义数据类型
TF_Graph* graph = TF_NewGraph();
TF_Status* status = TF_NewStatus();
TF_SessionOptions* sessionOpts = TF_NewSessionOptions();
TF_Buffer* RunOpts = NULL;
//tags
const char* tags[] = { "serve" };
// 加载 SavedModel模型
TF_Session* session = TF_LoadSessionFromSavedModel(
sessionOpts, /* session options */
RunOpts, /* run options */
"D:\\modelpb", /* export directory */
tags, /* tags */
1, /* number of tags */
graph, /* graph object */
NULL,
status /* status object */
);
if (TF_GetCode(status) != TF_OK) {
std::cerr << "Failed to load model: " << TF_Message(status) << std::endl;
return -1;
}
// 加载图像
cv::Mat image = cv::imread("D:\\8a3e6ffc3490c975.jpg");
if (image.empty()) {
std::cerr << "Failed to load image" << std::endl;
return -1;
}
// 创建输入张量
//第一个维度是batch size,为1表示一次输入1张图片或者数据
//第二个和第三个维度是图片大小 第四个维度是通道数
const std::vector<std::int64_t> input_dims = { 1, height, width, channel };
//分配tensor空间
TF_Tensor* input_tensor = TF_AllocateTensor(TF_FLOAT, input_dims.data(), input_dims.size(), sizeof(float)* height * width * channel);
// 将图像数据拷贝到输入张量中
mat_2_tensor(image, input_tensor, width, height, channel);
// 创建输出张量并分配空间
// 输出层预测number个类别的图片
// 第一个维度是1 第二个维度是number 说明输出一个长度为number 的向量
const std::vector<std::int64_t> output_dims = { 1, number };
TF_Tensor* output_tensor = TF_AllocateTensor(TF_FLOAT, output_dims.data(), output_dims.size(), sizeof(float)* number );
// 设置输入张量和输出张量
const std::vector<TF_Output> inputs = { TF_Output{ TF_GraphOperationByName(graph, "serving_default_conv2d_input"), 0 } };
const std::vector<TF_Output> outputs = { TF_Output{ TF_GraphOperationByName(graph, "StatefulPartitionedCall"), 0 } };
const TF_Output* inputs_ptr = inputs.data();
TF_Tensor* const* input_tensor_ptr = &input_tensor;
const int num_inputs = inputs.size();
TF_Output* outputs_ptr = (TF_Output*)outputs.data();
TF_Tensor** output_tensor_ptr = &output_tensor;
const int num_outputs = outputs.size();
// 运行模型
TF_SessionRun(session, nullptr, inputs_ptr, input_tensor_ptr, num_inputs, outputs_ptr, output_tensor_ptr, num_outputs, nullptr, 0, nullptr, status);
if (TF_GetCode(status) != TF_OK) {
std::cerr << "Failed to run model: " << TF_Message(status) << std::endl;
return -1;
}
// 解码输出
float* output_data = reinterpret_cast<float*>(TF_TensorData(output_tensor));
//输出张量的shape为{1,number},因此输出值一共有number个。通过循环将这number个输出值输出到控制台。
for (int i = 0; i < number; i++){
std::cout << output_data[i] << '\n';
}
// 释放资源
TF_DeleteGraph(graph);
TF_DeleteSessionOptions(sessionOpts);
TF_DeleteTensor(input_tensor);
TF_DeleteTensor(output_tensor);
TF_CloseSession(session, status);
TF_DeleteSession(session, status);
TF_DeleteStatus(status);
// 暂停
std::cin.get();
return 0;
}
serving_default_conv2d_input StatefulPartitionedCall是前面使用saved_model_cli工具查看的输入输出层的名字
运行可能报错缺少tensorflow.dll文件,把tf库目录的tensorflow.dll文件加入到系统环境变量路径下,或者直接拷贝到程序所在目录就可以了
运行结果,成功输出了9个类别的预测概率
2023-11-22 02:07:29.041750: I tensorflow/cc/saved_model/reader.cc:38] Reading SavedModel from: D:\modelpb
2023-11-22 02:07:29.346574: I tensorflow/cc/saved_model/reader.cc:90] Reading meta graph with tags { serve }
2023-11-22 02:07:29.347768: I tensorflow/cc/saved_model/reader.cc:132] Reading SavedModel debug info (if present) from: D:\modelpb
2023-11-22 02:07:29.349696: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations: AVX2
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2023-11-22 02:07:30.996485: I tensorflow/cc/saved_model/loader.cc:211] Restoring SavedModel bundle.
2023-11-22 02:07:32.634922: I tensorflow/cc/saved_model/loader.cc:195] Running initialization op on SavedModel bundle at path: D:\modelpb
2023-11-22 02:07:33.901410: I tensorflow/cc/saved_model/loader.cc:283] SavedModel load for tags { serve }; Status: success: OK. Took 4859648 microseconds.
0.0193346
0.33941
0.0311138
0.114876
0.0562953
0.0596782
0.0566688
0.0208309
0.301792
至此python训练Tensorflow模型,使用c++调用模型就完成了