EV_SDK
说明
EV_SDK的目标
开发者专注于算法开发及优化,最小化业务层编码,即可快速部署到生产环境,共同打造商用级高质量算法。
极市平台做了哪些
- 统一定义算法接口:针对万千视频和图片分析算法,抽象出接口,定义在
include
目录下的ji.h
文件中 - 提供工具包:比如算法授权(必须实现)、模型加密,在
3rd
目录下 - 应用层服务:此模块不在ev_sdk中,比如视频处理服务、算法对外通讯的http服务等
开发者需要做什么
- 模型的训练和调优
- 实现
ji.h
约定的接口,同时包括授权、支持分析区域等功能 - 实现约定的输入输出
- 其他后面文档提到的功能
目录
代码目录结构
ev_sdk
|-- 3rd # 第三方源码或库目录,发布时请删除
| |-- wkt_parser # 针对使用WKT格式编写的字符串的解析器
| |-- cJSON # c版json库,简单易用
| |-- ev_encrypt_module # 模型加密库及相关工具
| |-- darknet # 示例项目依赖的库
| `-- license # SDK授权库及相关工具
|-- CMakeLists.txt # 本项目的cmake构建文件
|-- README.md # 本说明文件
|-- model # 模型数据存放文件夹
|-- config # 程序配置目录
| |-- README.md # algo_config.json文件各个参数的说明和配置方法
| `-- algo_config.json # 程序配置文件
|-- doc
|-- include # 库头文件目录
| `-- ji.h # libji.so的头文件,理论上仅有唯一一个头文件
|-- lib # 本项目编译并安装之后,默认会将依赖的库放在该目录,包括libji.so
|-- src # 实现ji.cpp的代码
`-- test # 针对ji.h中所定义接口的测试代码,请勿修改!!!
使用示例
作为示例,我们提供了一个使用darknet
实现的图像检测器,并将其使用EV_SDK
规范进行封装,需要实现的业务逻辑是当检测到狗时,需要返回相关的报警信息。使用如下步骤尝试编译和测试该项目:
下载EV_SDK
git clone https://github.com/ExtremeMart/dev-docs
mv dev-docs /usr/local/ev_sdk
编译
编译和安装libji.so
:
mkdir -p /usr/local/ev_sdk/build
cd /usr/local/ev_sdk/build
cmake ..
make install
测试示例程序和接口规范
执行完成之后,/usr/local/ev_sdk/lib
下将生成libji.so
和相关的依赖库,以及/usr/local/ev_sdk/bin/
下的测试程序test-ji-api
。
-
要使用
/usr/local/ev_sdk/bin/test-ji-api
测试EV_SDK
的接口,需要重新生成授权所使用的参考码reference.txt
,并使用私钥对其进行加密后重新生成授权文件license.txt
# 生成公私钥以及公钥对应的头文件 bash /usr/local/ev_sdk/3rd/license/bin/oneKeyAuth.sh # 生成硬件参考码文件和授权文件 bash /usr/local/ev_sdk/3rd/license/bin/oneKeyTest.sh
-
使用
test-ji-api
测试ji_calc_frame
接口,测试添加了一个ROI
参数/usr/local/ev_sdk/bin/test-ji-api -f ji_calc_frame -i /usr/local/ev_sdk/data/dog.jpg -o /tmp/output.jpg -l /usr/local/ev_sdk/authorization/license.txt -a '{"roi":["POLYGON((0.2 0.2,0.6 0.1,0.8 0.7,0.4 0.9,0.1 0.8,0.2 0.25))"]}'
输出内容样例:
code: 0 json: { "alert_flag": 1, "dogs": [{ "x": 129, "y": 186, "width": 369, "height": 516, "confidence": 0.566474 }] }
使用EV_SDK
快速封装算法
假设项目需要检测输入图像中是否有狗,如果检测到狗,就需要输出报警信息,以下示例开发算法与使用EV_SDK
进行封装的流程
实现自己的模型
假设我们使用darknet
开发了针对狗的检测算法,程序需要在检测到狗时输出报警信息。
下载EV_SDK
git clone https://github.com/ExtremeMart/dev-docs
mv dev-docs /usr/local/ev_sdk
生成授权功能所依赖的文件
-
使用
EV_SDK
提供的工具一键生成公钥和私钥、以及公钥对应的头文件pubKey.hpp
bash /usr/local/ev_sdk/3rd/license/bin/oneKeyAuth.sh
执行成功后将在
/usr/local/ev_sdk
下生成公钥authorization/pubKey.perm
和私钥authorization/privateKey.pem
,以及头文件include/pubKey.hpp
; -
在
ji_init(int argc, char **argv)
的接口实现中,添加校验授权文件的功能。注:这部分代码在示例代码
ji.cpp
中已经实现,可以无需变动,直接使用。// 使用公钥校验授权信息 int ret = ji_check_license(pubKey, license, url, activation, timestamp, qps, version); return ret == EV_SUCCESS ? JISDK_RET_SUCCEED : JISDK_RET_UNAUTHORIZED;
更多授权功能的原理,请参考算法授权。
添加模型加密功能
-
使用
EV_SDK
提供的工具加密模型,并生成C++
头文件,这里仅仅示例加密yolov3-tiny.cfg
文件,请根据实际需要,对重要的模型/权重文件进行加密mkdir -p /usr/local/ev_sdk/model_encryption/ cd /usr/local/ev_sdk/model_encryption/ /usr/local/3rd/ev_encrypt_module/bin/encrypt_tool /usr/local/ev_sdk/model/yolov3-tiny.cfg
执行成功后会生成加密后的文件
model_str.hpp
,encrypt_tool
程序支持在加密模型时指定一个混淆字符串,具体方法请执行encrypt_tool
参考帮助文档。将头文件移动到代码区mv /usr/local/ev_sdk/model_encryption/model_str.hpp /usr/local/ev_sdk/include
这个加密后的模型将被硬编码到
libji.so
。 -
在
ji_create_predictor(int)
的接口实现中,添加模型解密的功能。注:示例代码
ji.cpp
里面提供了解密的方法,对于加密文本类型的模型文件的场景可以直接使用,无需更改。// 创建解密句柄 void *h = CreateEncryptor(model_str.c_str(), model_str.size(), key.c_str()); // 获取解密后的字符串 int fileLen = 0; model_struct_str = (char *) FetchBuffer(h, fileLen); // 获取解密后的文件句柄 // file *file = (file *) FetchFile(h); DestroyEncrtptor(h);
模型解密的详细接口函数请参考其头文件encrypt_wrapper.h
实现ji.h
中的接口
ji.h
中定义了所有EV_SDK
规范的接口,详细的接口定义和实现示例,请参考头文件ji.h和示例代码ji.cpp。
将代码编译成libji.so
mkdir -p /usr/local/ev_sdk/build
cd /usr/local/ev_sdk/build
cmake ..
make install
编译完成后,将在/usr/local/ev_sdk/lib
下生成libji.so
和其他依赖的库。
测试接口功能
测试libji.so
的授权功能是否正常工作以及ji.h
的接口规范
-
使用
EV_SDK
提供的程序oneKeyTest.sh
一键生成授权文件bash /usr/local/ev_sdk/3rd/license/bin/oneKeyTest.sh
oneKeyTest.sh
会执行:-
检查
authorization/pubKey.pem
和authorization/privateKey.pem
的有效性; -
生成硬件参考码文件
authorization/reference.txt
和授权文件authorization/license.txt
。
-
-
检查授权功能和
ji.h
的接口规范性EV_SDK
代码中提供了测试所有接口的测试程序,编译并安装libji.so
之后,会在/usr/local/ev_sdk/bin
下生成test-ji-api
可执行文件,test-ji-api
用于测试ji.h
的接口实现是否正常,例如,测试ji_calc_frame
接口以及授权功能是否正常:/usr/local/ev_sdk/bin/test-ji-api -f ji_calc_frame \ -i /usr/local/ev_sdk/data/dog.jpg \ -o /tmp/output.jpg \ -l /usr/local/ev_sdk/authorization/license.txt \ -a '{"roi":["POLYGON((0.2 0.2,0.6 0.1,0.8 0.7,0.4 0.9,0 0.8,0.2 0.2))"]}'
接口测试程序的详细功能请查阅
test-ji-api --help
的帮助文档及其代码test.cpp
哪些内容必须完成才能通过测试?
按照需求实现接口(由项目经理告知)
ji_calc_frame
,用于实时视频流分析ji_calc_buffer
,用于分析图片buffer
ji_calc_file
,用于分析图片文件ji_calc_video_file
:用于极市平台测试组测试和开发者自测视频文件
规范要求
规范测试大部分内容依赖于内置的/usr/local/ev_sdk/test
下面的代码,这个测试程序会链接/usr/local/ev_sdk/lib/libji.so
库,EV_SDK
封装完成提交后,极市方会使用test-ji-api
程序测试ji.h
中的所有接口。测试程序与EV_SDK
的实现没有关系,所以请请不要修改/usr/local/ev_sdk/test
目录下的代码!!!
-
接口功能要求
-
确定
test-ji-api
能够正常编译,并且将test-ji-api
和license.txt
移动到任意目录,都需要能够正常运行; -
在提交算法之前,请自行通过
/usr/local/ev_sdk/bin/test-ji-api
测试接口功能是否正常; -
未实现的接口需要返回
JISDK_RET_UNUSED
; -
实现的接口,如果传入参数异常时,需要返回
JISDK_RET_INVALIDPARAMS
; -
对于实现多个接口的情况,请确保每个接口对同样的输入数据保持一致的算法分析结果,比如
ji_calc_frame
和ji_calc_file
两个接口对于同样的输入图片数据,应该保持一样的分析结果; -
输入图片和输出图片的尺寸应保持一致;
-
对于接口中传入的参数
args
(如,ji_calc_frame(void *, const JI_CV_FRAME *, const char *args, JI_CV_FRAME *, JI_EVENT *)
中中args
),根据项目需求,算法实现需要支持args
实际传入的参数。例如,如果项目需要支持在
args
中传入roi
参数,使得算法只对roi
区域进行分析,那么算法内部必须实现只针对roi
区域进行分析的功能; -
对于接口
ji_calc_video
接口,其保存的json
文件格式必须与ji_calc_frame
、ji_calc_file
、ji_calc_buffer
中的JI_EVENT.json
格式保持一致; -
通常输出图片中需要画
roi
区域、目标框等,请确保这一功能正常,包括但不仅限于:args
中输入的roi
需要支持多边形- 算法默认分析区域必须是全尺寸图,如当
roi
传入为空时,算法对整张图进行分析;
-
为了保证多个算法显示效果的一致性,与画框相关的功能必须优先使用
ji_utils.hpp.h
中提供的工具函数;
test-ji-api
的使用方法可以参考上面的使用示例以及运行test-ji-api --help
;- 以上要求在示例程序
ji.cpp
中有实现;
-
-
业务逻辑要求
针对需要报警的需求,算法必须按照以下规范输出结果:
-
报警时输出:
JI_EVENT.code=JISDK_CODE_ALARM
,JI_EVENT.json
内部填充"alert_flag"=1
; -
未报警时输出:
JI_EVENT.code=JISDK_CODE_NORMAL
,JI_EVENT.json
内部填充"alert_flag"=0
; -
处理失败的接口返回
JI_EVENT.code=JISDK_CODE_FAILED
-
算法输出的
json
数据必须与项目需求保持一致;
-
-
授权功能要求
需要实现授权功能,并且在调用接口(比如
ji_calc_frame
)时,如果授权没有通过,必须返回JISDK_RET_UNAUTHORIZED
。注:授权功能已经在示例代码实现,基本不需要修改
-
算法配置选项要求
EV_SDK
的实现需要使用标准JSON格式的配置文件,所有算法与SDK
可配置参数必须存放在统一的配置文件:/usr/local/ev_sdk/config/algo_config.json
中;- 配置文件中必须实现的参数项:
draw_roi_area
:true
或者false
,是否在输出图中绘制roi
分析区域;roi_line_thickness
:ROI区域的边框粗细;roi_fill
:是否使用颜色填充ROI区域;roi_color
:roi
框的颜色,以BGRA表示的数组,如[0, 255, 0, 0]
,参考model/README.md;roi
:针对图片的感兴趣区域进行分析,如果没有此参数或者此参数解析错误,则roi默认值为整张图片区域;thresh
:算法阈值,需要有可以调整算法灵敏度、召回率、精确率的阈值参数,如果算法配置项有多个参数,请自行扩展,所有与算法效果相关并且可以变动的参数必须在/usr/local/ev_sdk/config/README.md
中提供详细的配置方法和说明(包括类型、取值范围、建议值、默认值、对算法效果的影响等);draw_result
:true
或者false
,是否绘制分析结果,比如示例程序中,如果检测到狗,是否将检测框和文字画在输出图中;draw_confidence
:true
或者false
,是否将置信度画在检测框顶部,小数点后保留两位;- 所有
json
内的键名称必须是小写字母,并且单词间以下划线分隔,如上面几个示例。
- 必须支持参数实时更新。所有
/usr/local/ev_sdk/config/algo_config.json
内的可配置参数必须支持能够在调用ji_calc_frame
、ji_calc_buffer
、ji_calc_file
、ji_calc_video_file
四个接口时,进行实时更新。也就是必须要在ji_calc_*
等接口的args
参数中,加入这些可配置项。
-
算法输出规范要求
算法输出结果,即
JI_EVENT.json
必须是使用json
格式填充的字符串,json
字符串内所有的键名称必须是小写字母,并且单词之间使用下划线分隔,如alert_flag
; -
文件结构规范要求
- 授权功能生成的公私钥必须放在
/usr/local/ev_sdk/bin
,且一次生成后,请勿再次更新公私钥(极市方会保存第一版的私钥),如果在后续更新中,重新生成了公私钥,会导致公私钥不匹配; - 与模型相关的文件必须存放在
/usr/local/ev_sdk/model
目录下,例如权重文件、目标检测通常需要的名称文件coco.names
等。 - 最终编译生成的
libji.so
必须自行链接必要的库,test-ji-api
不会链接除/usr/local/ev_sdk/lib/libji.so
以外的算法依赖库; - 如果
libji.so
依赖了系统动态库搜索路径(如/usr/lib
,/lib
等)以外的库,必须将其安装到/usr/local/ev_sdk/lib
下,可以使用ldd /usr/local/ev_sdk/lib/libji.so
查看libji.so
是否正确链接了所有的依赖库。 - 授权文件位置
- 请务必将生成的私钥
privateKey.pem
和公钥publicKey.pem
放到/usr/local/ev_sdk/authorization
下。并请自行保存一份,后续算法迭代过程都会使用第一次提交的公私钥,不能重新生成。
- 请务必将生成的私钥
- 授权功能生成的公私钥必须放在
FAQ
如何使用接口中的args
?
通常,在实际项目中,外部需要将多种参数(例如ROI
)传入到算法,使得算法可以根据这些参数来改变处理逻辑。EV_SDK
接口(如int ji_calc_frame(void *, const JI_CV_FRAME *, const char *args, JI_CV_FRAME *, JI_EVENT *)
中的args
参数通常由开发者自行定义和解析,但只能使用JSON格式。格式样例:
{
"cid": "1000",
"roi": [
"POLYGON((0.0480.357,0.1660.0725,0.3930.0075,0.3920.202,0.2420.375))",
"POLYGON((0.5130.232,0.790.1075,0.9280.102,0.9530.64,0.7590.89,0.510.245))",
"POLYGON((0.1150.497,0.5920.82,0.5810.917,0.140.932))"
],
"cross_line": ["LINESTRING(0.070.21,0.360.245,0.580.16,0.970.27)"],
"point": [
"POINT(0.38 0.10)",
"POINT(0.47 0.41)"
]
}
例如当算法支持输入ROI
参数时,那么开发者需要在EV_SDK
的接口实现中解析上面示例中roi
这一值,提取其中的ROI
参数,并使用WKTParser
对其进行解析,应用到自己的算法逻辑中。
如何在algo_config.json
内添加一个自定义配置项?
假定需要在配置文件中添加一个额外的算法阈值参数nms_thresh
,则需要:
-
在
algo_config.json
中加入默认配置参数:"nms_thresh": 0.4
-
在
Configuration.hpp
中的Configuration
结构体中添加这一参数对应的变量:float nmsThresh = 0.4;
-
在
Configuration.hpp
的Configuration.parseAndUpdateArgs
方法中添加对该参数的解析代码:cJSON *nmsThreshObj = cJSON_GetObjectItem(confObj, "nms_thresh"); if (nmsThreshObj != nullptr && nmsThreshObj->type == cJSON_Number) { nmsThresh = nmsThreshObj->valuedouble; // 获取默认的阈值 algoConfig.thresh = newThresh; }
为什么不能且不需要修改/usr/local/ev_sdk/test
下的代码?
/usr/local/ev_sdk/test
下的代码是用于测试ji.h
接口在libji.so
中是否被正确实现,这一测试程序与EV_SDK
的实现无关,且是极市方的测试标准,不能变动;- 编译后
test-ji-api
程序只会依赖libji.so
,如果test-ji-api
无法正常运行,很可能是libji.so
没有按照规范进行封装;
为什么运行test-ji-api
时,会提示找不到链接库?
由于test-ji-api
对于算法而言,只链接了/usr/local/ev_sdk/lib/libji.so
库,如果test-ji-api
运行过程中,找不到某些库,那么很可能是libji.so
依赖的某些库找不到了。此时
- 可以使用
ldd /usr/local/ev_sdk/lib/libji.so
检查是否所有链接库都可以找到; - 请按照规范将系统动态库搜索路径以外的库放在
/usr/local/ev_sdk/lib
目录下。
如何使用test-ji-api
进行测试?
-
输入单张图片和授权文件,并调用
ji_calc_frame
接口:./test-ji-api -f ji_calc_frame -i /path/to/test.jpg -l /path/to/license.txt
-
输入
json
格式的roi
参数到args
参数:./test-ji-api \ -f ji_calc_frame \ -i /path/to/test.jpg \ -l /path/to/license.txt \ -a '{"roi":["POLYGON((0.21666666666666667 0.255,0.6924242424242424 0.1375,0.8833333333333333 0.72,0.4106060606060606 0.965,0.048484848484848485 0.82,0.2196969696969697 0.2575))"]}'
-
保存输出图片:
./test-ji-api -f ji_calc_frame -i /path/to/test.jpg -l /path/to/license.txt -o /path/to/out.jpg
更多选项,请参考test-ji-api --help
本文转载于ExtremeMart
在GitHub
上的dev-docs
项目EV_SDK