Window10+vs2019+opencv2+tensorflow2+c_api+pb模型制作,调用以及应用

提示:本文仅供参考


前言

本文起因是作为一个不常研究深度学习的人,想要基于自己工作工程的数据集训练自己的resnet50的模型,并利用模型在visual studio环境下进行开发时,发现一头雾水,从建模型,到训练模型,到训练模型成功了以后,发现不知道怎么在C++环境下调用。查找了国内大量的网站,也找不到一个方便快捷的调用模型的方法。
最终在阅读源码,通过源码的关键词搜索,终于找到了前人的部分工作,并感慨学习不易,因此制作了本次学习内容笔记。


一、我要干啥,方向怎么选

1.目标

我手里有图像及其分类,如果再有一张一样大小的图,我能不能通过深度学习神经网络的方式,直接让训练好的模型告诉我这个图像属于哪一类。也就是常说的图像分类工作。

2.模型选择

为了完成这一目标,我随便翻了翻近几年的图像分类深度学习算法,也就resnet50还挺常用的,网上资源也不少。那么就选这个了,后面好像有更好的算法了,不过如果这个跑顺利了,他们加到基础模型里,到时候ctrl+c,ctrl+v改改就好了。

3.流程分析

模型有了,那么用什么工具搭建模型,用什么工具调用模型呢?问了在大厂做计算机视觉的同学,他们是在linux上搭建服务器,模型自己在自己的环境里,也就是好用的python环境,训练好放上去,在linux环境下,通过C++语言调用pb模型进行分类。
结合我现在的工程业务需要,我是要用户自己训练,自己测试。并且我的程序基础开发环境是C++。那么我就需要前面用C++制作初步的数据集,调用python精细化将数据集弄成神经网络能够读取的数据集。然后通过python调用各种库完成模型训练。训练好后将模型放到一个位置上。再通过C++直接调用tensorflow进行模型读取。最后用跑在内存里的模型,实时处理每一个被识别图像即可。
接下来就可以考虑走一走这个计划流程了。

二、我有数据,怎么做数据库

1.工具选择

我手里有一堆图以及这些图的标注,指向了这些图的分类内容,那么怎么弄成神经网络可以直接读取的数据呢?
通过调研,首先确立了自己要用的工具,也就是现在大家都在用的keras,这个对我这样的菜鸡十分友好。并且keras读取的数据集格式是h5,那么现在第一件事情就是----------环境搭建。。。

2.环境搭建

当场就花了几分钟搭建了一下环境(前提是你之前做过深度学习,也就是你的电脑里有张支持cuda的显卡,装过cuda,cudnn之类的东西,并且opencv的C++环境本身也有)。也就是先去python官网装了个python3.10.4。然后下载了一个pycharm社区版。接下来进到pycharm里,新建了个工程,配置了环境,加了好多库,如下图,装了好多有的没的:

1
2
主要就是装了比如os,keras,tensorflow-gpu,scikit-learn,tqdm,numpy,等等等等。反正抄代码的时候,发现哪个库没有就pip3 install一下就好了。这个基础如果不懂的话,可以先看看别人的,我就不讲了。以上库也不是必须要全装,仅供参考。

import numpy as np
import tensorflow as tf
import os
import matplotlib.pyplot as plt
import h5py
import math
from keras import layers
from keras.layers import Input, Add, Dense, Activation, ZeroPadding2D, BatchNormalization, Flatten, Conv2D, \
    AveragePooling2D, MaxPooling2D, GlobalMaxPooling2D
from keras.models import Model, load_model
from keras.preprocessing import image
from keras.initializers import glorot_uniform
# 导入所需的图像预处理模块
from keras.preprocessing.image import load_img
import keras.backend as K
K.set_image_data_format('channels_last')
K.set_learning_phase(1)

3.先把你的数据读到程序里

接下来就是代码了,我是把标注label和图片pic分别放到两个文件夹里,然后按照名称顺序分别存了txt文件和jpg文件。txt里就写了分类,比如0,1,2,3,4,5,6,7巴拉巴拉之类的。然后通过get_files代码获取到图片的路径,标注的数值以及分类的最大值。作为输出:

