下载
中文
注册

流程编排开发方式

样例介绍

本样例以Atlas 推理系列产品为例,通过Vision SDK图像分类案例,介绍如何使用Vision SDK流程编排方式开发推理应用。案例使用ResNet-50模型对图片进行分类并最后输出分类结果。

ResNet-50模型的基本介绍如下:

  • 输入数据:RGB格式、224*224分辨率的输入图片。
  • 输出数据:图片的类别标签及其对应置信度(置信度是指图片所属某个类别可能性)。

准备工作

  1. 请先完成Vision SDK安装部署后,再进行快速入门样例。
    表1 环境要求软件依赖

    软件依赖名称

    推荐版本

    获取链接

    操作系统

    请参见支持的硬件和操作系统

    -

    系统依赖

    -

    Ubuntu系统CentOS系统

    CANN开发套件包

    8.0.RC3

    CANN获取链接

    npu-driver驱动包

    Ascend HDK 24.1.RC3

    单击获取链接在左侧配套资源的“编辑资源选择”中进行配置,筛选配套的软件包,确认版本信息后获取所需软件包。

    请参见各硬件产品中驱动和固件安装升级指南获取对应的指导。

    npu-firmware固件包

    Ascend HDK 24.1.RC3

  2. 获取样例代码。本样例支持Atlas 推理系列产品上运行。

    请访问获取链接,获取样例代码压缩包。

  3. 以普通用户登录已安装Vision SDK的开发环境并将样例代码压缩包上传。
  4. 解压样例代码压缩包,进入解压后的目录,命令参考如下。
    unzip pipelineSample.zip 
    cd pipelineSample

    样例代码目录结构参考如下。样例代码目录结构参考如下,其中om模型仅供示例,用户可通过模型转换功能(ATC)转换为om模型进行推理。

    pipelineSample
    ├── data
    │   ├── dog1_1024_683.jpg            // 测试图片
    
    ├── models                        //存放模型目录
    │   ├── resnet50_tensorflow_1.7.om      //om模型文件
    │   ├── resnet50_aipp_tf.cfg     //模型配置文件
    │   ├── resnet50_clsidx_to_labels.names   //模型输出类别名称文件   
    
    ├── pipeline                     // 存放pipeline文件
    │   ├── Sample.pipeline        // pipeline文件 
                                       
    ├── src
    │   ├── CMakeLists.txt              // CMakeLists文件
    │   ├── main.cpp                    // 主函数,图片分类功能的实现文件
    │   ├── run.sh                   // 编译脚本
  5. 准备用于推理的图片数据。

    用户需使用自行获取的图片进行测试(请将获取的图片名称更名为与样例代码的图片名字一致,如dog1_1024_683.jpg),以下图片为展示用途。

    图1 典型样本图片

编排pipeline文件

编排pipeline文件是使用Vision SDK开发应用最核心的任务,图像分类应用可拆解为一系列的业务流程,通过编辑pipeline文件,调用Vision SDK插件库完成推理业务,本文的pipeline文件内容以图2中所示的业务流程进行样例配置编排。

图2 业务流程编排

样例如下所示。

