本篇对基于Caffe ResNet-50网络实现图片分类的代码进行详细说明

代码结果

样例代码结构如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
├── data
│ ├── dog1_1024_683.jpg //测试数据,需要按指导获取测试图片,放到data目录下
│ ├── dog2_1024_683.jpg //测试数据,需要按指导获取测试图片,放到data目录下

├── inc
│ ├── model_process.h //声明模型处理相关函数的头文件
│ ├── sample_process.h //声明资源初始化/销毁相关函数的头文件
│ ├── utils.h //声明公共函数(例如:文件读取函数)的头文件

├── script
│ ├── transferPic.py //将*.jpg转换为*.bin,同时将图片从1024*683的分辨率缩放为224*224

├── src
│ ├── acl.json //系统初始化的配置文件
│ ├── CMakeLists.txt //编译脚本
│ ├── main.cpp //主函数,图片分类功能的实现文件
│ ├── model_process.cpp //模型处理相关函数的实现文件
│ ├── sample_process.cpp //资源初始化/销毁相关函数的实现文件
│ ├── utils.cpp //公共函数(例如:文件读取函数)的实现文件

├── .project //工程信息文件,包含工程类型、工程描述、运行目标设备类型等
├── CMakeLists.txt //编译脚本,调用src目录下的CMakeLists文件

transferPic.py

转换图片格式及分辨率,主要方法位于process函数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def process(input_path):
try:
input_image = Image.open(input_path) # 读入图片
input_image = input_image.resize((256, 256)) # 缩放到256*256
# hwc
img = np.array(input_image) # 转为ndarray格式
height = img.shape[0] # 获取图片高度
width = img.shape[1] # 获取图片宽度
h_off = int((height-224)/2) # 计算高度偏移量,用于中心裁剪出224*224大小图片
w_off = int((width-224)/2) # 计算宽度偏移量,用于中心裁剪出224*224大小图片
crop_img = img[h_off:height-h_off, w_off:width-w_off, :] # 裁剪图像
# rgb to bgr
img = crop_img[:, :, ::-1] # 通道转换,RGB转为BGR
shape = img.shape # 获取HWC
img = img.astype("float16") # 转为FP16
img[:, :, 0] -= 104 # B通道标准化
img[:, :, 1] -= 117 # G通道标准化
img[:, :, 2] -= 123 # R通道标准化
img = img.reshape([1] + list(shape)) # NHWC
result = img.transpose([0, 3, 1, 2]) # NCHW
output_name = input_path.split('.')[0] + ".bin" # 保存
result.tofile(output_name)
except Exception as except_err:
print(except_err)
return 1
else:
return 0

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>                                  # 引入头文件 
#include "sample_process.h"
#include "utils.h"
using namespace std;
bool g_isDevice = false;

int main()
{
SampleProcess sampleProcess;
Result ret = sampleProcess.InitResource(); # 初始化资源
if (ret != SUCCESS) {
ERROR_LOG("sample init resource failed");
return FAILED;
}

ret = sampleProcess.Process(); # 处理
if (ret != SUCCESS) {
ERROR_LOG("sample process failed");
return FAILED;
}

INFO_LOG("execute sample success");
return SUCCESS;
}

上述代码主要用于初始化资源和启动程序,主要过程被封装于sampleProcess类中。

sample_process.cpp

首先看sampleProcess类的头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#pragma once
#include "utils.h"
#include "acl/acl.h"

class SampleProcess {
public:
/**
* @brief Constructor
*/
SampleProcess();

/**
* @brief Destructor
*/
virtual ~SampleProcess();

/**
* @brief init reousce
* @return result
*/
Result InitResource();

/**
* @brief sample process
* @return result
*/
Result Process();

private:
void DestroyResource();

int32_t deviceId_;
aclrtContext context_;
aclrtStream stream_;
};

需要引入utils.hacl/acl.h,一个为封装的常用方法,一个为AscendCL;
该类除了构造函数和析构函数,还有两个公有方法和一个私有方法,以及三个私有成员变量。

构造函数

1
2
3
SampleProcess::SampleProcess() :deviceId_(0), context_(nullptr), stream_(nullptr)
{
}

初始化私有成员变量。

InitResource