def get_files(file_dir):
    pic_path = []
    label_pic_path = []
    label_pic = []

    max_get_y = 0

    for file in os.listdir(file_dir + '/pics'):
        pic_path.append(file_dir + '/pics' + '/' + file)
    for file in os.listdir(file_dir + '/labels'):
        label_pic_path.append(file_dir + '/labels' + '/' + file)
        f_txt = open(file_dir + '/labels' + '/' + file, "r")
        first_line = f_txt.readline()
        first_line = first_line.strip("\n")
        first_line = first_line.split()
        label_pic.append(first_line[0])
        if int(first_line[0]) > max_get_y:
            max_get_y = int(first_line[0])

    image_list = pic_path
    label_list = label_pic

    # 利用shuffle打乱顺序
    temp = np.array([image_list, label_list])
    temp = temp.transpose()
    np.random.shuffle(temp)

    # 从打乱的temp中再取出list(img和lab)
    image_list = list(temp[:, 0])
    label_list = list(temp[:, 1])
    label_list = [int(i) for i in label_list]

    return image_list, label_list, max_get_y
    # 返回两个list 分别为图片文件名及其标签  顺序已被打乱

4.制作H5数据集

制作成h5数据集,然后返回值是类别个数。在这里我通过随机数的方式,将数据分成了训练集和测试集,放到同一个h5里面,并且按照4:1的比例存放了。具体看代码吧,在前人基础上改的,但是前人是谁忘了。。。好多人都用的这套写法。


# 制作h5数据集
def create_h5_for_now(dataset_path, h5_data_name):
    image_list, label_list, max_y = get_files(dataset_path)

    if len(image_list) <= 10:
        return -1

    f = h5py.File(dataset_path + '/data/' + h5_data_name + '_data.h5', 'w')

    if len(image_list) > 50:
        train_count = int(len(image_list) / 5 * 4)
        test_count = len(image_list) - train_count

        train_image = np.random.rand(train_count, 128, 128).astype('int')
        train_label = np.random.rand(train_count, 1).astype('int')

        test_image = np.random.rand(test_count, 128, 128).astype('int')
        test_label = np.random.rand(test_count, 1).astype('int')

        for i in range(train_count):
            train_image[i] = np.array(plt.imread(image_list[i]))
            train_label[i] = np.array(label_list[i])

        for i in range(train_count, len(image_list)):
            test_image[i - train_count] = np.array(plt.imread(image_list[i]))
            test_label[i - train_count] = np.array(label_list[i])

        class_list = np.arange(max_y + 1).astype('int')

        # 写入data.h5文件
        f.create_dataset('train_set_x', data=train_image)
        f.create_dataset('train_set_y', data=train_label)
        f.create_dataset('test_set_x', data=test_image)
        f.create_dataset('test_set_y', data=test_label)
        f.create_dataset('list_classes', data=class_list)

    else:
        train_image = np.random.rand(len(image_list) - 10, 128, 128).astype('int')
        train_label = np.random.rand(len(image_list) - 10, 1).astype('int')

        test_image = np.random.rand(10, 128, 128).astype('int')
        test_label = np.random.rand(10, 1).astype('int')

        for i in range(len(image_list) - 10):
            train_image[i] = np.array(plt.imread(image_list[i]))
            train_label[i] = np.array(label_list[i])

        for i in range(len(image_list) - 10, len(image_list)):
            test_image[i + 10 - len(image_list)] = np.array(plt.imread(image_list[i]))
            test_label[i + 10 - len(image_list)] = np.array(label_list[i])

        class_list = np.arange(max_y + 1).astype('int')

        # 写入data.h5文件
        f.create_dataset('train_set_x', data=train_image)
        f.create_dataset('train_set_y', data=train_label)
        f.create_dataset('test_set_x', data=test_image)
        f.create_dataset('test_set_y', data=test_label)
        f.create_dataset('list_classes', data=class_list)

    f.close()

    return max_y + 1

到此数据集就有了

三、有了数据集了,怎么训练

这里就简单了,网上类似的方案一抓一大把,我这里分享我的方法。大家仅供参考:

# 训练模型
def train_resnet50_128_128_1_model(class_type_get, h5_data_path, pb_model_path, save_flag=True):
    # 初始化resnet50
    model = ResNet50(input_shape=(128, 128, 1), classes=class_type_get)
    model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
    # 加载数据集
    X_train_orig, Y_train_orig, X_test_orig, Y_test_orig, classes = load_dataset(h5_data_path)

    # 归一化图像向量
    X_train = X_train_orig / 255.
    X_test = X_test_orig / 255.

    # Convert training and test labels to one hot matrices
    Y_train = convert_to_one_hot(Y_train_orig, class_type_get).T
    Y_test = convert_to_one_hot(Y_test_orig, class_type_get).T

    # 训练模型
    model.fit(X_train, Y_train, epochs=200, batch_size=8)  # 默认迭代次数为20 修改为50
    # 验证模型精度
    preds = model.evaluate(X_test, Y_test)

    if save_flag:
        # 保存模型
        mp = pb_model_path + '.h5'
        model.save(pb_model_path)  # 存为tf默认模型格式
        model.save(mp)  # 存为h5

    return model, preds[0], preds[1]

