EmxAiPlugin对接手册

1.介绍

  EmxAiPlugin是设计的一个独立模块,它以模块化和可扩展的库形式,集成于EmxMediaServer中。

模块

  EmxAiPlugin将Ai算法功能的实现划分为若干个模块,模块可以自由组合,这些模块都遵循MediaServerAiChn的回调函数(虚函数),供芯片厂商实现

配置文件

  配置文件统一放在 ${EMX_DATA_DIR}/AiPlugin/configs 路径,内容格式由开发厂商自由设定,满足自身算法即可!   在模块的回调函数中使用如下方法进行配置文件的加载,m_chn是此模块当前的通道号,文件名和通道号启动的时候由MediaServer传入

Json::Value root;
char path[EMX_MAX_PATH_SIZE] = {};
snprintf(path, sizeof(path), "%s/AiPlugin/configs/%s.json", SysEnv::GetDataDir(), m_name.c_str());
auto e = EasyJson::Load(path, root);
if (e != ErrCodeE::Success) {
emxloge("get media %s configuration failed\n", path);
return;
}
auto &json = root[m_chn];

通道号

  针对上面配置文件的例子,这里提出通道的概念,关于各个模块的通道号m_chn,这个通道号是一个虚拟的概念,指的并不是实际的物理通道,模块的虚拟通道号就是json文件中json对象数组的索引下标

[
{
"vpssGrp": 2,
"vpssChn": 0,
"interval": 25,
"threshold": 32,
"minArea": 128
}
]

参数文件

  参数文件统一放在 ${EMX_DATA_DIR}/AiPlugin/configs 路径,内容格式固定,由应用层修改变量!   参数文件名为 param.json,备用配置paran_bak.json,默认配置param_def.json,AiPlugin开发人员需要提供param_def.json。   如果param.json 不存在时为依次查询备用配置和默认配置, 配置文件由MediaServer获取json句柄,然后通过Create(const char *workspace, Json::Value &param) 或 SetParam(const Json::Value &paramNew) 传入

[
{
"ena": true,
"MotionDetection": {
"ena": false,
"threshold": 32
},
"PersonDetection": {
"ena": true
},
"InvasionDetection": {
"ena": false,
"invasionType": 1,
"captureBackImg": false,
"captureRectImg": false,
"captureQuality": 70,
"map": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
},
"CrossingDetection": {
"ena": false,
"crossType": 1,
"captureBackImg": false,
"captureRectImg": false,
"captureQuality": 70,
"lineX1": 0,
"lineY1": 5000,
"lineX2": 10000,
"lineY2": 5000,
"direction": 0
},
"PassengerFlowStatistics": {
"ena": false,
"captureBackImg": false,
"captureRectImg": false,
"captureQuality": 70,
"lineX1": 0,
"lineY1": 5000,
"lineX2": 10000,
"lineY2": 5000,
"direction": 0
},
"FaceDetection": {
"ena": true,
"minWidth": 40,
"minHeight": 40
},
"FaceRecognition": {
"ena": false,
"threshold": 50,
"snapMode":0,
"snapTime":10,
"snapQuality":20,
"snapClarity":70,
"snapAngle":60,
"captureBackImg": true,
"captureRectImg": true,
"captureQuality": 70,
"aeEna":true,
"aeThreshold":60,
"minWidth":80,
"minHeight":80,
"maskEna":true,
"maskThreshold": 60,
"recognitionEna":true
},
"LicensePlateRecognition": {
"ena": false,
"threshold": 60,
"captureBackImg": true,
"captureRectImg": true,
"captureQuality": 70
},
"PersonVehicleNonDetection": {
"ena": false,
"threshold": 60,
"captureBackImg": true,
"captureRectImg": true,
"captureQuality": 70
},
"VehicleRecognition": {
"ena": false,
"threshold": 60,
"captureBackImg": true,
"captureRectImg": true,
"captureQuality": 70
}
}
]

profile

  Ai对接的过程中,各个算法都有自身的配置文件,因此很多情况下,新的需求可以仅通过修改配置文件即可满足,但同样存在不能仅靠修改配置文件就能满足需求的情况。因此,这里提出profile的概念,一个profile就是某款芯片的一个完整的代码实现,起初,新的芯片接入只需要实现chipXDefault(chipX为芯片系列)这个默认的profile即可,当出现无法通过配置文件满足的需求时,可能某些模块的代码需要进行一些特殊的定制化的修改,当面临这样的修改时,就可以新建一个profile,姑且命名为profileA,profileA可以继承chipXDefault通用的部分,然后override自身特殊的部分,从而实现特殊的功能,同样的也可建立profileBprofileC等等,他们之间可以有多层次的继承关系 >注意:profile的命名应当是芯片系列名称+合适的后缀,例如hi351xDouStitch,cv182xDefault等

2.工程搭建

  芯片厂商拿到EmxSdk以及Emx3dr后就可以愉快的进行对接开发了

拷贝工程模板

  将EmxSdk中的EmxAiPluginDevelop拷贝到一个让自己舒适的地方   然后进入到scripts目录执行下面的操作,这一步是为了将依赖的EmxSdk和Emx3dr目录软连接到EmxAiPluginDevelop工程目录下,方便为后续编译和打包提供一致性环境(不然编不过的)

