赞
踩
目录
前言
本节主要讲解昇腾芯片,例程中使用resnet50推理图像类别的程序。本节讲解的程序,它的环境搭建与使用方法在前面的教程有讲解,第一次看的宝子可以移步前面的教程先搭建一下开发环境
代码目录: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;
- }

如果报错,解决方法可以看前面的教程。下图是推理成功了,类别是 label:162,名字是class:beagle,概率是conf:0.902209
数据集所有类别:
ImageNet图像库1000个类别名称(中文注释不断更新)_imagene162t类别-CSDN博客
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字节的内存空间
下图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]; 字节数组中
129行,aclrtMemcpy(inputBuffer_, inputBufferSize_, imageBytes, inputBufferSize_, kind);
如下图,前面处理模型图片输入函数已经将读取到 并且处理好的图片放到imageBytes数组中了,129行代码的作用就是将imageBytes的数据拷贝到inputBuffer_数组中去
● 132行,aclmdlExecute(modelId_, inputDataset_, outputDataset_);
函数根据模型的id,获取到模型信息。根据输入图片获取到输入图片的数据,然后进行推理。将推理得到的数据存放到outputDataset_,等下就可以通过outputDataset_来获取输出的推理结果
141-143行,通过推理得到的outputDataset_获取其中的数据。得到的数据存储在void * data;数组中,将这个数组强转成float * outData;数组。得到的长度len应该是4000字节
161-166行,float的长度是4,所以最终float数组的长度是1000,利用map对float数组进行排序,得到float数组中最大的数字。
这个数字就是程序最终的预测概率,又通过这个最大的数字在数组中的位置,找到这个类别。例如本程序 outData[162] 是最大的,并且通过outData[162] 计算出预测准确率是90.2209%
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。