主要就是读数据,训练数据,保存模型并返回制作的模型以及预测结果。
其中模型保存分别保存成了h5格式和tensorflow默认格式。这里就需要注意了,tensorflow默认格式是为后续调用做准备的,能不动就不动,动了不好用了也别说我写的有问题。

四、训练好的模型,怎么验证

验证也是现成的嘛,就是读个模型,读个图进来,然后跑一跑:

1.加载模型


# 加载模型
def load_resnet50_128_128_1_model(h5_model_path):
    model_loaded = load_model(h5_model_path)  # 加载模型和图等
    return model_loaded
model_get = load_resnet50_128_128_1_model(path_of_test + '/cfg/' + h5_name + '_model.h5')  # 加载模型和图等

2.读图测试

# 使用模型对图片进行分类-简单版,只输出类别,和置信度
def classification_img_with_model_simple(model_now, pic_path):
    original = load_img(pic_path, target_size=(128, 128), color_mode='grayscale')

    # 将输入图像从PIL格式转换为Numpy格式
    # In PIL-- 图像为(width, height, channel)
    # In Numpy——图像为(height, width, channel)
    numpy_image = image.img_to_array(original)

    # 将图像/图像转换为批量格式
    # expand_dims将为特定轴上的数据添加额外的维度
    # 网络的输入矩阵具有形式(批量大小,高度,宽度,通道)
    # 因此,将额外的维度添加到轴0。
    image_batch = np.expand_dims(numpy_image, axis=0)

    # 使用各种网络进行预测
    # 通过从批处理中的图像的每个通道中减去平均值来预处理输入。
    # 平均值是通过从ImageNet获得的所有图像的R,G,B像素的平均值获得的三个元素的阵列
    # 获得每个类的发生概率
    # 将概率转换为人类可读的标签

    # ResNet50网络模型
    # 对输入到ResNet50模型的图像进行预处理
    processed_image = image_batch / 255.

    predict_result = model_now.predict(processed_image)

    return np.argmax(predict_result, axis=1)[0], np.max(np.squeeze(predict_result))
    

    # 多张图同时进行识别-函数外调用,这个不会不知道吧
    # for file in os.listdir('./Learning/test_2/pics'):
    #     img_path = './Learning/test_2/pics' + '/' + file
    #
    #     start_time = time.time()
    #     classify_result, confidence_result = classification_img_with_model_simple(model_get, img_path)
    #     end_time = time.time()
    #     print(file + 'image', "time cost:", float(end_time - start_time) * 1000.0, "ms")
    #     print(classify_result)
    #     print(confidence_result)

五、验证没啥问题的模型,怎么被C++环境下调用

终于到了要讲的重点了,累死了。

1.找方法

到了这一步,我就陷入了深深的迷茫,网上什么信息都有,乱七八糟的。大部分都是让你对tensorflow源码进行编译,然后用编译好的库,再用tensorflow::之类的东西来跑。
经过层层筛选,核查,最终结论我就直接说吧:
1.tensorflow分成1.和2.的版本,他们各有优缺点。但是根据你的项目环境,首先都2022年了,cuda和cudnn的版本都很高了,也就2.几的版本支持了,别的都不好用。所以就盯着2用吧。
2.tensorflow1.几的版本编译是用cmake,2.几用的是bazel
3.其实如果只是调用tensorflow的库,也不用非得自己编译,人家都有现成的,用就行了。

2.配环境

4.如果就是在和我环境差不多的里面去调用pb模型,那就直接下载一个libtensorflow的东西就行了。从这下
5.下载好了以后,就和opencv一样,配置一下程序的库目录,C++,链接库什么的,这个就是小儿科了。具体的话,我给个图吧,帮人帮到底
3
4
5
然后记得把dll文件放到你的debug,release文件夹里,未来打包程序以后,记得带着,不然最后他会告诉你为什么要带着的。
至此环境配完了

