OrangePi AIpro学习5 —— 模型推理程序开发

目录

一、准备工作

1.1 代码裁剪

1.2 测试运行

二、程序讲解

2.1 初始化

2.2 处理模型图片输入

2.3 推理函数

2.4 对输出结果进行处理


前言

本节主要讲解昇腾芯片,例程中使用resnet50推理图像类别的程序。本节讲解的程序,它的环境搭建与使用方法在前面的教程有讲解,第一次看的宝子可以移步前面的教程先搭建一下开发环境

一、准备工作

1.1 代码裁剪

代码目录:cd ~/samples/inference/modelInference/sampleResnetQuickStart

下图,对原来的代码进行备份,然后压缩一下代码量

#include <opencv2/opencv.hpp>
#include <dirent.h>
#include "acl/acl.h"
#include "label.h"

using namespace cv;
using namespace std;

class SampleResnetQuickStart {
public:
    SampleResnetQuickStart(const char* ModelPath, int32_t modelWidth, int32_t modelHeight);
    void InitResource();
    void ProcessInput(const string testImgPath);
    void Inference();
    void GetResult();
private:
    aclrtContext context_;
    aclrtStream stream_;
    uint32_t modelId_;
    const char * modelPath_;
    int32_t modelWidth_;
    int32_t modelHeight_;
    aclmdlDesc * modelDesc_;
    aclmdlDataset * inputDataset_;
    aclmdlDataset * outputDataset_;
    void * inputBuffer_;
    void * outputBuffer_;
    size_t inputBufferSize_;
    float * imageBytes;
    String imagePath;
    Mat srcImage;
    aclrtRunMode runMode_;
};

SampleResnetQuickStart::SampleResnetQuickStart(const char* modelPath, int32_t modelWidth, int32_t modelHeight)
{
    modelPath_ = modelPath;
    modelWidth_ = modelWidth;
    modelHeight_ = modelHeight;
}

void SampleResnetQuickStart::InitResource()
{
    aclInit("");                                // AscendCL初始化函数
    aclrtSetDevice(0);                          // 设置本进程使用哪一个昇腾芯片
    aclrtCreateContext(&context_, 0);           // 在昇腾芯片上面创建一个集合,指定当前线程使用这个集合,传出集合的context(句柄)
    aclrtCreateStream(&stream_);                // 在集合中创建一个用来处理任务的stream(一个昇腾芯片可以创建几百上千的stream)
    aclrtGetRunMode(&runMode_);

    aclmdlLoadFromFile(modelPath_, &modelId_);  // 加载一个模型,传出这个模型的modelId(句柄)
    modelDesc_ = aclmdlCreateDesc();            // 创建模型描述符
    aclmdlGetDesc(modelDesc_, modelId_);        // 从模型modelId(句柄)中获取模型信息到模型描述符中

    // 分配模型输入数据空间
    inputDataset_ = aclmdlCreateDataset();              // 获取模型推理时传入的结构体
    inputBufferSize_ = aclmdlGetInputSizeByIndex(modelDesc_, 0);    
                                                        // 通过模型描述符,获取模型第1个输入需要的空间(模型可能有多个输入)
    aclrtMalloc(&inputBuffer_, inputBufferSize_, ACL_MEM_MALLOC_HUGE_FIRST);
    aclDataBuffer * inputData = aclCreateDataBuffer(inputBuffer_, inputBufferSize_);
    aclmdlAddDatasetBuffer(inputDataset_, inputData);   // 把分配的空间大小信息和地址指针,给模型推理时传入的结构体

    // 分配模型输出数据空间
    outputDataset_ = aclmdlCreateDataset();             // 获取模型推理时传出的结构体
    size_t modelOutputSize = aclmdlGetOutputSizeByIndex(modelDesc_, 0);     
                                                        // 通过模型描述符,获取模型第1个输出需要的空间(模型可能有多个输入)
    aclrtMalloc(&outputBuffer_, modelOutputSize, ACL_MEM_MALLOC_HUGE_FIRST);
    cout << modelOutputSize << endl;
    aclDataBuffer * outputData = aclCreateDataBuffer(outputBuffer_, modelOutputSize);
    aclmdlAddDatasetBuffer(outputDataset_, outputData); // 把分配的空间大小信息和地址指针,给模型推理时传出的结构体
}