{
  "classification": {                // 修改"classification" 为当前业务推理流程的名称
    "stream_config": {
      "deviceId": "0"                // "deviceId" 表示要使用的芯片的ID号
    },
    "appsrc0": {                     // "appsrc0" 表示输入元件名称
      "props": {                     // "props"指元件属性
        "blocksize": "409600"        // 每个buffer读取的大小
      },
      "factory": "appsrc",           // "factory" 定义该元件类型
      "next": "mxpi_imagedecoder0"   // "next" 填写连接的下游元件--图像解码元件
    },
    "mxpi_imagedecoder0": {          // 图像解码元件名称,0表示编号,如果一个流程中要使用多个图像解码元件,可以依次按照0、1、2...命名
      "props": {
        "handleMethod": "opencv"     // 解码方法为opencv
      },
      "factory": "mxpi_imagedecoder",// 使用图像解码插件
      "next": "mxpi_imageresize0"    // "next" 填写连接的下游元件--图像缩放元件
    },
    "mxpi_imageresize0": {           // 图像缩放元件名称
      "props": {
        "handleMethod": "opencv",    // 解码方法为opencv
        "resizeHeight": "280",       // 指定缩放后的高
        "resizeWidth": "280",        // 指定缩放后的宽
        "resizeType": "Resizer_Stretch"// 缩放方式为拉伸缩放
      },
      "factory": "mxpi_imageresize", // 使用图像缩放插件
      "next": "mxpi_opencvcentercrop0" // "next" 填写连接的下游元件--图像中心裁剪元件
    },
    "mxpi_opencvcentercrop0": {                              // 图像中心裁剪元件名称
      "props": {
          "dataSource": "mxpi_imageresize0",                 // "dataSource"填写连接的上游元件--图像缩放元件
          "cropHeight": "224",                               // 裁剪出的图片高
          "cropWidth": "224"                                 // 裁剪出的图片宽
      },
      "factory": "mxpi_opencvcentercrop",                    // 使用图像中心裁剪元件插件
      "next": "mxpi_tensorinfer0"                            // "next"填写连接的下游元件--模型推理元件
    },
    "mxpi_tensorinfer0": {                                   // 模型推理元件名称
      "props": {                                             // "props"指元件属性,可以加载指定目录中的文件
        "dataSource": "mxpi_opencvcentercrop0",              // "dataSource"填写连接的上游元件--图像中心裁剪元件
        "modelPath": "../models/resnet50_tensorflow_1.7.om", // "modelPath" 属性定义了推理业务使用的模型,用户需要根据获取的模型修改文件名
        "waitingTime": "2000",                               // 多batch模型可容忍的等待组BATCH时间
        "outputDeviceId": "-1"                               // 内存拷贝到指定位置,设为-1则拷贝至Host侧
      },
      "factory": "mxpi_tensorinfer",                         // 使用模型推理插件
      "next": "mxpi_classpostprocessor0"                     // "next"填写连接的下游元件--模型后处理元件
    },
    "mxpi_classpostprocessor0": {                            // 模型后处理元件名称
      "props": {                                             // "props"指元件属性,可以加载指定目录中的文件
        "dataSource": "mxpi_tensorinfer0",                   // "dataSource"填写连接的上游元件--模型推理元件
        "postProcessConfigPath": "../models/resnet50_aipp_tf.cfg",// "postProcessConfigPath" 指定模型后处理配置文件
        "labelPath": "../models/resnet50_clsidx_to_labels.names", // "labelPath" 指定模型输出的类别名称文件
        "postProcessLibPath": "libresnet50postprocess.so"    // "postProcessLibPath" 指定模型后处理依赖的动态库
      },
      "factory": "mxpi_classpostprocessor",                  // 使用模型后处理插件
      "next": "mxpi_dataserialize0"                          // "next" 填写连接的下游元件--序列化元件
    },
    "mxpi_dataserialize0": {                                 // 序列化元件名称
      "props": {
        "outputDataKeys": "mxpi_classpostprocessor0"         // "outputDataKeys" 指定需要输出的数据的索引
      },
      "factory": "mxpi_dataserialize",                       // 使用序列化插件
      "next": "appsink0"                                     // "next" 填写连接的下游元件--输出元件
    },
    "appsink0": {                                            // 输出元件名称
      "props": {
        "blocksize": "4096000"                               // 每个buffer读取的大小
      },
      "factory": "appsink"                                   // 使用输出插件
    }
  }
}

pipeline文件中的注释仅用于辅助理解,在编写pipeline文件时,请删除其中的注释文字,否则会导致文件解析失败。

在这段pipeline中,有以下关键概念:

  • “next”属性值指明了元件之间的连接关系。
  • 用户通过“appsrc0”元件向Stream发送数据,通过“appsink0”元件从Stream获取推理结果。
  • “mxpi_classpostprocessor0”元件用于对模型推理的输出张量进行后处理,比如在上述样例中,模型后处理元件处理上游模型推理元件输出的一维张量,最终返回模型的识别结果(类别ID、名称、置信度)。
  • “mxpi_dataserialize0”元件将推理结果组装成JSON字符串输出。

代码解析

“pipelineSample/src”目录中的“main.cpp”文件为应用程序源码。

