打开微信,使用扫一扫进入页面后,点击右上角菜单,
点击“发送给朋友”或“分享到朋友圈”完成分享
知乎链接: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参数,代表推理结果对应的输入目标。详情参考用户手册中推理模块部分。
热门帖子
精华帖子