void SampleResnetQuickStart::ProcessInput(const string testImgPath)
{
    // read image from file by cv
    imagePath = testImgPath;
    srcImage = imread(testImgPath);

    // zoom image to modelWidth_ * modelHeight_
    Mat resizedImage;
    resize(srcImage, resizedImage, Size(modelWidth_, modelHeight_));

    // get properties of image
    int32_t channel = resizedImage.channels();
    int32_t resizeHeight = resizedImage.rows;
    int32_t resizeWeight = resizedImage.cols;

    // data standardization
    const float min_chn_0 = 123.675;
    const float min_chn_1 = 116.28;
    const float min_chn_2 = 103.53;
    const float var_reci_chn_0 = 0.0171247538316637;
    const float var_reci_chn_1 = 0.0175070028011204;
    const float var_reci_chn_2 = 0.0174291938997821;
    float meanRgb[3] = {min_chn_2, min_chn_1, min_chn_0};
    float stdRgb[3]  = {var_reci_chn_2, var_reci_chn_1, var_reci_chn_0};

    // create malloc of image, which is shape with NCHW
    imageBytes = (float*)malloc(channel * resizeHeight * resizeWeight * sizeof(float));
    memset(imageBytes, 0, channel * resizeHeight * resizeWeight * sizeof(float));

    uint8_t bgrToRgb=2;
    // image to bytes with shape HWC to CHW, and switch channel BGR to RGB
    for (int c = 0; c < channel; ++c)
    {
        for (int h = 0; h < resizeHeight; ++h)
        {
            for (int w = 0; w < resizeWeight; ++w)
            {
                int dstIdx = (bgrToRgb - c) * resizeHeight * resizeWeight + h * resizeWeight + w;
                imageBytes[dstIdx] =  static_cast<float>((resizedImage.at<cv::Vec3b>(h, w)[c] - 1.0f*meanRgb[c]) * 1.0f*stdRgb[c] );
            }
        }
    }
}

void SampleResnetQuickStart::Inference()
{
    // 拷贝host数据到device中
    aclrtMemcpyKind kind;
    if (runMode_ == ACL_DEVICE)
    {
        kind = ACL_MEMCPY_DEVICE_TO_HOST;
    }
    else{
        kind = ACL_MEMCPY_HOST_TO_DEVICE;
    }

    // 把opencv处理好的数据放入输入缓冲区中
    aclrtMemcpy(inputBuffer_, inputBufferSize_, imageBytes, inputBufferSize_, kind);

    // 执行推理
    aclmdlExecute(modelId_, inputDataset_, outputDataset_);
}

void SampleResnetQuickStart::GetResult() 
{
    // get result from output data set
    void * outHostData = nullptr;
    float * outData = nullptr;
    size_t outputIndex = 0;
    aclDataBuffer * dataBuffer = aclmdlGetDatasetBuffer(outputDataset_, outputIndex);
    void * data = aclGetDataBufferAddr(dataBuffer);
    uint32_t len = aclGetDataBufferSizeV2(dataBuffer);

    // copy device output data to host
    aclrtMemcpyKind kind;
    if (runMode_ == ACL_DEVICE)
    {
        kind = ACL_MEMCPY_DEVICE_TO_HOST;
    }
    else
    {
        kind = ACL_MEMCPY_HOST_TO_DEVICE;
    }
    aclrtMallocHost(&outHostData, len);
    aclrtMemcpy(outHostData, len, data, len, kind);

    outData = reinterpret_cast<float*>(outHostData);

    // create map<confidence, class> and sorted by maximum
    map<float, unsigned int, greater<float>> resultMap;
    for (unsigned int j = 0; j < len / sizeof(float); ++j) 
    {
        resultMap[*outData] = j;
        outData++;
    }

    // do data processing with softmax and print top 1 classes
    double totalValue=0.0;
    for (auto it = resultMap.begin(); it != resultMap.end(); ++it) 
    {
        totalValue += exp(it->first);
    }

    // get max <confidence, class>
    float confidence = resultMap.begin()->first;
    unsigned int index = resultMap.begin()->second;
    string line = format("label:%d  conf:%lf  class:%s", index, exp(confidence) / totalValue, label[index].c_str());

    // write image to ../out/
    cv::putText(srcImage, line, Point(0,35), cv::FONT_HERSHEY_TRIPLEX, 1.0, Scalar(255,0,0),2);

    int sepIndex = imagePath.find_last_of("/");
    string fileName = imagePath.substr(sepIndex + 1, -1);
    string outputName = "out_" + fileName;
    imwrite(outputName, srcImage);
    cout << outputName << endl;
    cout << line << endl;

    aclrtFreeHost(outHostData);
    outHostData = nullptr;
    outData = nullptr;
}