cd scripts
chmod +x *
./softLink.py the/path/of/Public

下面来介绍一下这个工程里面的内容

├── Public # 刚刚软链接过来的,里面存放了各个EmxSdk版本和依赖库
├── CMakeLists.txt # 工程编译的顶层cmake文件,不需要改动
├── EmxAiPluginVersion # 版本号记录文件,不需要理会,每次编译自动更新
├── chip # 这个下面全是代码,存放不同芯片厂商以及芯片系列的模块回调源文件
│   └── manufactoryX # 这里假设厂商名是manufactoryX
│   └── chipX # 这里假设芯片系列是chipX
│ ├── CrossingInvasionComm.cpp #入侵越界算法
│ ├── CrossingInvasionComm.hpp
│ ├── FaceAlgorithm.cpp #人脸算法
│ ├── FaceAlgorithm.hpp
│   ├── MotionDetection.cpp #移动侦测算法
│   ├── MotionDetection.hpp
│ ├── PassengerFlowStatistics.cpp #客流统计算法
│ ├── PassengerFlowStatistics.hpp
│   ├── PersonDetection.cpp #人形侦测算法
│   └── PersonDetection.hpp
│   ├── chipXDefault.cpp # 默认profile实现的算法类型
│   └── chipXDefault.hpp
├── common # 这里面用于存放一些通用的代码
├── profile # 这里面是每个profile的各种配置文件
│   └── chipXDefault # chipXDefault配置文件
│   ├── build.cmake # profile的cmake构建配置文件,需要修改
│   ├── configs
│   │   ├── config.json #配置参数,暂时没用
│ │ ├── CrossingInvasionComm.json #入侵越界侦测配置
│ │ ├── FaceAlgorithm.json #人脸配置
│ │ ├── PassengerFlowStatistics.json #客流统计配置
│   │   ├── MotionDetection.json #移动侦测配置
│   │   ├── param.def.json #默认参数
│   │   ├── PersonDetection.json #人形侦测配置
│   │   └── range.json #参数范围
│   └── profile.json # profile的信息,需要修改
└── scripts # 各种脚本
├── build.py # 单独profile编译
├── release.py # 所有profile编译并发布
├── softLink.py # 软连接
└── utils.py # 工具

编译测试

  此时将profile/chipXDefault/profile.jsonEmxSdkVersion的值修改为EmxSdk目录中的最新版本,例如"EmxSdk-20231031-1",然后进入scripts目录下执行:

./build.py

由于没有指定参数,此时应当会出现下面的打印,profile list列出了当前已经实现的profile有哪些(通过遍历profile目录)

usage:./build.py profile_index
eg. ./build.py 0
-------------------profile list-------------------
0:chipXDefault

此时我们执行build命令并携带profile的索引号,编译chipXDefault

./build.py 0

一通编译打印之后应该会在EmxAiPluginDevelop/output目录下生成了EmxAiPlugin-Debug文件夹,这里面包含了一个完整的profile的输出

如果没编过,看看cmake版本是否正确,EmxSdk版本是否正确,softLink的两个路径是否正确...

发布测试

  执行下面命令即可,会将所有的profile目录下的所有profile进行编译打包

./release.py

最终输出包含版本信息的所有支持的profile

EmxAiPlugin-xxxxxxxx-x
└── chipXDefault

这个EmxAiPlugin-xxxxxxxx-x就是芯片对接最终版本交付文件

3.工程配置

  通过工程的搭建,目前可以完成chipXDefault的发布,接下来通过工程配置,就可以完成芯片厂商即将对接芯片的发布,期间也会对各种工程配置文件如何修改和编写进行详细的说明

命名

  首先要对即将对接芯片系列进行命名,包括厂商-芯片系列-默认profile,可以参考如下定义方式,**后面的所有说明均以此为例**

厂商-ChipManufactory:cvitek
芯片系列-ChipSerial:cv181x
默认profile-Profile:cv181xDefault

填充Emx3dr/chip

  Emx3dr/chip中存储着各个芯片厂商自己的sdk文件,对接前需要将芯片厂商自己的sdk中的头文件/库文件/sample文件(如果对接需要用到并编译这些代码)/驱动文件 按照格式放入到Emx3dr/chip中,路径依次是chip-cvitek-cv181x-sdk版本号-头文件+库文件+驱动+[sample],这样才能保证后面工程的顺利编译,像下面这样:

chip
├── cvitek
│   ├── cv181x
│   │   ├── 4.1.5_debug
│   │   └── ai
│   └── cv182x
│   ├── 2.1.1.19
│   └── 2.1.1.31
├── manufactoryX
│   └── chipX
│   ├── 0.0.0.0
│   └── ai
└── sigmastar
└── ssc377qe
└── 1.2.0
...

其中sdk版本号按照厂商自己的规则来就行

建立profile

  进入EmxAiPluginDevelop/profile中,将chipXDefault复制为cv181xDefault   进入EmxAiPluginDevelop/chip中,复制manufactoryX为cvitek,然后将cvitek下的chipX统一替换为cv181x,chipXDefault替换为cv181xDefault,包括:

  • chipX目录名
  • chipXDefault目录名
  • chipXDefault.xpp文件名及其中代码内容