六、测试C++下调用代码

1.引入库

代码如下:

#include <tensorflow/c/c_api.h>

2.读入数据

主函数代码如下:

    qDebug() << "Hello from TensorFlow C library version" << TF_Version();

    QString weight_path = "model_new/";
    std::string model_path = weight_path.toStdString();
    const char* tag_model = "serve";
    int num_of_tags = 1;

    TF_Graph* graph = TF_NewGraph();
    TF_Status* status = TF_NewStatus();
    TF_SessionOptions* options = TF_NewSessionOptions();
    TF_Buffer* buffer_run_options = nullptr;

    TF_Buffer* buffer_meta_graph_def = TF_NewBuffer();
    // 读入模型
    TF_Session* newsession = TF_LoadSessionFromSavedModel(options, buffer_run_options, model_path.c_str(), &tag_model, num_of_tags, graph, buffer_meta_graph_def, status);
    //判断读入状态
    if (TF_GetCode(status) == TF_OK)
    {
		qDebug() << "status: Session successfully created.";
    }
    else
    {
		qDebug() << "status: Session created failed." << TF_Message(status);
    }

    //制作输入张量tensor//****** Get input tensor
    //TODO : need to use saved_model_cli to read saved_model arch
    int NumInputs = 1;
    TF_Output* Input = (TF_Output*)malloc(sizeof(TF_Output) * NumInputs);

    TF_Output t0 = { TF_GraphOperationByName(graph, "serving_default_input_1"), 0 };
    if (t0.oper == NULL)
        qDebug() << "ERROR: Failed TF_GraphOperationByName serving_default_input_1";
    else
        qDebug() << "TF_GraphOperationByName serving_default_input_1 is OK";

    Input[0] = t0;

    //制作输出张量tensor//********* Get Output tensor
    int NumOutputs = 1;
    TF_Output* Output = (TF_Output*)malloc(sizeof(TF_Output) * NumOutputs);

    TF_Output t2 = { TF_GraphOperationByName(graph, "StatefulPartitionedCall"), 0 };
    if (t2.oper == NULL)
        qDebug() << "ERROR: Failed TF_GraphOperationByName StatefulPartitionedCall";
    else
        qDebug() << "TF_GraphOperationByName StatefulPartitionedCall is OK";

    if (Output == NULL)
    {
        return;
    }
    Output[0] = t2;

    //********* Allocate data for inputs & outputs
    TF_Tensor** InputValues = (TF_Tensor**)malloc(sizeof(TF_Tensor*) * NumInputs);
    TF_Tensor** OutputValues = (TF_Tensor**)malloc(sizeof(TF_Tensor*) * NumOutputs);



    //这里图片我们用OpenCV读取8的灰度图片
    TF_Tensor* float_tensor = Mat2Tensor(cv::imread("./Learning/test_1/pics/img512.jpg"), TF_FLOAT, 128, 128, 1);//TF_NewTensor(TF_FLOAT, dims, ndims, data, ndata, &NoOpDeallocator, 0);
    if (float_tensor != NULL)
    {
        qDebug() << "TF_NewTensor is OK";
    }
    else
    {
        qDebug() << "ERROR: Failed TF_NewTensor";
    }

    if (InputValues == NULL)
    {
        return;
    }
    InputValues[0] = float_tensor;

    //Run the Session
    TF_SessionRun(newsession, NULL, Input, InputValues, NumInputs, Output, OutputValues, NumOutputs, NULL, 0, NULL, status);

    if (TF_GetCode(status) == TF_OK)
    {
        qDebug() << "Session is OK";
    }
    else
    {
        qDebug() << "%s", TF_Message(status);
    }


	if (newsession != nullptr)
	{
		qDebug() << "Release Session.";
		//释放相关资源,防止内存泄漏
		TF_CloseSession(newsession, status);
        //Free memory
        TF_DeleteGraph(graph);
        TF_DeleteSession(newsession, status);
        TF_DeleteSessionOptions(options);
        TF_DeleteStatus(status);
	}
	else
	{
		qDebug() << "Session created failed.";
	}

    if (OutputValues == NULL)
    {
        return;
    }
    void* buff = TF_TensorData(OutputValues[0]);
    float* offsets = (float*)buff;
    qDebug() << "Result Tensor :";
    std::vector<float>result_list;
    result_list.resize(5);//这个5是类别个数哈,你们自己记得改。如何读我还没研究到那呢
    for (int i = 0; i < 5; i++)//这个5是类别个数哈,你们自己记得改。如何读我还没研究到那呢
    {
        result_list[i] = offsets[i];
        qDebug() << offsets[i];
    }

    size_t maxIndex = argmax(result_list.begin(), result_list.end());

