×

签到

分享到微信

打开微信,使用扫一扫进入页面后,点击右上角菜单,

点击“发送给朋友”或“分享到朋友圈”完成分享

【CN-SDK04】寒武纪 CNStream 案例介绍 小飞人2023-07-24 16:45:34 回复 查看 社区交流 干货资源
【CN-SDK04】寒武纪 CNStream 案例介绍
分享到:

知乎链接:https://zhuanlan.zhihu.com/p/614071544


若是初学者,建议先初级部分,本课程的主要内容有:

  • CNStream 概述,案例介绍,二次开发以及实验。

  • 主要目的是希望大家了解如何编译和运行 CNStream ,了解何时需要二次开发,以及如何进行二次开发。

  • 依赖的知识点:了解寒武纪硬件平台,对寒武纪基础软件CNToolkit,CNCV,MagicMind有概念,了解基础的图像和人工智能相关知识。

1、概述

1)代码结构

CNStream开源,仓库位于github。仓库目录结构如图:



2)环境

OpenCV,FFMpeg,GFlags,GLog,librdkafka等。

寒武纪基础软件依赖:CNToolkit,CNCV,MagicMind

可通过 ${CNSTREAM_DIR}/tools 下的 pre_required_helper.sh 脚本进行安装。

编译:git clone代码,更新子仓,创建build文件夹,进入build文件夹后,cmake 及make。

git clone https://github.com/Cambricon/CNStream.gitcd ${CNSTREAM_DIR}git submodule update --init --remotemkdir build; cd buildcmake ..make

运行:以目标检测为例,进入samples/cns_launcher/ _detection目录,运行./run.sh mlu370 rtsp yolov3。

cd  ${CNSTREAM_DIR}/samples/cns_launcher/ _detection./run.sh  mlu370  rtsp  yolov3

2、示例介绍



  • samples目录结果如图,cns_launcher提供了一些常见的场景包括有,解码,分类,目标检测,目标追踪,车牌检测识别,车辆识别和姿态检测。一些通用的json配置文件在configs目录下。

  • common目录下存放的是示例提供的示例前后处理,二级过滤器,kafkaHandler,OsdHandler和姿态检测相关代码。

  • 另外还提供了multi_pipelines,展示如何在单进程中启动多pipeline。

  • simple_run_pipeline,展示如何在不使用json配置文件的情况下创建一个应用,更适合于业务场景固定的情况。

1 示例概述

cns_launcher

位于samples/cns_launcher。示例流程

  1. 首先解析json配置文件搭建pipeline。

  2. 设置message observer用于接受pipeline发给应用的信息,例如处理结束或出现异常等等。

  3. 启动pipeline,依次添加视频流。支持动态添加和删除视频流。

  4. 当视频流处理结束时,msg obsever将收到消息,并停止pipeline。

以目标检测为例,可以通过脚本执行,传入file list,例如我们传入两路本地视频文件和两路rtsp流。可以设置输入帧率(仅对mp4文件生效)。

${SAMPLES_DIR}/bin/cns_launcher  \
    --data_path ${SAMPLES_DIR}/files.list_video \
    --src_ _rate 25 \
    --config_fname ${CURRENT_DIR}/config.json \
    --logtostderr=true

其中files.list_video 如下:文件中每一行即为一路视频。上图中代表共4路视频,其中2路本地视频,2路rtsp流

/root/CNStream/samples/../data/video/cars.mp4/root/CNStream/samples/../data/video/cars.mp4rtsp://admin:abcd@123@192.168.80.47:554/h264/ch1/main/av_streamrtsp://admin:abcd@123@192.168.80.47:554/h264/ch2/main/av_stream

2 示例流程

run.sh 脚本中通过 config_fname 参数传入的 JSON 配置文件用于搭建Pipeline

有些示例中,配置文件 config.json并不存在,但我们可以找到一个名为 config_template.json的文件。

config_template.json为模版配置文件,run.sh根据传入的参数替换模版中的 placeholders,并生成 config.json作为配置文件。

执行 ./run.sh mlu370 rtsp yolov3后,根据run脚本传入的参数,NN 替换为 yolov3,platform placeholder替换为mlu370,sinker placehoder替换为rtsp。



config.json 中连接了4个子图:[decode]—>[ _detection]—>[osd_label_map_coco]—>[sinker]

decode解码,整体串行配置如下:

