图像分类应用样例开发介绍(C++)
在本章节介绍基于AscendCL接口如何开发一个基于ResNet-50模型的图像分类样例。
样例介绍
“图片分类应用”,即标识图片所属的分类。

本例中使用的是Caffe框架的ResNet-50模型。用户可以直接使用训练好的开源模型,也可以基于开源模型的源码进行修改、重新训练,还可以基于算法、框架构建适合的模型。
ResNet-50模型的基本介绍如下:
- 输入数据:RGB格式、224*224分辨率的输入图片。
- 输出数据:图片的类别标签及其对应置信度。

- 置信度是指图片所属某个类别可能性。
- 类别标签和类别的对应关系与训练模型时使用的数据集有关,需要查阅对应数据集的标签及类别的对应关系。
获取代码
- 获取代码文件。
单击获取链接或使用wget命令获取代码(使用wget时需确保开发者套件能够连接外网),下载代码文件压缩包,以root用户登录开发者套件。
wget https://ascend-repo.obs.cn-east-2.myhuaweicloud.com/Atlas%20200I%20DK%20A2/DevKit/models/sdk_cal_samples/resnet50_acl_cplusplus.zip
- 将“resnet50_acl_cplusplus.zip”压缩包上传到开发者套件,解压并进入解压后的目录。
unzip resnet50_acl_cplusplus.zip cd resnet50_acl_cplusplus
代码目录结构如下所示,按照正常开发流程,需要将框架模型文件转换成昇腾AI处理器支持推理的om格式模型文件,鉴于当前是入门内容,用户可直接获取已转换好的om模型进行推理。
cd resnet50_acl_cplusplus ├── data │ ├── dog1_1024_683.jpg // 测试图片 ├── model │ ├── resnet50.om // om模型文件 ├── script │ ├── transferPic.py // 将测试图片预处理为符合模型要求的图片,包括将*.jpg转换为*.bin,同时将图片从1024*683的分辨率缩放为224*224 ├── src │ ├── CMakeLists.txt // cmake编译脚本 │ ├── main.cpp // 主函数,图片分类功能的实现文件 ├── sample_build.sh // 编译代码的脚本 ├── sample_run.sh // 运行应用的脚本
代码解析
开发代码过程中,在“resnet50_acl_cplusplus/src/main.cpp”文件中已包含读入数据、前处理、推理、后处理等功能,串联整个应用代码逻辑,此处仅对代码进行解析。
- 在“main.cpp”文件的开头有如下代码,用于导入第三方库与调用AscendCL接口推理所需文件。
#include "acl/acl.h" #include <iostream> #include <fstream> #include <cstring> #include <map> using namespace std;
- 资源初始化。
使用AscendCL接口开发应用时,必须先初始化AscendCL ,否则可能会导致后续系统内部资源初始化出错,进而导致其它业务异常。
在初始化时,还支持跟推理相关的可配置项(例如,性能相关的采集信息配置),以json格式的配置文件传入AscendCL初始化接口。如果当前的默认配置已满足需求(例如,默认不开启性能相关的采集信息配置),无需修改,可向AscendCL初始化接口中传入nullptr。int32_t deviceId = 0;//指定运行模型的昇腾AI处理器ID,0表示指定0号处理器 void InitResource() { aclError ret = aclInit(nullptr);//调用AscendCL初始化接口 ret = aclrtSetDevice(deviceId); //调用指定运行模型的昇腾AI处理器ID的接口 }
- 模型加载。
uint32_t modelId; void LoadModel(const char* modelPath) { aclError ret = aclmdlLoadFromFile(modelPath, &modelId);//调用加载模型文件接口,modelId为模型ID的指针,通过接口加载模型后,会为模型ID赋值 }
- 将测试图片读入内存,供推理使用。
size_t pictureDataSize = 0; void *pictureData; //申请内存,使用C/C++标准库的函数将测试图片读入内存 void LoadPicture(const char* picturePath) { string fileName = picturePath; ifstream binFile(fileName, ifstream::binary); binFile.seekg(0, binFile.end); pictureDataSize = binFile.tellg(); binFile.seekg(0, binFile.beg); aclError ret = aclrtMallocHost(&pictureData, pictureDataSize); binFile.read((char*)pictureData, pictureDataSize); binFile.close(); }
- 执行推理。
在调用AscendCL接口进行模型推理时,模型推理有输入、输出数据,输入、输出数据需要按照AscendCL规定的数据类型存放。相关数据类型如下:
- 使用aclmdlDesc类型的数据描述模型基本信息(例如输入/输出的个数、名称、数据类型、Format、维度信息等)。
模型加载成功后,用户可根据模型的ID,调用该数据类型下的操作接口获取该模型的描述信息,进而从模型的描述信息中获取模型输入/输出的个数、内存大小、维度信息、Format、数据类型等信息。
- 使用aclmdlDataset类型的数据描述模型的输入/输出数据。
模型可能存在多个输入、多个输出,调用aclmdlDataset类型的操作接口添加多个aclDataBuffer类型的数据。
图2 aclmdlDataset与aclDataBuffer的关系
aclmdlDataset *inputDataSet; aclDataBuffer *inputDataBuffer; aclmdlDataset *outputDataSet; aclDataBuffer *outputDataBuffer; aclmdlDesc *modelDesc; size_t outputDataSize = 0; void *outputData; // 准备模型推理的输入数据结构 void CreateModelInput() { // 创建aclmdlDataset类型的数据,描述模型推理的输入 inputDataSet = aclmdlCreateDataset(); inputDataBuffer = aclCreateDataBuffer(pictureData, pictureDataSize); aclError ret = aclmdlAddDatasetBuffer(inputDataSet, inputDataBuffer); } // 准备模型推理的输出数据结构 void CreateModelOutput() { // 创建模型描述信息 modelDesc = aclmdlCreateDesc(); aclError ret = aclmdlGetDesc(modelDesc, modelId); // 创建aclmdlDataset类型的数据,描述模型推理的输出 outputDataSet = aclmdlCreateDataset(); // 获取模型输出数据需占用的内存大小,单位为Byte outputDataSize = aclmdlGetOutputSizeByIndex(modelDesc, 0); // 申请输出内存 ret = aclrtMalloc(&outputData, outputDataSize, ACL_MEM_MALLOC_HUGE_FIRST); outputDataBuffer = aclCreateDataBuffer(outputData, outputDataSize); ret = aclmdlAddDatasetBuffer(outputDataSet, outputDataBuffer); } // 执行模型 void Inference() { CreateModelInput(); CreateModelOutput(); float start; float finish; aclError ret = aclmdlExecute(modelId, inputDataSet, outputDataSet); }
- 使用aclmdlDesc类型的数据描述模型基本信息(例如输入/输出的个数、名称、数据类型、Format、维度信息等)。
- 处理模型推理的结果数据,在屏幕上显示图片的top5置信度的类别编号。
void PrintResult() { // 将内存中的数据转换为float类型 float* outFloatData = reinterpret_cast<float *>(outputData); // 屏显测试图片的top5置信度的类别编号 map<float, unsigned int, greater<float>> resultMap; for (unsigned int j = 0; j < outputDataSize / sizeof(float);++j) { resultMap[*outFloatData] = j; outFloatData++; } int cnt = 0; for (auto it = resultMap.begin();it != resultMap.end();++it) { if(++cnt > 5) { break; } printf("top %d: index[%d] value[%lf] \n", cnt, it->second, it->first); } }
- 卸载模型,并释放模型描述信息。
- 释放内存、销毁推理相关的数据类型。
void UnloadPicture() { aclError ret = aclrtFreeHost(pictureData); pictureData = nullptr; aclDestroyDataBuffer(inputDataBuffer); inputDataBuffer = nullptr; aclmdlDestroyDataset(inputDataSet); inputDataSet = nullptr; ret = aclrtFree(outputData); outputData = nullptr; aclDestroyDataBuffer(outputDataBuffer); outputDataBuffer = nullptr; aclmdlDestroyDataset(outputDataSet); outputDataSet = nullptr; }
- 释放资源。
- 定义一个运行main函数。
int main() { // 1.定义一个资源初始化的函数,用于AscendCL初始化、运行管理资源申请(指定计算设备) InitResource(); // 2.定义一个模型加载的函数,加载图片分类的模型,用于后续推理使用 const char *modelPath = "../model/resnet50.om"; //定义模型文件路径,om模型文件路径请参见获取代码。 LoadModel(modelPath);//定义模型加载函数 // 3.定义一个读图片数据的函数,将测试图片数据读入内存,并传输到Device侧,用于后续推理使用 const char *picturePath = "../data/dog1_1024_683.bin"; LoadPicture(picturePath); // 4.定义一个推理的函数,用于执行推理 Inference(); // 5.定义一个推理结果数据处理的函数,用于在终端上屏显测试图片的top5置信度的类别编号 PrintResult(); // 6.定义一个模型卸载的函数,卸载图片分类的模型 UnloadModel(); // 7.定义一个函数,用于释放内存、销毁推理相关的数据类型,防止内存泄露 UnloadPicture(); // 8.定义一个资源去初始化的函数,用于AscendCL去初始化、运行管理资源释放(释放计算设备) DestroyResource(); }
运行推理
- 编译代码。
进入“resnet50_acl_cplusplus”目录下,执行以下命令。
下为设置环境变量的示例,<SAMPLE_DIR>表示样例所在的目录。
export APP_SOURCE_PATH=<SAMPLE_DIR>/resnet50_acl_cplusplus export DDK_PATH=/usr/local/Ascend/ascend-toolkit/latest export NPU_HOST_LIB=/usr/local/Ascend/ascend-toolkit/latest/aarch64-linux/devlib chmod +x sample_build.sh ./sample_build.sh
- 如果复制多行命令到MobaXterm执行,可能会出现是否确认执行的提示,单击"OK"即可;
- 如果执行脚本报错“ModuleNotFoundError: No module named 'PIL'”,则表示缺少Pillow库,请使用pip3 install Pillow --user命令安装Pillow库。
- 执行以下命令。
chmod +x sample_run.sh ./sample_run.sh
终端上屏显的结果如下,index表示类别标识、value表示该分类的最大置信度。
top 1: index[161] value[0.764648] top 2: index[162] value[0.156616] top 3: index[167] value[0.038971] top 4: index[163] value[0.021698] top 5: index[166] value[0.011887]
类别标签和类别的对应关系与训练模型时使用的数据集有关,本样例使用的模型是基于imagenet数据集进行训练的,用户可以在互联网上查阅对应数据集的标签及类别的对应关系。
当前屏显信息中的类别标识与类别的对应关系如下:
"161": ["basset", "basset hound"]
"162": ["beagle"]
"163": ["bloodhound", "sleuthhound"]
"166": ["Walker hound", "Walker foxhound"]
"167": ["English foxhound"]