编写profile.json

  EmxAiPluginDevelop/profile/cv181xDefault/profile.json文件存储了工程在编译打包过程中需要的必要信息,厂商需要按照实际情况完成修改,内容示例如下:

{
"EmxSdkVersion": "EmxSdkDebug",
"ChipManufactory": "cvitek",
"ChipSerial": "cv181x",
"ChipSdkVersion": "4.1.5_debug",
"ChipAiVersion": "4.1.5_debug",
"Cross": "riscv64-unknown-linux-musl",
"CrossPath": "",
"comment0": "runtime中保存的是插件运行过程中需要的所有二进制文件,运行前会对其md5进行校验",
"comment1": "runtime中所有文件需存放在Emx3dr/chip/manufactoryX/chipX/ai/default_0.0.1目录中",
"runtime": {
"comment0": "由于所有的so会被dlopen动态加载进内存,数组的顺序必须符合依赖顺序",
"lib": [
"libsys.so",
"libz.so.1",
"libae.so",
"libawb.so",
"libisp_algo.so",
"libisp.so",
"libcvi_bin_isp.so",
"libcvi_bin.so",
"libvenc.so",
"libvpu.so",
"libcvikernel.so",
"libcviruntime.so",
"libcnpy.so",
"libcvimath.so",
"libopencv_core.so.3.2",
"libopencv_imgproc.so.3.2",
"libcvi_ive.so",
"libcviai.so",
"libcviai_app.so"
],
"comment1": "插件依赖的其他文件,例如驱动,模型等",
"file": [
"mobiledetv2-pedestrian-d0-ls-448.cvimodel",
"yolov8n_384_640_person_vehicle.cvimodel"
]
}
}
Key Type Description
EmxSdkVersion string Emx版本,修改为实际基于的版本,例如EmxSdk-20231031-1
ChipManufactory string 芯片厂商
ChipSerial string 芯片系列

| ChipSdkVersion | string | Emx3dr/chip中基于的芯片sdk版本 | ChipAiVersion | string | Emx3dr/chip中基于的芯片ai版本 | | Cross | string | 编译链 | | CrossPath | string | 编译链的绝对路径,这个值可以为空"",如果某种情况下找不到编译链的时候才需要填写 | | runtime | obj | 这部分内容指示固件打包的时候需要将Emx3dr/chip芯片sdk中的哪些文件复制到固件中,也就是设备运行时需要依赖的芯片库和驱动等 | | runtime.lib | string array | 设备运行依赖动态库文件列表 | | runtime.file | string array | 设备运行依赖文件列表 |

编写build.cmake

  顶层CMakeLists.txt会根据选择的profile定位并包含此文件,是芯片厂编译自身代码的入口,其中有一些由顶层CMakeLists.txt传进来的变量可以使用,包括:

CMAKE_SOURCE_DIR : 工程顶层EmxAiPluginDevelop的绝对路径
ChipManufactory :厂商名
ChipSerial :芯片系列
ChipSdkVersion :芯片sdk版本号
Profile :当前profile
EmxChipPath :Emx3dr/chip路径

下面说明这个文件内需要做的事情

  • 赋值ChipSources变量
    # 将需要编译的源文件添加到ChipSources中并增加头文件目录
    include_directories(${CMAKE_SOURCE_DIR}/chip/${ChipManufactory}/${ChipSerial})
    # 这里包含插件所需要的算法
    set(EmxAiPluginSources ${EmxAiPluginSources}
    ${CMAKE_SOURCE_DIR}/chip/${ChipManufactory}/${ChipSerial}/DefaultAiHandle.cpp
    ${CMAKE_SOURCE_DIR}/chip/${ChipManufactory}/${ChipSerial}/MotionDetection.cpp
    #客流统计
    # ${CMAKE_SOURCE_DIR}/chip/${ChipManufactory}/${ChipSerial}/PassengerFlowStatistics.cpp
    #人形侦测
    ${CMAKE_SOURCE_DIR}/chip/${ChipManufactory}/${ChipSerial}/PersonDetection.cpp
    #越界入侵
    ${CMAKE_SOURCE_DIR}/chip/${ChipManufactory}/${ChipSerial}/CrossingInvasionComm.cpp
    ${CMAKE_SOURCE_DIR}/chip/${ChipManufactory}/${ChipSerial}/InvasionDetection.cpp
    ${CMAKE_SOURCE_DIR}/chip/${ChipManufactory}/${ChipSerial}/CrossingDetection.cpp
    )
    aux_source_directory(${CMAKE_SOURCE_DIR}/chip/${ChipManufactory}/${ChipSerial}/${AiPluginProfile} EmxAiPluginSources)
  • 赋值EmxAiPluginLibs变量 将最终需要链接的库文件添加到EmxAiPluginLibs中,例如:
    set(EmxAiPluginLibs
    )
  • 添加路径依赖 自行添加头文件和库文件路径依赖,例如:
    # 头文件依赖
    include_directories(${CMAKE_SOURCE_DIR}/chip/${ChipManufactory}/${ChipSerial}/${AiPluginProfile})
    include_directories(${EmxChipPath}/${ChipManufactory}/${ChipSerial}/${ChipSdkVersion}/include/cviai)
    include_directories(${EmxChipPath}/${ChipManufactory}/${ChipSerial}/${ChipSdkVersion}/include/cviai/app)
    include_directories(${EmxChipPath}/${ChipManufactory}/${ChipSerial}/${ChipSdkVersion}/include)
    ...
    # 库文件依赖
    link_directories(${EmxChipPath}/${ChipManufactory}/${ChipSerial}/${ChipSdkVersion}/lib)
    ...
  • 自定义宏定义和编译选项 厂商可以在这里添加自己需要的定义
    add_definitions(-DEmxAiPluginDefine)
    add_compile_options(-DEmxAiPluginOption -Wno-unused-parameter -D_GNU_SOURCE
    -D_MIDDLEWARE_V2_ -MMD -D__CV181X__ -DARCH_CV181X -mno-ldd
    -D__aarch64__
    -mcpu=c906fdv -march=rv64imafdcv0p7xthead -mcmodel=medany -mabi=lp64d
    )

  至此,就完成了cv181x芯片的工程配置,可以使用2.工程搭建中的方法尝试编译和发布一下新的profile,如果编译发布没有问题,可以进行下面的上板运行工作了

