cpp使用Tensorflow2.6训练好的模型进行预测

⌚Time: 2023-11-22 02:25:37

👨‍💻Author: Jack Ge

要在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格式


model.fit(...)

model_pb_save_path = "./modelpb"

tf.saved_model.save(model, model_pb_save_path)

查看保存的模型目录


MODELPB

│  saved_model.pb

│

├─assets

└─variables

        variables.data-00000-of-00001

        variables.index

我们需要得到输入和输出层的名字,使用tensorflow自带的saved_model_cli工具,下面的命令三条命令最后得到了输入和输出层的名字serving_default_conv2d_inputStatefulPartitionedCall


>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项目的三个步骤:

  1. 把tf库的include目录加入到附加包含目录

  2. tf库的lib目录附加库目录

  3. 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++调用模型就完成了