打开微信,使用扫一扫进入页面后,点击右上角菜单,
点击“发送给朋友”或“分享到朋友圈”完成分享
知乎链接:https://zhuanlan.zhihu.com/p/614071544
若是初学者,建议先初级部分,本课程的主要内容有:
CNStream 概述,案例介绍,二次开发以及实验。
主要目的是希望大家了解如何编译和运行 CNStream ,了解何时需要二次开发,以及如何进行二次开发。
依赖的知识点:了解寒武纪硬件平台,对寒武纪基础软件CNToolkit,CNCV,MagicMind有概念,了解基础的图像和人工智能相关知识。
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
samples目录结果如图,cns_launcher提供了一些常见的场景包括有,解码,分类,目标检测,目标追踪,车牌检测识别,车辆识别和姿态检测。一些通用的json配置文件在configs目录下。
common目录下存放的是示例提供的示例前后处理,二级过滤器,kafkaHandler,OsdHandler和姿态检测相关代码。
另外还提供了multi_pipelines,展示如何在单进程中启动多pipeline。
simple_run_pipeline,展示如何在不使用json配置文件的情况下创建一个应用,更适合于业务场景固定的情况。
cns_launcher
位于samples/cns_launcher。示例流程
首先解析json配置文件搭建pipeline。
设置message observer用于接受pipeline发给应用的信息,例如处理结束或出现异常等等。
启动pipeline,依次添加视频流。支持动态添加和删除视频流。
当视频流处理结束时,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
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的值为一个字符串数组。
简单介绍一个二级结构化示例,车牌检测识别,整体流程如下:
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,可选择不同形式:编码视频文件,图片文件以及推流。
本章我们将介绍二次开发相关内容,包括开发新模块,自定义前后处理,二级目标过滤,OsdHandler和KafkaHandler。
开发一个新模块时,需要多重继承 cnstream::Module 和 cnstream::ModuleCreator 两个基类。其中 Module 是所有模块的基类,ModuleCreator 实现了反射机制,使得可以通过类名创建实例。
重写 Open , Close 和 Process 函数。
将自定义的新插件添加进 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;}
1)自定义前处理
继承 Preproc 基类
重写 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)自定义后处理
继承 Postproc 基类,
重写 Execute 函数。Execute 中实现后处理的主要逻辑部分。对于一级推理,net_outputs为输入参数,即模型batch输出,model_info模型信息,包括shape等,packages为数据对应的CN Info,我们需要将后处理结果填入其中并向后传递。labels为目标id对应的label。次级推理和一级推理相比,多了一个 s参数,代表推理结果对应的输入目标。详情参考用户手册中推理模块部分。
热门帖子
精华帖子