int main()
{
    const char * modelPath = "../model/resnet50.om";
    int32_t modelWidth = 224;
    int32_t modelHeight = 224;

    vector<string> allPath;
    allPath.push_back("../data/dog1_1024_683.jpg");

    SampleResnetQuickStart sampleResnet(modelPath, modelWidth, modelHeight);
    sampleResnet.InitResource();

    for (size_t i = 0; i < allPath.size(); i++)
    {
        sampleResnet.ProcessInput(allPath.at(i));
        sampleResnet.Inference();
        sampleResnet.GetResult();
    }
    return 0;
}

1.2 测试运行

如果报错,解决方法可以看前面的教程。下图是推理成功了,类别是 label:162,名字是class:beagle,概率是conf:0.902209

数据集所有类别: 

ImageNet图像库1000个类别名称(中文注释不断更新)_imagene162t类别-CSDN博客

二、程序讲解

2.1 初始化

204行,SampleResnetQuickStart sampleResnet(modelPath, modelWidth, modelHeight);

这是在程序中自定义的一个类,这个类是用来进行流程控制的。初始化类的时候,将模型和模型要求输入的图片宽高输入进去了

这个模型的宽高是根据模型来确定的,resnet50要求输入224*224*3的矩阵,代表图片宽是224,高是224,有RGB三个颜色通道

205行,sampleResnet.InitResource();

在模型参数传进去后,SampleResnetQuickStart类会根据传入的模型文件、模型宽高来对模型推理需要的参数进行初始化

44行,aclInit("");

● 只有执行了这一行,才能使用后续的acl库函数,一个进程只执行一次

● 在进程的最后,要成对的使用 aclFinalize(); 函数,做收尾工作

45行,clrtSetDevice(0); 

● 一个主板上有多个昇腾芯片(主板上插了多块显卡),选择索引号为0的昇腾芯片(选择第一块显卡)

● 在线程的最后,要成对的使用aclrtResetDevice(0);函数,做收尾工作

46行,aclrtCreateContext(&context_, 0);

● 香橙派上是昇腾310B芯片,昇腾310B芯片device中默认存在1个context,本程序中可创建也可不创建context

● 猜测context可能不是一个实体的内容,而是stream集合的一个概念,几个stream组成一个context

● 本程序就是在昇腾设备0上创建了一个context。创建好了以后,本线程就默认使用新创建的context。当前线程在同一时刻内只能使用其中一个Context

●  在线程的最后,要成对的使用aclrtDestroyContext(g_context);函数,做收尾工作

47行:aclrtCreateStream(&stream_);

● 用于给当前context创建一个stream。上图昇腾310B芯片硬件资源最多支持1024个stream

● 一个stream可以用于执行一个任务,本案例中使用这个stream执行了图片裁剪指定区域的任务。多个stream可以用于同时执行多个任务

● 在线程的最后,要成对的使用aclrtDestroyStream(g_stream);函数,做收尾工作

48行:aclrtGetRunMode(&runMode_);

● 获取当前程序的运行模式

ACL_DEVICE:昇腾AI软件栈运行在Device的Control CPU或板端环境上
ACL_HOST:昇腾AI软件栈运行在Host CPU上

如果是ACL_DEVICE,就代表当前程序是在AI CPU上运行的;如果是ACL_HOST,就代表当前程序是在CPU上运行的,如下图红色圆圈中写的那样

50行,aclmdlLoadFromFile(modelPath_, &modelId_);

把硬盘里面的模型,加载到内存条上。因为可能加载了多个不同的模型到内存条上了,所以要通过modelId_(句柄)去找到modelPath("../model/resnet50.om")模型在内存条上加载的位置。

51-52行,modelDesc_ = aclmdlCreateDesc();  aclmdlGetDesc(modelDesc_, modelId_);

创建一个模型的描述符,类似于创建了一个结构体,如下图:

struct ModelDesc {
    vector<int> allBufferSize;
    ...
};

strcut ModelDesc * modelDesc_ = new ModelDesc;

然后aclmdlGetDesc(modelDesc_, modelId_);就是根据模型的句柄,在内存上找到模型,将模型的各种信息赋值给上面的结构体

55行,inputDataset_ = aclmdlCreateDataset();