申请运行管理资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Result SampleProcess::InitResource()
{
// ACL init
const char *aclConfigPath = "../src/acl.json"; # 配置文件
aclError ret = aclInit(aclConfigPath); # 初始化
if (ret != ACL_SUCCESS) {
ERROR_LOG("acl init failed, errorCode = %d", static_cast<int32_t>(ret));
return FAILED;
}
INFO_LOG("acl init success");

// set device
ret = aclrtSetDevice(deviceId_); # 指定设备
if (ret != ACL_SUCCESS) {
ERROR_LOG("acl set device %d failed, errorCode = %d", deviceId_, static_cast<int32_t>(ret));
return FAILED;
}
INFO_LOG("set device %d success", deviceId_);

// create context (set current)
ret = aclrtCreateContext(&context_, deviceId_); # 创建context
if (ret != ACL_SUCCESS) {
ERROR_LOG("acl create context failed, deviceId = %d, errorCode = %d",
deviceId_, static_cast<int32_t>(ret));
return FAILED;
}
INFO_LOG("create context success");

// create stream
ret = aclrtCreateStream(&stream_); # 创建stream
if (ret != ACL_SUCCESS) {
ERROR_LOG("acl create stream failed, deviceId = %d, errorCode = %d",
deviceId_, static_cast<int32_t>(ret));
return FAILED;
}
INFO_LOG("create stream success");

// get run mode
// runMode is ACL_HOST which represents app is running in host
// runMode is ACL_DEVICE which represents app is running in device
aclrtRunMode runMode;
ret = aclrtGetRunMode(&runMode); # 获取软件栈的运行模式
if (ret != ACL_SUCCESS) {
ERROR_LOG("acl get run mode failed, errorCode = %d", static_cast<int32_t>(ret));
return FAILED;
}
g_isDevice = (runMode == ACL_DEVICE);
INFO_LOG("get run mode success");
return SUCCESS;
}

Process

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
Result SampleProcess::Process()
{
// model init
ModelProcess modelProcess; # 封装模型相关的处理过程
const char* omModelPath = "../model/resnet50.om"; # 模型目录
Result ret = modelProcess.LoadModel(omModelPath); # 加载模型
if (ret != SUCCESS) {
ERROR_LOG("execute LoadModel failed");
return FAILED;
}

ret = modelProcess.CreateModelDesc(); # 获取模型描述信息
if (ret != SUCCESS) {
ERROR_LOG("execute CreateModelDesc failed");
return FAILED;
}

string testFile[] = { # 测试图片
"../data/dog1_1024_683.bin",
"../data/dog2_1024_683.bin"
};

size_t devBufferSize; # 用于存放输入大小
void *picDevBuffer = nullptr;
ret = modelProcess.GetInputSizeByIndex(0, devBufferSize); # 获取指定输入的大小
if (ret != SUCCESS) {
ERROR_LOG("execute GetInputSizeByIndex failed");
return FAILED;
}

aclError aclRet = aclrtMalloc(&picDevBuffer, devBufferSize, ACL_MEM_MALLOC_NORMAL_ONLY); # 申请内存
if (aclRet != ACL_SUCCESS) {
ERROR_LOG("malloc device buffer failed. size is %zu, errorCode is %d",
devBufferSize, static_cast<int32_t>(aclRet));
return FAILED;
}

ret = modelProcess.CreateInput(picDevBuffer, devBufferSize); # 创建输入
if (ret != SUCCESS) {
ERROR_LOG("execute CreateInput failed");
aclrtFree(picDevBuffer);
return FAILED;
}

ret = modelProcess.CreateOutput(); # 创建输出
if (ret != SUCCESS) {
aclrtFree(picDevBuffer);
ERROR_LOG("execute CreateOutput failed");
return FAILED;
}

for (size_t index = 0; index < sizeof(testFile) / sizeof(testFile[0]); ++index) {
INFO_LOG("start to process file:%s", testFile[index].c_str());
// copy image data to device buffer
ret = Utils::MemcpyFileToDeviceBuffer(testFile[index], picDevBuffer, devBufferSize); # 移入device buffer
if (ret != SUCCESS) {
aclrtFree(picDevBuffer);
ERROR_LOG("memcpy device buffer failed, index is %zu", index);
return FAILED;
}

ret = modelProcess.Execute(); # 执行处理
if (ret != SUCCESS) {
ERROR_LOG("execute inference failed");
aclrtFree(picDevBuffer);
return FAILED;
}

// Print the top 5 confidence values with indexes.
// Use function [DumpModelOutputResult] if you want to dump results to file in the current directory.
modelProcess.OutputModelResult(); # 获得输出

}
// release model input output
modelProcess.DestroyInput(); # 释放输入
modelProcess.DestroyOutput(); # 释放输出

aclrtFree(picDevBuffer); # 释放内存
return SUCCESS;
}