在本样例中,关键步骤与代码参考如下,不可以直接拷贝编译运行,需要根据实际情况修改pipeline文件路径、输入图片路径、Stream名称,Stream名称需要与pipeline文件中的业务推理流程的名称匹配,如上述pipeline文件的业务推理流程的名称为“classification”。完整样例代码请参考样例文件。
int main(int argc, char* argv[])
{
    // 1. 判断是否使用Atlas 推理系列产品
    if (!MxBase::DeviceManager::IsAscend310P()) {
        LogError << "Current demo only support on Ascend310P, please check!";
        return APP_ERR_INVALID_DEVICE;
    }
    // 2.解析pipeline文件
    std::string pipelineConfigPath = "../pipeline/Sample.pipeline";  // 修改pipeline文件路径  
    std::string pipelineConfig = ReadPipeline(pipelineConfigPath);
    if (pipelineConfig == "") {
        LogError << "Read pipeline failed.";
        return APP_ERR_COMM_INIT_FAIL;
    }
    // 3.初始化stream manager
    MxStream::MxStreamManager mxStreamManager;
    APP_ERROR ret = mxStreamManager.InitManager();
    if (ret != APP_ERR_OK) {
        LogError << GetError(ret) << "Failed to init Stream manager.";
        return ret;
    }
    // 4.创建stream
    ret = mxStreamManager.CreateMultipleStreams(pipelineConfig);
    if (ret != APP_ERR_OK) {
        LogError << GetError(ret) << "Failed to create Stream.";
        mxStreamManager.DestroyAllStreams();
        return ret;
    }
    // 5.读取待推理图片
    MxStream::MxstDataInput dataBuffer;
    ret = ReadFile("../data/dog1_1024_683.jpg", dataBuffer);    // 修改输入图片路径
    if (ret != APP_ERR_OK) {
        LogError << GetError(ret) << "Failed to read image file.";
        mxStreamManager.DestroyAllStreams();
        return ret;
    }
    std::string streamName = "classification";    // 修改业务推理流程的名称
    int inPluginId = 0;
    // 6.发送待推理图片至stream
    ret = mxStreamManager.SendData(streamName, inPluginId, dataBuffer);
    if (ret != APP_ERR_OK) {
        LogError << GetError(ret) << "Failed to send data to stream.";
        delete dataBuffer.dataPtr;
        dataBuffer.dataPtr = nullptr;
        mxStreamManager.DestroyAllStreams();
        return ret;
    }
    // 7.获取推理结果
    MxStream::MxstDataOutput* output = mxStreamManager.GetResult(streamName, inPluginId);
    if (output == nullptr) {
        LogError << "Failed to get pipeline output.";
        delete dataBuffer.dataPtr;
        dataBuffer.dataPtr = nullptr;
        mxStreamManager.DestroyAllStreams();
        return ret;
    }
    std::string result = std::string((char *)output->dataPtr, output->dataSize);
    std::cout << "Results:" << result << std::endl;
    // 8.销毁stream,并释放资源
    mxStreamManager.DestroyAllStreams();
    delete dataBuffer.dataPtr;
    dataBuffer.dataPtr = nullptr;
    delete output;
    return 0;
}

编译和运行应用

  1. 以普通用户登录,进入“pipelineSample/src”目录。
  2. 配置环境变量(以CANN的默认安装路径“/usr/local/Ascend/ascend-toolkit”Vision SDK的安装路径/home/mxVision-{version}为例)。
    source /usr/local/Ascend/ascend-toolkit/set_env.sh
    source /home/mxVision-{version}/set_env.sh
  3. 运行应用,执行编译脚本。
    chmod +x run.sh
    ./run.sh
    终端上屏显的结果如下,“classId”表示类别号、“className”表示类名称,“confidence”表示该分类的最大置信度:
    Results: {
        "MxpiClass": [{
            "classId": 163,
            "className": "beagle",
            "confidence": 0.86181640599999998
        }]
    }

    类别标签和类别的对应关系与训练模型时使用的数据集有关,本样例的模型基于Imagenet数据集进行训练,您可以在互联网上查阅对应数据集的标签及类别的对应关系。

    当前屏显信息中的类别标识与类别的对应关系如下:

    • "160" ["Rhodesian ridgeback"]
    • "161" ["Afghan hound, Afghan"]
    • "162" ["basset, basset hound"]
    • "163" ["beagle"]
    • "164" ["bloodhound, sleuthhound"]
    • "165" ["bluetick"]
    • "166" ["black-and-tan coonhound"]