创建一个输入数据描述符,类似于创建一个结构体,如下图:

struct Dataset {
    uint32_t bufLen;
    char * bufData;
    ...
};

struct Dataset * inputDataset_ = new Dataset;

56行,inputBufferSize_ = aclmdlGetInputSizeByIndex(modelDesc_, 0);

这段代码相当于获取结构体ModelDesc的vector<int> allBufferSize;中第一个值。

allBufferSize中存储的是模型从第1个到第N个输入需要的空间大小(单位是字节),resnet50这个模型只有1个输入,所以现在只能取到索引号为0的输入

58行,aclrtMalloc(&inputBuffer_, inputBufferSize_, ACL_MEM_MALLOC_HUGE_FIRST);

根据模型输入需要的大小,在显存上分配空间,也就是下图昇腾310B芯片中的内存上分配空间

59-60行,aclDataBuffer * inputData = aclCreateDataBuffer(inputBuffer_, inputBufferSize_); aclmdlAddDatasetBuffer(inputDataset_, inputData);

相当于给结构体struct Dataset中的数据赋值。inputDataset_->bufLen=inputBufferSize_; inputDataset_->bufData=inputBuffer_;

输出结构体的代码逻辑和输入是一样的,这里不多赘述了,至本段代码结束,我们得到了下面三个关键的变量,其中inputDataset_->bufData和inputBuffer_同时指向大小为602112字节的内存空间

outputBuffer_->bufData指向大小为4000字节的内存空间

2.2 处理模型图片输入

下图209行,就是处理模型输入图片的,使用opencv方法将图片resize成224*224*3格式的矩阵,然后将图片的数据给到602112字节的输入数组中(在上图中)

下图210行,对图片进行推理,得到1000*4的输出矩阵,输出矩阵的数据存放到4000字节的输出数组中(在上图)

下图211行,对输出矩阵中的内容进行处理

下面详细的看一下ProcessInput函数

76行,srcImage = imread(testImgPath);

使用opencv,读取图片到内存中

80行,resize(srcImage, resizedImage, Size(modelWidth_, modelHeight_));

将图片大的宽高修改为224*224

83-85行,获取图片的通道数为3,RGB。图片的宽高为224*224

88-113行

● 对图片进行处理,将图片从HWC转成CHW。

HWC格式是 [224, 224, 3] 类型的矩阵,相当于有一个二维数组arr[224][224],数组的元素是那个位置的RGB,符合我们的直观感觉

CHW格式是 [3, 224, 224] 类型的矩阵,相当于有一个一维数组arr[3],这个数组的元素是一个二维数组,arr[0]、arr[1]、arr[2]中的存储的分别是RGB三个通道在224*224图片每个位置的颜色值

● 将图片从BGR转换成RGB。如果是HWC,则arr[3][2](随机的一个位置)的变化如下图

如果是CHW,则变化则如下,下图从上面的BGR变成RGB。下图中,每个方格里面存储的是1字节数据,区别于上图一个方格里面存储的是RGB 3个字节数据

● ProcessInput(const string testImgPath)这个函数会将得到的数据存储到char * imageBytes[602112]; 字节数组中

2.3 推理函数

129行,aclrtMemcpy(inputBuffer_, inputBufferSize_, imageBytes, inputBufferSize_, kind);

如下图,前面处理模型图片输入函数已经将读取到 并且处理好的图片放到imageBytes数组中了,129行代码的作用就是将imageBytes的数据拷贝到inputBuffer_数组中去

● 132行,aclmdlExecute(modelId_, inputDataset_, outputDataset_);

函数根据模型的id,获取到模型信息。根据输入图片获取到输入图片的数据,然后进行推理。将推理得到的数据存放到outputDataset_,等下就可以通过outputDataset_来获取输出的推理结果

2.4 对输出结果进行处理

141-143行,通过推理得到的outputDataset_获取其中的数据。得到的数据存储在void * data;数组中,将这个数组强转成float * outData;数组。得到的长度len应该是4000字节

161-166行,float的长度是4,所以最终float数组的长度是1000,利用map对float数组进行排序,得到float数组中最大的数字。

这个数字就是程序最终的预测概率,又通过这个最大的数字在数组中的位置,找到这个类别。例如本程序 outData[162] 是最大的,并且通过outData[162] 计算出预测准确率是90.2209%

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

herb.dr

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

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

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

打赏作者

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

抵扣说明:

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

余额充值