4.调试运行

  Emx的运行需要特定的运行环境,首先保证设备(或开发板)上电并已经加载好芯片需要的运行环境,包括各种ko,然后需要设备具备挂载nfs文件系统的功能(或者使用效率稍微低一些的sd卡挂载方式,以下仅以nfs举例)

创建固件目录结构

  修改并使用如下脚本在当前目录下创建调试目录emx

#!/bin/sh
# 先修改这些变量
Cross=riscv64-unknown-linux-musl
# 需要绝对路径
EmxSdkVersionPath=/xxx/the/path/of/EmxSdk-xxxxxxxx-x
Emx3drPath=/xxx/the/path/of/Emx3dr
EmxProfilePath=/xxx/the/path/of/EmxMedia-xxxxxxxx-x/chipXDefault
EmxAiPluginPath=/xxx/the/path/of/EmxAiPlugin-xxxxxxxx-x/chipXDefault
# 构建目录
mkdir -p emx
cd emx
mkdir -p firmware data/normal data/backup data/AiPlugin
cd data/AiPlugin
# 拷贝通用aiPlugin-configs
cp ${EmxAiPluginPath}/AiPlugin.json .
cp -r ${EmxAiPluginPath}/configs .
cp ${EmxAiPluginPath}/libEmxAiPlugin.so .
cp -r ${Emx3drPath}/chip/${ChipManufactory}/${ChipSerial}/ai/${ChipAiVersion}/* .
cd -
cd firmware
mkdir -p 3dr/bin 3dr/lib app parm media/bin media/lib configs/media/scene/default
# 拷贝sdk
cp -r ${EmxSdkVersionPath}/cross/${Cross}/bin app
cp -r ${EmxSdkVersionPath}/cross/${Cross}/lib app
chmod +x app/bin/*
# 拷贝第三方库,注意版本号更新
cp ${Emx3drPath}/common/jsoncpp/1.9.3/${Cross}/lib/*.so* 3dr/lib
cp ${Emx3drPath}/common/libuv/1.44.2/${Cross}/lib/*.so* 3dr/lib
# 拷贝param,调试阶段这里先使用明文参数
cp -r ${EmxSdkVersionPath}/EmxProductTemplate/orgParam/* param/
# 拷贝通用psp-param
cp -r ${EmxSdkVersionPath}/EmxProductTemplate/psp/param/* param/
# 拷贝通用configs
cp -r ${EmxSdkVersionPath}/EmxProductTemplate/orgConfigs/* configs/
# 拷贝通用psp-configs
cp -r ${EmxSdkVersionPath}/EmxProductTemplate/psp/configs/* configs/
# 将scene中的配置文件加入到scene中
cp ${EmxProfilePath}/configs/* configs/media/scene/default
# 拷贝EmxMediaServer
cp ${EmxProfilePath}/EmxMediaServer media/bin
chmod +x media/bin/*
# 芯片运行需要依赖的厂商自身的动态需要自行拷贝到media/lib下
# 如果已经在系统默认路径下如/lib /usr/lib则可以不用拷贝

挂载

  将刚刚生成的emx目录挂载到设备上,这里假设挂载到设备的/mnt/nfs目录下

mount -t nfs -o nolock 192.168.10.123/home/xx/workspace/emx /mnt/nfs

修改环境变量

  运行前需要向环境变量中添加firmware的所在路径,可以将以下内容写到某个文件中source一下

export EMX_DATA_DIR=/mnt/nfs/emx/data
export EMX_FIRMWARE_DIR=/mnt/nfs/emx/firmware
export EMX_3DR_DIR=${EMX_FIRMWARE_DIR}/3dr
export EMX_APP_DIR=${EMX_FIRMWARE_DIR}/app
export EMX_MEDIA_DIR=${EMX_FIRMWARE_DIR}/media
export EMX_CONFIGS_DIR=${EMX_FIRMWARE_DIR}/configs
export EMX_PARAM_DIR=${EMX_FIRMWARE_DIR}/param
export PATH=/usr/bin:/usr/sbin:/bin:/sbin
export PATH=${PATH}:${EMX_3DR_DIR}/bin
export PATH=${PATH}:${EMX_APP_DIR}/bin
export PATH=${PATH}:${EMX_APP_DIR}/scripts
export PATH=${PATH}:${EMX_MEDIA_DIR}/bin
export PATH=${PATH}:${EMX_MEDIA_DIR}/scripts
export LD_LIBRARY_PATH=/lib:/usr/lib
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${EMX_3DR_DIR}/lib
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${EMX_APP_DIR}/lib
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${EMX_MEDIA_DIR}/lib
# 调试阶段定义这个环境变量可以暂时使用明文参数
export EMX_PLAINT_PARAM=1

执行

  libEmxAiPlugin.so 会被EmxMediaServer加载,所以需要启动EmxMediaServer并进入后台执行

EmxCoreServer -b
EmxMediaServer -b

测试工具

  完成模块对接后,可以运行如下指令,然后配合客户端EmxClient.exe,进行EmxMediaServer的模块测试,需保证通过完整测试用例

EmxToolsV2 -b

在发布的EmxSdk包里面cross对应平台的下的demo目录提供了两个测试ai的简单工具:

DemoAi
DemoAiFaceRegister

DemoAi 程序运行后会实时获取所有ai模块上传的ai数据,并打印数据类型和数量

DemoAiFaceRegister 程序是单次下发程序,需要携带参数实现人脸注册、注销、同步数据库的操作,基本使用方式:

注册人脸:DemoAiFaceRegister 0 id imgPath
eg:./DemoAiFaceRegister 0 1 /tmp/face1.jpg
这样运行就会实现id为1 的人脸注册,并返回注册结果
注销人脸:DemoAiFaceRegister 1 id
eg:./DemoAiFaceRegister 1 1
这样运行就会注销id为1 的人脸
同步数据库:DemoAiFaceRegister 2 libJsonPath
eg:./DemoAiFaceRegister 2 /tmp/facelib4.json
这样运行就会将facelib4这个人脸库信息同步给ai人脸识别模块底库

5.模块开发

  EmxAiPlugin 算法实现很开放,调用者只关注返回的算法结果,算法的模块包含:

enum class TypeE : uint8_t {
MotionDetection = 0, //!<移动侦测数据
PersonDetection, //!<人形侦测数据
FaceDetection, //!<人脸检测数据
Classify, //!<分类数据
FaceRecognition, //!<人脸识别数据
PersonRecognition, //!<人形识别数据
TypeNum, //!<数据类型数量
PassengerFlowStatistics, //!<客流统计数据
CrossingDetection, //!<越界数据
InvasionDetection, //!<区域入侵数据
PersonVehicleNonDetection,//!<人车非检测
ElectricBicycleDetection, //!<电动车检测
VehicleRecognition, //!<车辆识别含车牌
RegionalPeopleStatistics, //!<区域人数统计
OffDutyDetection, //!<离岗检测
BabyCryDetection, //!<婴儿啼哭检测
PtzTargetTrack, //!<云台追踪目标
KeywordSpotting, //!<语义分析,关键字检测
FlameSmokeDetection, //!<火焰烟雾检测
};
.....

  芯片对接需要实现的代码放在chip/cvitek/cv181x下,此目录下会存在profile对应的一个文件夹,里面实现了一个库接口初始化和算法模块加载的接口。下面以cv181x为例,profile是cv181xHangYanIndoor,对应文件目录也是 cv181xHangYanIndoor,在此目录中创建任意名称文件: cv181xDefault.cpp,cv181xDefault.hpp

   cv181xDefault 需要继承 MediaServerAiChn,并且实现:

namespace Emx {
class cv181xDefault : public MediaServerAiChn {
public:
cv181xDefault(int32_t chn) : MediaServerAiChn(chn) {}
virtual ~cv181xDefault(){};
ErrCodeE OnCreate() override;
void OnDestroy() override;
};
}// namespace Emx
ErrCodeE
错误码定义
Definition: EmxTypeDef.hpp:29
Definition: EmxGpio.hpp:10

   cv181xDefault.cpp中还需要实现库调用的初始化接口:

static char g_AiPluginWorkspace[EMX_MAX_PATH_SIZE] = {0};
static EmxAiPluginVersionT g_AiPluginVersion;
int EmxAiPluginInit(const char *path) {
return 0;
}
void EmxAiPluginDeInit() {
}
void *EmxAiPluginMakeAiChn(int32_t chn) {
return (void *) new cv181xDefault(chn);
}
struct EmxAiPluginVersionT EmxAiPluginGetVersion() {
g_AiPluginVersion.sEmxAiPluginVersion = EmxAiPluginVersion;
g_AiPluginVersion.sEmxMediaVersion = EmxMediaVersion;
g_AiPluginVersion.sEmxSdkVersion = EmxSdkVersion;
g_AiPluginVersion.sChipManufactory = ChipManufactory;
g_AiPluginVersion.sChipSerial = ChipSerial;
g_AiPluginVersion.sChipAiVersion = ChipAiVersion;
g_AiPluginVersion.sChipSdkVersion = ChipSdkVersion;
g_AiPluginVersion.sAiPluginProfile = AiPluginProfile;
return g_AiPluginVersion;
}
ErrCodeE cv181xDefault::OnCreate() {
Json::Value param;
char tmp[EMX_MAX_PATH_SIZE] = {0};
if (LoadParam(param) != ErrCodeE::Success) return ErrCodeE::Failure;
sprintf(tmp, "%s/configs/%s", g_AiPluginWorkspace, "config.json");
Json::Value node;
if (EasyJson::Load(tmp, node) != ErrCodeE::Success) {
emxloge("load %s failed\n", tmp);
return ErrCodeE::ParseFailed;
}
Json::Value root = node[m_chn];
if(root.isMember("MotionDetection") && root["MotionDetection"].asBool()){
m_algs.push_back(new MotionDetection(m_chn));
emxlogi("Config MotionDetection mode start!\n");
}
if(root.isMember("PersonDetection") && root["PersonDetection"].asBool()){
m_algs.push_back(new PersonDetection(m_chn));
emxlogi("Config PersonDetection mode start!\n");
}
if(root.isMember("CrossingInvasionComm") && root["CrossingInvasionComm"].asBool()){
m_algs.push_back(new CrossingInvasionComm(m_chn));
emxlogi("Config CrossingInvasionComm mode start!\n");
}
if(root.isMember("FaceAlgorithm") && root["FaceAlgorithm"].asBool()){
m_algs.push_back(new FaceAlgorithm(m_chn));
emxlogi("Config FaceAlgorithm mode start!\n");
}
for (auto &e: m_algs) { e->Create(g_AiPluginWorkspace, param[m_chn]); }
return ErrCodeE::Success;
}
void cv181xDefault::OnDestroy() {
for (auto &e: m_algs) {
e->Destroy();
delete e;
}
m_algs.clear();
}

  所有算法模块都有一些相同的特性,了解这些特性对于后续对接开发有很大帮助,这里为了方便描述,假设我们一个算法名为移动侦测,对应的源文件是MotionDetection.cppMotionDetection.hpp,在MotionDetection.hpp定义了一个同名的类MotionDetection这个类继承了EmxAiPluginAlgBase,它是EmxAiplugin实现的一个基础类,我们只需要重点关注这些定义函数的实现即可。   通常MotionDetection会有3个通用的回调需要实现,Create表示模块某个通道的创建回调,Destroy表示某个通道的销毁回调,SetParam表示模块某个通道的参数发生改变的回调。MotionDetection的通道号是从父类继承来的,成员变量m_chn,关于通道号的信息

void Create(const char *workspace, Json::Value &param) override;
void Destroy() override;
ErrCodeE SetParam(const Json::Value &paramNew) override;

  在每个模块的详细说明中,可以被阻塞的回调都会特别说明指出,没有指明的回调均是不可被阻塞的

  一般来讲在Create阶段必定会涉及到配置文件的加载,加载配置文件使用如下方式:

Json::Value root;
char path[EMX_MAX_PATH_SIZE] = {};
snprintf(path, sizeof(path), "%s/AiPlugin/configs/%s.json", SysEnv::GetDataDir(), m_name.c_str());
auto e = EasyJson::Load(path, root);
if (e != ErrCodeE::Success) {
emxloge("get media %s configuration failed\n", path);
return;
}
auto &json = root[m_chn];

json中存储的就是这个模块通道的配置文件,这个配置文件由芯片厂商自行编写,m_name.c_str()是模块名称字符串xxxx

6.Ai数据上报

目前支持的Ai数据类型都以头文件形式提供,头文件在 sdk/include/media/common下:

哭声侦测:MediaInfAiDataBabyCryDetection.hpp
分类数据:MediaInfAiDataClassify.hpp
越界侦测:MediaInfAiDataCrossingDetection.hpp
人脸检测:MediaInfAiDataFaceDetection.hpp
人脸识别:MediaInfAiDataFaceRecognition.hpp
火焰烟雾检测:MediaInfAiDataFlameSmokeDetection.hpp
区域入侵侦测:MediaInfAiDataInvasionDetection.hpp
语义关键字检测:MediaInfAiDataKeywordSpotting.hpp
车牌识别:MediaInfAiDataLicensePlateRecognition.hpp
移动侦测:MediaInfAiDataMotionDetection.hpp
离岗检测:MediaInfAiDataOffDutyDetection.hpp
客流统计:MediaInfAiDataPassengerFlowStatistics.hpp
人形侦测:MediaInfAiDataPersonDetection.hpp
人形识别:MediaInfAiDataPersonRecognition.hpp
人车非检测:MediaInfAiDataPersonVehicleNonDetection.hpp
目标云台追踪:MediaInfAiDataPtzTargetTrack.hpp
区域人数统计:MediaInfAiDataRegionalPeopleStatistics.hpp
车辆识别:MediaInfAiDataVehicleRecognition.hpp
....

   Ai数据上报是最终目标,当获取到对应数据时需要调用MediaAiJob::PushAiData上报数据,以移动侦测为例

void MotionDetection::OnGetFrame(void *arg) {
cvai_object_t obj;
...
//获取移动侦测结果
CVI_AI_MotionDetection(m_aiHandle, &stFrame, &obj, m_threshold, (double) m_minArea);
.....
if (obj.size > 0) {
auto &header = m_data.header;
header.chn = m_chn;
header.type = MediaInfAiData::TypeE::MotionDetection;
header.timestampMs = Time::GetMs64();
header.srcPicSize.w = (int) stFrame.stVFrame.u32Width;
header.srcPicSize.h = (int) stFrame.stVFrame.u32Height;
m_data.num = (int) obj.size;
m_data.Alloc(m_data.num);
for (int i = 0; i < (int) obj.size; i++) {
auto &bbox = obj.info[i].bbox;
m_data.array[i].Set((int) bbox.x1, (int) bbox.y1, (int) (bbox.x2 - bbox.x1), (int) (bbox.y2 - bbox.y1));
m_data.array[i].id = obj.info[i].id;
m_data.array[i].score = bbox.score;
}
m_got = true;
}
//释放算法数据
CVI_AI_Free(&obj);
}
void MotionDetection::OnGetFrameComplete(void *arg) {
if (!m_got) return;
MediaAiJob::PushAiData(&m_data);
m_data.Free();
m_got = false;
}

7.人脸识别

   人脸识别模块设计到人脸注册、人脸注销、数据库更新操作,主要是属性是人脸id和人脸特征信息

   所有操作都通过Ctrl接口下发,接口参数均采用json格式,人脸(陌生人)识别模块需要自己实现此虚函数

virtual ErrCodeE Ctrl(const Json::Value &in, Json::Value &out) {
return ErrCodeE::OperationNotSupport;
}

   写个简单例子:

hpp:
#include "media/common/MediaInfAiDataFaceRecognition.hpp"
#include "media/common/MediaInfAiDataFaceDetection.hpp"
#include "media/server/MediaServerAi.hpp"
namespace Emx {
class FaceAlgorithm : public EmxAiPluginAlgBase {
public:
FaceAlgorithm(int chn) : EmxAiPluginAlgBase(chn, "FaceAlgorithm"), m_created(false), m_started(false) {}
...
ErrCodeE Ctrl(const Json::Value &in, Json::Value &out) override;
private:
...
ErrCodeE FaceRegister(const Json::Value &info, Json::Value &out);
ErrCodeE FaceUnregister(const Json::Value &id);
ErrCodeE FaceSyncLibrary(const Json::Value &library);
...
};
}// namespace Emx
cpp:
ErrCodeE FaceAlgorithm::Ctrl(const Json::Value &in, Json::Value &out) {
ErrCodeE e = ErrCodeE::Success;
if (in.isMember("faceRegister")) { e = FaceRegister(in["faceRegister"], out); }
if (in.isMember("faceUnregister")) { e = FaceUnregister(in["faceUnregister"]); }
if (in.isMember("syncLibrary")) { e = FaceSyncLibrary(in["syncLibrary"]); }
return e;
}

人脸注册

   人脸注册根据下发的json数组,获取人脸id、是否是图片标志,和图片路径(或摄像头采集画面)。    人脸输入信息json格式要求:

[
{"id":1,"isImg":true,"path":"/tmp/face1.jpg"},
{"id":2,"isImg":false,"path":""}
]

   path是当isImg为true时才会传具体的图片路径,目前图片仅支持jpg。

   人脸特征信息json格式要求:

[
{"id":1,"feature":"base64(feature)"}
]

  id时传入人脸信息的id,feature 是将二进制的特征信息做base64编码存储。

  简单例子:

ErrCodeE FaceAlgorithm::FaceRegister(const Json::Value &info, Json::Value &out) {
ErrCodeE e = ErrCodeE::Success;
//人脸提取特征数据
for (int i = 0; i < (int) info.size(); i++) {
Json::Value faceInfo;
std::string buf;
faceInfo["id"] = info[i]["id"].asInt();
//是使用jpg图片提取
if(info["isImg"].asBool()){
//e = getFeature(info[i]["path"].asString(),buf);
emxlogi("register[%d],id=%d,path=%s\n", i,info[i]["id"].asInt(),info[i]["path"].asString().c_str());
}else{
//e = getSourceFeature(buf);
emxlogi("register[%d],id=%d,path=null\n", i,info[i]["id"].asInt());
}
if(e == ErrCodeE::Success){
std::string feature;
Base64::Encode(buf, feature);
faceInfo["feature"] = feature;
out.append(faceInfo);
}
}
return e;
}

人脸注销

   人脸注销根据下发的id数组来删除人脸底库中所有对应id的人脸特征信息    人脸注销json格式要求:

[1,5,120,62,20]

  简单例子:

ErrCodeE FaceAlgorithm::FaceUnregister(const Json::Value &id) {
for (int i = 0; i < (int) id.size(); i++) {
int idNum = id[i].asInt();
emxlogi("unregister[%d],id=%d\n", i,idNum);
//删除对应id人脸库
// faceDelete(idNum);
}
return ErrCodeE::Success;
}

人脸库同步

   人脸库同步是应用层将当前保存在数据库的人脸id和特征信息下发给算法端,算法端需要将这些信息放到人脸底库中做识别。    注意ai模块不需要离线保存人脸底库,由应用层保存,且这里同步底库需要ai模块将原始的人脸底库清空,完全使用同步的新数据,一般是开机后同步一次   简单例子:

ErrCodeE FaceAlgorithm::FaceSyncLibrary(const Json::Value &library) {
//同步特征数据
for (int i = 0; i < (int) library.size(); i++) {
int idNum = library[i]["id"].asInt();
std::string feature = library[i]["feature"].asString();
//添加人脸库
std::string buf;
Base64::Decode(feature,buf);
emxlogi("lirary[%d] idNum=%d\n", i,idNum);
// addLibrary(idNum,buf);
}
return ErrCodeE::Success;
}

8.必要文件说明

   在AiPlugin目录下的configs目录下,有几个必要的文件,这个文件是算法模块的配置文件,所有芯片都需要存在       config.jsonrange.jsonparam.def.json

range.json

   range.json 是一个json数组,每个对象对应一个算法通道的参数范围,因为算法的具体范围各个厂家不一致,现在这里统一为空,但是必须有一个空的对象,算法调用时会根据range中的对象数量来创建算法通道,比如:

单目设备1个算法通道:
[{ }]
双目设备2个算法通道:
[{ },{ }]
三目设备3个算法通道:
[{ },{ },{ }]
...
依此类推

config.json

   config.json 是一个json数组,每个对象对应一个算法通道的具体算法信息,里面的参数字段不做限制,由AiPlugin的开发者自行定义,比如:

单目设备1个算法通道:
[{
"MotionDetection":true,
"PersonDetection": true,
}]
双目设备2个算法通道:
[{
"MotionDetection":true,
"PersonDetection": true,
},
{
"MotionDetection":true,
"PersonDetection": false,
}]
...
依此类推

param.def.json

   param.def.json 是一个json数组,每个对象对应一个算法通道的具体算法控制参数,里面的参数字段全部由EmxSdk统一,不支持的算法可以缺省,比如:

[
{
"MotionDetection": {
"ena": true,
"threshold": 20
},
"PersonDetection": {
"ena": true
},
"Classify": {
"ena": false
},
"InvasionDetection": {
"ena": false,
"invasionType": 1,
"captureBackImg": false,
"captureRectImg": false,
"captureQuality": 70,
"map": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
},
"CrossingDetection": {
"ena": false,
"crossType": 1,
"captureBackImg": false,
"captureRectImg": false,
"captureQuality": 70,
"lineX1": 0,
"lineY1": 5000,
"lineX2": 10000,
"lineY2": 5000,
"direction": 0
},
"PassengerFlowStatistics": {
"ena": false,
"captureBackImg": false,
"captureRectImg": false,
"captureQuality": 70,
"lineX1": 0,
"lineY1": 5000,
"lineX2": 10000,
"lineY2": 5000,
"direction": 0
},
"RegionalPeopleStatistics": {
"ena": false,
"intervalTime":60,
"map": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
},
"OffDutyDetection": {
"ena": false,
"limitNum": 1,
"timeOut": 60,
"map": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
},
"FaceDetection": {
"ena": false,
"minWidth": 40,
"minHeight": 40
},
"FaceRecognition": {
"ena": false,
"threshold": 50,
"snapMode": 0,
"snapTime": 10,
"snapQuality": 20,
"snapClarity": 70,
"snapAngle": 60,
"captureBackImg": true,
"captureRectImg": true,
"captureQuality": 70,
"aeEna": true,
"aeThreshold": 60,
"minWidth": 80,
"minHeight": 80,
"maskEna": true,
"maskThreshold": 60,
"recognitionEna": true
},
"PersonVehicleNonDetection": {
"ena": false,
"threshold": 60,
"captureBackImg": false,
"captureRectImg": false,
"captureQuality": 70
},
"VehicleRecognition": {
"ena": false,
"licensePlateEna": false,
"threshold": 60,
"captureBackImg": true,
"captureRectImg": true,
"captureLicensePlateImg": true,
"captureQuality": 70
},
"PtzTargetTrack": {
"ena": false,
"type comment":"MediaInfAiDataPtzTargetTrack::TypeE 0=移动 1=人形 其他暂不支持",
"type": 1,
"flip mirror comment":"如果设备设置了画面镜像翻转,需要给算法同步状态",
"flip": false,
"mirror": false,
"stopTimeout comment":"停止追踪超时时间,超过此时间会上报ai报警",
"stopTimeout": 30,
"horStep verStep comment":"追踪转换步长的补偿值,如果电机每次转动到超过了目标的位置就减小,反之加大",
"horStep": 8,
"verStep": 3,
"horSpeed verSpeed comment":"追踪速度,越大越快",
"horSpeed": 600,
"verSpeed": 350
},
"FlameSmokeDetection": {
"ena": false
},
}
]