析构函数

用于释放资源

1
2
3
4
SampleProcess::~SampleProcess()
{
DestroyResource();
}

DestroyResource

释放运行管理资源,与InitResource顺序相反。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void SampleProcess::DestroyResource()
{
aclError ret;
if (stream_ != nullptr) {
ret = aclrtDestroyStream(stream_);
if (ret != ACL_SUCCESS) {
ERROR_LOG("destroy stream failed, errorCode = %d", static_cast<int32_t>(ret));
}
stream_ = nullptr;
}
INFO_LOG("end to destroy stream");

if (context_ != nullptr) {
ret = aclrtDestroyContext(context_);
if (ret != ACL_SUCCESS) {
ERROR_LOG("destroy context failed, errorCode = %d", static_cast<int32_t>(ret));
}
context_ = nullptr;
}
INFO_LOG("end to destroy context");

ret = aclrtResetDevice(deviceId_);
if (ret != ACL_SUCCESS) {
ERROR_LOG("reset device %d failed, errorCode = %d", deviceId_, static_cast<int32_t>(ret));
}
INFO_LOG("end to reset device %d", deviceId_);

ret = aclFinalize();
if (ret != ACL_SUCCESS) {
ERROR_LOG("finalize acl failed, errorCode = %d", static_cast<int32_t>(ret));
}
INFO_LOG("end to finalize acl");
}

model_process.cpp

首先看头文件,主要包括与模型相关的ACL接口调用,具体实现在进阶班中说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#pragma once
#include <iostream>
#include "utils.h"
#include "acl/acl.h"

class ModelProcess {
public:
/**
* @brief Constructor
*/
ModelProcess();

/**
* @brief Destructor
*/
virtual ~ModelProcess();

/**
* @brief load model
* @param [in] modelPath: model path
* @return result
*/
Result LoadModel(const char *modelPath);

/**
* @brief unload model
*/
void UnloadModel();

/**
* @brief create model desc
* @return result
*/
Result CreateModelDesc();

/**
* @brief destroy desc
*/
void DestroyModelDesc();

/**
* @get input size by index
* @param [in] index: input index
* @param [out] inputSize: input size of index
* @return result
*/
Result GetInputSizeByIndex(const size_t index, size_t &inputSize);

/**
* @brief create model input
* @param [in] inputDataBuffer: input buffer
* @param [in] bufferSize: input buffer size
* @return result
*/
Result CreateInput(void *inputDataBuffer, size_t bufferSize);

/**
* @brief destroy input resource
*/
void DestroyInput();

/**
* @brief create output buffer
* @return result
*/
Result CreateOutput();

/**
* @brief destroy output resource
*/
void DestroyOutput();

/**
* @brief model execute
* @return result
*/
Result Execute();

/**
* @brief dump model output result to file
*/
void DumpModelOutputResult();

/**
* @brief print model output result
*/
void OutputModelResult();

private:
uint32_t modelId_;
size_t modelWorkSize_; // model work memory buffer size
size_t modelWeightSize_; // model weight memory buffer size
void *modelWorkPtr_; // model work memory buffer
void *modelWeightPtr_; // model weight memory buffer
bool loadFlag_; // model load flag
aclmdlDesc *modelDesc_;
aclmdlDataset *input_;
aclmdlDataset *output_;
};
  • ModelProcess构造函数,用于私有成员变量的初始化;
  • CreateModelDesc创建模型描述信息;
  • LoadModel加载模型;
  • Execute运行模型;
  • OutputModelResult获取模型输出;
  • ~ModelProcess析构函数,用于卸载模型、销毁模型描述信息、销毁输入、销毁输出(释放内存)。

结束语

本篇对图片分类的代码进行了说明,介绍了代码运行逻辑,但未进一步介绍ACL相关的接口,该部分将在进阶班中展开。