【CANN训练营快速上手实践操作记录】图片分类代码详解
收藏回复举报
【CANN训练营快速上手实践操作记录】图片分类代码详解
发表于2023-05-15 20:06:37
0 查看

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

代码结果

样例代码结构如下所示。

├── 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函数中。

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

#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类的头文件

#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.h和acl/acl.h,一个为封装的常用方法,一个为AscendCL; 该类除了构造函数和析构函数,还有两个公有方法和一个私有方法,以及三个私有成员变量。

构造函数

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

初始化私有成员变量。

InitResource

申请运行管理资源。

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

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; 
}

析构函数

用于释放资源

SampleProcess::~SampleProcess()
{
    DestroyResource();
}

DestroyResource

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

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接口调用,具体实现在进阶班中说明。

#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相关的接口,该部分将在进阶班中展开。

我要发帖子