{
    "profiler_config" : {
        "enable_profiling" : true,
        "enable_tracing" : false
    },
  
    "subgraph:decode" : {
        "config_path" : "../configs/decode_config.json",
        "next_modules" : ["subgraph: _detection"]
    },
  
    "subgraph: _detection" : {
        "config_path" : "../configs/yolov3_ _detection_mlu370.json",
        "next_modules" : ["subgraph:osd_label_map_coco"]
    },
  
    "subgraph:osd_label_map_coco" : {
        "config_path" : "../configs/osd_configs/osd_label_map_coco.json",
        "next_modules" : ["subgraph:sinker"]
    },
  
    "subgraph:sinker" : {
        "config_path" : "../configs/sinker_configs/rtsp.json"
    }}

1)decode

{
    "source" : {
        "class_name" : "cnstream::DataSource",
        "custom_params" : {
            "bufpool_size" : 16,
            "interval" : 1,
            "device_id" : 0
        }
    }}

2) _detection 推理

{
    "detector" : {
        "class_name" : "cnstream::Inferencer",
        "parallelism" : 1,
        "max_input_queue_size" : 20,
        "custom_params" : {
            "model_path" : "../../../data/models/yolov3_v0.13.0_4b_rgb_uint8.magicmind",
            "preproc"  : "name=PreprocYolov3;use_cpu=false",
            "postproc" : "name=PostprocYolov3;threshold=0.5",
            "batch_timeout" : 200,
            "engine_num" : 4,
            "model_input_pixel_format" : "RGB24",
            "device_id" : 0
        }
    }}

3)osd_label_map_coco标签叠加

{
    "osd" : {
        "class_name" : "cnstream::Osd",
        "parallelism" : 1,
        "max_input_queue_size" : 20,
        "custom_params" : {
            "label_path" : "../../../../data/models/label_map_coco.txt"
        }
    }}

4)sinker 编码推流

{
    "venc" : {
        "class_name" : "cnstream::VEncode",
        "parallelism" : 1,
        "max_input_queue_size" : 10,
        "custom_params" : {
            "rtsp_port" : 9554,
            "device_id": 0
        }
    }}

CNStream支持分叉和汇聚的情况,如图所示。

当模块有多个并行下游模块时,通过next_modules设置,next_modules的值为一个字符串数组。



3 二级结构化示例

简单介绍一个二级结构化示例,车牌检测识别,整体流程如下:



1)起始模块DataSource

{
    "source" : {
        "class_name" : "cnstream::DataSource",
        "custom_params" : {
            "bufpool_size" : 16,
            "interval" : 1,
            "device_id" : 0
        }
    }}

2)目标检测 Detection:使用Yolov3网络,包含前后处理

{
    "detector" : {
        "class_name" : "cnstream::Inferencer",
        "parallelism" : 1,
        "max_input_queue_size" : 20,
        "custom_params" : {
            "model_path" : "../../../data/models/yolov3_v0.13.0_4b_rgb_uint8.magicmind",
            "preproc"  : "name=PreprocYolov3;use_cpu=false",
            "postproc" : "name=PostprocYolov3;threshold=0.5",
            "batch_timeout" : 200,
            "engine_num" : 4,
            "model_input_pixel_format" : "RGB24",
            "device_id" : 0
        }
    }}

3)车牌检测 Plate Detection,使用lpd网络。另外使用 FilterVideoStruct过滤二级目标,仅检测车。

{
    "plate_detector" : {
        "class_name" : "cnstream::Inferencer",
        "parallelism" : 1,
        "max_input_queue_size" : 20,
        "custom_params" : {
            "model_path" : "../../../data/models/mobilenet_ssd_plate_detection_v0.13.0_4b_bgr_fp32.magicmind",
            "engine_num" : 4,
            "batch_timeout" : 200,
            "preproc" : "name=PreprocSSDLpd;use_cpu=false",
            "postproc" : "name=PostprocSSDLpd",
            "model_input_pixel_format" : "TENSOR",
            "filter" : "name= FilterVideoStruct; categroies=2",
            "device_id" : 0
        }
    }}

3)车牌识别 Plate Detection,使用lpr网路。并对二级目标进行过滤,仅识别车牌。

{
    "plate_ocr" : {
        "class_name" : "cnstream::Inferencer",
        "parallelism" : 1,
        "max_input_queue_size" : 20,
        "custom_params" : {
            "model_path" : "../../../data/models/lprnet_v0.13.0_4b_bgr_uint8.magicmind",
            "engine_num" : 4,
            "batch_timeout" : 200,
            "preproc" : "name=PreprocLprnet;use_cpu=false",
            "postproc" : "name=PostprocLprnet",
            "model_input_pixel_format" : "BGR24",
            "filter" : "name= FilterLpr; categroies=1",
            "device_id" : 0
        }
    }}