其他代码如下,记得放在主函数的上边:

//C++中argmin和argmax的实现
template<class ForwardIterator>
inline size_t argmin(ForwardIterator first, ForwardIterator last)
{
    return std::distance(first, std::min_element(first, last));
}

template<class ForwardIterator>
inline size_t argmax(ForwardIterator first, ForwardIterator last)
{
    return std::distance(first, std::max_element(first, last));
}


TF_Tensor* CreateTensor(TF_DataType data_type, const std::int64_t* dims, std::size_t num_dims, const void* data, std::size_t len)
{
    if (dims == nullptr || data == nullptr)
    {
        return nullptr;
    }
    TF_Tensor* tensor = TF_AllocateTensor(data_type, dims, static_cast<int>(num_dims), len);
    if (tensor == nullptr)
    {
        return nullptr;
    }
    void* tensor_data = TF_TensorData(tensor);
    if (tensor_data == nullptr)
    {
        TF_DeleteTensor(tensor);
        return nullptr;
    }
    std::memcpy(TF_TensorData(tensor), data, (std::min)(len, TF_TensorByteSize(tensor)));
    return tensor;
}

TF_Tensor* Mat2Tensor(cv::Mat img, TF_DataType data_type, int tagHeight, int tagWidth, int channel)
{
    const std::vector<std::int64_t>input_dims = { 1, tagHeight, tagWidth, channel };
    unsigned long long data_size = 1;
    switch (data_type)
    {
    case TF_FLOAT:
        data_size = sizeof(std::float_t);
        break;
    case TF_UINT8:
        data_size = sizeof(std::uint8_t);
        break;
    case TF_UINT32:
        data_size = sizeof(std::uint32_t);
        break;
    case TF_DOUBLE:
        data_size = sizeof(std::double_t);
        break;
    default:
        data_size = sizeof(std::float_t);
        break;
    }
    for (auto i : input_dims)
    {
        data_size *= i;//bytes size
    }
    cv::resize(img, img, cv::Size(tagWidth, tagHeight));
    //cvtColor(img, img, (1 == channel) ? cv::COLOR_BGR2GRAY : cv::COLOR_RGB2BGR);//我读入的是灰度图,所以这个和原来大佬写的相比,被我注释掉了
   // img = (img - 0) / 255.0f;

    //mnist部分测试代码,其他dim需要自行填充数据
    size_t length = tagHeight * tagWidth;
    float* data = new float[length];
    for (size_t i = 0; i < length; ++i)
    {
        data[i] = img.data[i] / 255.0f;//归一化
    }


    //PS:这里的(n,h,w,c)的n,也就是批大小TF会自动扩展一维无需手动扩增,这个和NCNN里面是一致的,需要注意一下!
    TF_Tensor* image_input = CreateTensor(data_type, input_dims.data(), input_dims.size(), data, data_size);
    return image_input;
}

TF_Buffer* read_file(const char* file);

void free_buffer(void* data, size_t length) {
    free(data);
}

TF_Buffer* read_file(const char* file) {
    FILE* f = fopen(file, "rb");
    fseek(f, 0, SEEK_END);
    long fsize = ftell(f);
    fseek(f, 0, SEEK_SET);  //same as rewind(f);                                            

    void* data = malloc(fsize);
    fread(data, fsize, 1, f);
    fclose(f);

    TF_Buffer* buf = TF_NewBuffer();
    buf->data = data;
    buf->length = fsize;
    buf->data_deallocator = free_buffer;
    return buf;
}

void NoOpDeallocator(void* data, size_t a, void* b) {}

哦对,我是在qt里跑的,所以输出用的qDebug,你们可以用cout啥的都行,随心所欲吧


总结

至此,全都讲完了,我基本上把我想到的都写了,我都不理解为什么我去百度搜个TF_一些函数都搜不到解释。也不明白为啥网上没有tensorflow2.几版本lib文件的调用方法。就离谱。最后好不容易找到一个大佬的(如果想要直接进行参考调用,可以直接转前人工作)进行学习。,改了改就现在这样了。这就是我自学的思路,感觉真麻烦。就没有一个很成系统的学习方法么。。。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

迷失的walker

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值