4)标签叠加 Osd,使用自定义OsdHandlerVS

{
    "osd" : {
        "class_name" : "cnstream::Osd",
        "parallelism" : 1,
        "max_input_queue_size" : 20,
        "custom_params" : {
            "label_path" : "../../../data/models/label_map_coco.txt",
            "label_size" : "normal",
            "osd_handler": "OsdHandlerVS"
        }
    }}

5)sinker,可选择不同形式:编码视频文件,图片文件以及推流。



3、二次开发

本章我们将介绍二次开发相关内容,包括开发新模块,自定义前后处理,二级目标过滤,OsdHandler和KafkaHandler。

1 开发新模块

  1. 开发一个新模块时,需要多重继承 cnstream::Module 和 cnstream::ModuleCreator 两个基类。其中 Module 是所有模块的基类,ModuleCreator 实现了反射机制,使得可以通过类名创建实例。

  2. 重写 Open , Close 和 Process 函数。

  3. 将自定义的新插件添加进 Json 配置文件。

using namespace cnstream;class Example : public Module, public ModuleCreator<Example> {
 public:
  explicit Example(const std::string &name) : Module(name) { }
  bool Open(ModuleParamSet param_set) override { return true; }
  void Close() override { }
  int Process(std::shared_ptr<CN Info> data) override { return 0; }};

下面以 body pose osd 模块作为示例,介绍如何重载Process函数。Process函数是模块的主要逻辑部分。

首先通过CN Info 可获取视频帧数据 Data 。可从 collection 中获取自定义数据。可获取 Data 中保存的 cv::Mat BGR 图片。然后在 cv::Mat BGR 图片上画线。最后,return 0 代表成功。如失败,则return -1。



当自定义模块继承 Module 基类时,在 Process 函数结束后,由框架负责向下游传递数据。但存在一些场景,例如异步处理,需要模块自身控制数据传输的时机。

1.继承 ModuleEx类;

using namespace cnstream;class ExampleEx : public ModuleEx, public ModuleCreator<ExampleEx> {
 public:
  explicit ExampleEx(const std::string &name) : ModuleEx(name) { }
  bool Open(ModuleParamSet param_set) override { return true; }
  void Close() override { }
  int Process(std::shared_ptr<CN Info> data) override;};

2.处理结束后,主动调用 TransmitData;

3.在 Process 函数中处理 EOS(End of Stream) 帧;

4.必须保证数据流的顺序性

int ExampleEx::Process(std::shared_ptr<CN Info> data) {
  if (data->IsEos()) {
    TransmitData(data);
    return 0;
  }
  // process...
  TransmitData(data);
  return 0;}

2 自定义前后处理

1)自定义前处理

  1. 继承 Preproc 基类

  2. 重写 OnTensorParams 函数和Execute 函数。可通过OnTensorParams 获取一些模型信息。Execute 中实现前处理的主要逻辑部分。src为batch输入,dst为batch输出。次级推理时,src_rects保存输入对应的roi区域。详情参考用户手册中推理模块部分。

class PreprocYolov3 : public cnstream::Preproc {
 public:
  int OnTensorParams(const infer_server::CnPreprocTensorParams *params) override { return 0; }
  int Execute(cnedk::BufSurfWrapperPtr src,
                       cnedk::BufSurfWrapperPtr *dst,
                       const std::vector<CnedkTransformRect> &src_rects) override {
    // …    
    return 0;
  }
 private:
  DECLARE_REFLEX_ _EX(PreprocYolov3, cnstream::Preproc);};  // class PreprocYolov3IMPLEMENT_REFLEX_ _EX(PreprocYolov3, cnstream::Preproc);

2)自定义后处理

  1. 继承 Postproc 基类,

  2. 重写 Execute 函数。Execute 中实现后处理的主要逻辑部分。对于一级推理,net_outputs为输入参数,即模型batch输出,model_info模型信息,包括shape等,packages为数据对应的CN Info,我们需要将后处理结果填入其中并向后传递。labels为目标id对应的label。次级推理和一级推理相比,多了一个 s参数,代表推理结果对应的输入目标。详情参考用户手册中推理模块部分。


SyntaxHighlighter.all();
版权所有 © 2024 寒武纪 Cambricon.com 备案/许可证号:京ICP备17003415号-1