图像分割LinuxSDK集成文档-C++
简介
本文档介绍EasyEdge/EasyDL的Linux CPP SDK的使用方法。
- 网络类型支持: - 图像分类 - 物体检测 - 图像分割
-
硬件支持:
- CPU: aarch64 armv7hf
- GPU: ARM Mali G系列
- ASIC: Hisilicon NNIE1.1 on aarch64(Hi3559AV100/Hi3559CV100等)
- ASIC: Hisilicon NNIE1.2 on armv7l(Hi3519AV100/Hi3559V200等)
- Intel Movidius MyRIAD2 / MyRIAD X on x86_64
- Intel Movidius MyRIAD2 / MyRIAD X on armv7l
- Intel Movidius MyRIAD2 / MyRIAD X on aarch64
- Intel iGPU on x86_64
- 比特大陆 Bitmain SE50 (BM1684)
- 瑞芯微 RK3399Pro / RV1109 / RV1126 / RK3568 / RK3588
- 华为 Atlas200
- 晶晨 A311D
- 寒武纪 MLU220 on aarch64
- 英特尔 iGPU
-
操作系统支持:
- Linux (Ubuntu, Centos, Debian等)
- 海思HiLinux
- 树莓派Raspbian/Debian
- 瑞芯微Firefly
性能数据参考算法性能及适配硬件
Release Notes
时间 | 版本 | 说明 |
---|---|---|
2023.08.31 | 1.8.3 | Atlas系列Socs支持语义分割模型,Atlas Cann版本升级至6.0.1 |
2023.06.29 | 1.8.2 | 比特大陆版本升级至V23.03.01 |
2023.05.17 | 1.8.1 | 新增支持intel iGPU + CPU异构模式 |
2023.03.16 | 1.8.0 | 新增支持瑞芯微RK3588 |
2022.10.27 | 1.7.1 | 新增语义分割模型http请求示例 |
2022.09.15 | 1.7.0 | 新增瑞芯微 RK3568 支持, RK3399Pro、RV1126升级到RKNN1.7.1 |
2022.07.28 | 1.6.0 | 引擎升级;新增英特尔 iGPU 支持 |
2022.04.25 | 1.4.1 | EasyDL, BML升级支持paddle2模型 |
2022.03.25 | 1.4.0 | EasyDL新增上线支持晶晨A311D NPU预测引擎;Arm CPU、Arm GPU引擎升级;atlas 200在EasyDL模型增加多个量化加速版本; |
2021.12.22 | 1.3.5 | RK3399Pro, RV1109/RV1126 SDK扩展模型压缩加速能力,更新端上推理库版本;边缘控制台IEC功能升级,适配更多通用小型设备,NNIE 在EasyDL增加量化加速版本;Atlas200升级到Cann5.0.3 |
2021.06.29 | 1.3.1 | 视频流解析支持调整分辨率;预测引擎升级;设备端sdk新增支持瑞芯微RV1109、RV1126 |
2021.05.13 | 1.3.0 | 新增视频流接入支持;EasyDL模型发布新增多种加速方案选择;目标追踪支持x86平台的CPU、GPU加速版;展示已发布模型性能评估报告 |
2021.03.09 | 1.2.0 | http server服务支持图片通过base64格式调用 |
2021.01.27 | 1.1.0 | EasyDL经典版分类高性能模型升级;部分SDK不再需要单独安装OpenCV;新增RKNPU预测引擎支持;新增高通骁龙GPU预测引擎支持 |
2020.12.18 | 1.0.0 | 1.0版本发布!安全加固升级、性能优化、引擎升级、接口优化等多项更新 |
2020.10.29 | 0.5.7 | 优化多线程预测细节 |
2020.09.17 | 0.5.6 | 支持linux aarch64架构的硬件接入intel神经计算棒预测;支持比特大陆计算盒SE50 BM1684 |
2020.08.11 | 0.5.5 | 支持linux armv7hf架构硬件(如树莓派)接入intel神经计算棒预测 |
2020.06.23 | 0.5.4 | arm引擎升级 |
2020.05.15 | 0.5.3 | 支持EasyDL 专业版新增模型 ;支持树莓派(armv7hf, aarch64) |
2020.04.16 | 0.5.2 | Jetson系列SDK支持多线程infer |
2020.02.23 | 0.5.0 | 新增支持人脸口罩模型;Jetson SDK支持批量图片推理; ARM支持图像分割 |
2020.01.16 | 0.4.7 | 上线海思NNIE1.2,支持EasyEdge以及EasyDL;ARM引擎升级;增加推荐阈值支持 |
2019.12.26 | 0.4.6 | 海思NNIE支持EasyDL专业版 |
2019.11.02 | 0.4.5 | 移除curl依赖;支持自动编译OpenCV;支持EasyDL 专业版 Yolov3; 支持EasyDL经典版高精度物体检测模型升级 |
2019.10.25 | 0.4.4 | ARM引擎升级,性能提升30%; 支持EasyDL专业版模型 |
2019.09.23 | 0.4.3 | 增加海思NNIE加速芯片支持 |
2019.08.30 | 0.4.2 | ARM引擎升级;支持分类高性能与高精度模型 |
2019.07.25 | 0.4.1 | 引擎升级,性能提升 |
2019.06.11 | 0.3.3 | paddle引擎升级;性能提升 |
2019.05.16 | 0.3.2 | 新增armv7l支持 |
2019.04.25 | 0.3.1 | 优化硬件支持 |
2019.03.29 | 0.3.0 | ARM64 支持;效果提升 |
2019.02.20 | 0.2.1 | paddle引擎支持;效果提升 |
2018.11.30 | 0.1.0 | 第一版! |
【1.0 接口升级】 参数配置接口从1.0.0版本开始已升级为新接口,以前的方式被置为deprecated,并将在未来的版本中移除。请尽快考虑升级为新的接口方式,具体使用方式可以参考下文介绍以及demo工程示例。 【关于SDK包与RES模型文件夹配套使用的说明】 我们强烈建议用户使用部署tar包中配套的SDK和RES一起使用。 更新模型时,如果SDK版本号有更新,请务必同时更新SDK,旧版本的SDK可能无法正确适配新发布出来的RES。
快速开始
SDK在以下环境中测试通过
- aarch64(arm64), Ubuntu 16.04, gcc 5.3 (RK3399)
- Hi3559AV100, aarch64, Ubuntu 16.04, gcc 5.3
- Hi3519AV100, armv7l , HiLinux 4.9.37, (Hi3519AV100R001C02SPC020)
- armv7hf, Raspbian, (Raspberry 3b)
- aarch64, Raspbian, (Raspberry 4b)
- armv7hf, Raspbian, (Raspberry 3b+)
- armv7hf, Ubuntu 16.04, (RK3288)
- Bitmain se50 BM1684, Debian 9
- Rockchip rk3399pro, Ubuntu 18.04
- Rockchip rv1126, Debain 10
- Rockchip rk3568, Ubuntu 20.04
- Rockchip rk3588, Ubuntu 20.04
- Atlas200(华为官网指定的Ubuntu 18.04版本)
- Amlogic A311D, Ubuntu 20.04
- MLU220, aarch64, Ubuntu 18.04
安装依赖
依赖包括
- cmake 3+
- gcc 5.4 以上(需包含 GLIBCXX_3.4.22) ,gcc / glibc版本请以实际SDK ldd的结果为准
- opencv3.4.5 (可选)
依赖说明:树莓派
树莓派Raspberry默认为armv7hf系统,使用SDK包中名称中包含armv7hf_ARM_
的tar包。如果是aarch64系统,使用SDK包中名称中包含aarch64_ARM_
的tar包。
在安装前可通过以下命令查看是32位还是64位 :
1getconf LONG_BIT
232
依赖说明:比特大陆SE计算盒
需要安装SophonSDK V23.05.01及以上版本,SDK的默认安装位置为/opt/sophon/
,如SDK安装在自定义地址,需在CMakeList.txt中指定SDK安装地址:
1# 这里修改并填入所使用的SophonSDK路径
2set(EDGE_BMSDK_ROOT "{这里填写sdk路径}")
可通过命令 bm-smi
查看内部SDK和驱动的版本号(SophonSDK V23.05.01对应的内部SDK和驱动为0.4.6)。对于使用旧版BM1684 SDK或者低版本SophonSDK的用户,可参考SophonSDK安装包中的《LIBSOPHON 使用手册》先卸载旧版BM1684 SDK,安装、升级SophonSDK。
依赖说明:海思开发板
海思开发板需要根据海思SDK文档配置开运行环境和编译环境,SDK和opencv都需要在该编译环境中编译。 NNIE1.2用arm-himix200-linux交叉编译好的opencv,下载链接:https://pan.baidu.com/s/13QW0ReeWx4ZwgYg4lretyw 密码:yq0s。下载后修改SDK CMakesList.txt
依赖说明:RK3399Pro
所有用例基于 Npu driver版本1.7.1的RK3399pro开发板测试通过,SDK采用预编译模式,请务必确保板上驱动版本为1.7.1
查看RK3399Pro板上driver版本方法:dpkg -l | grep 3399pro
依赖说明:RV1109/RV1126
所有用例基于Rknn_server版本1.7.3的RV1126开发板测试通过,SDK采用预编译模式,请务必确保板上驱动版本为1.7.3
查看RV1109/RV1126板上Rknn_server版本方法:strings /usr/bin/rknn_server | grep build
依赖说明:RK3568
所有用例基于Rknn_server版本1.2.0的RK3568开发板测试通过,
查看RK3568板上Rknn_server版本方法:strings /usr/bin/rknn_server | grep build
依赖说明:RK3588
RK3588开发板需要确保环境正确安装了RKNPU驱动,平台用例基于v0.8.0版本的RKNPU驱动测试通过,查看RK3588NPU驱动版本的方法: sudo cat /sys/kernel/debug/rknpu/version
依赖说明:晶晨A311D
所有用例基于晶晨A311D开发板测试通过,需要驱动版本为 6.4.4.3(下载驱动请联系开发版厂商)
查看晶晨A311D开发板驱动版本方法:dmesg | grep Galcore
依赖说明:英特尔iGPU
用户在使用英特尔iGPU SDK前,需要根据英特尔官方文档提前安装好英特尔集成显卡驱动以及相关基础软件环境,安装完成后通过 clinfo 指令确认OpenCL能够正常识别到集成显卡信息,正确识别集显情况下clinfo指令输出参考如下:
使用序列号激活
请在官网获取序列号
SDK内bin目录下提供预编译二进制文件,可直接运行(二进制运行详细说明参考下一小节),用于图片推理和模型http服务,在二进制参数的serial_num(或者serial_key)处填入序列号可自动完成联网激活(请确保硬件首次激活时能够连接公网,如果确实不具备联网条件,需要使用纯离线模式激活,请下载使用百度智能边缘控制台纳管SDK)
1# SDK内提供的一些二进制文件,填入序列号可完成自动激活,以下二进制具体使用说明参考下一小节
2./edgekit_serving --cfg=./edgekit_serving.yml
3./easyedge_image_inference {model_dir} {image_name or image_directory} {serial_num}
4./easyedge_serving {res_dir} {serial_key} {host} {port}
如果是基于源码集成,设置序列号方法如下
1global_controller()->set_licence_key("")
默认情况下(联网激活或者离线激活的场景),按照上述说明正确设置序列号即可,如果是实例数鉴权模式(请在百度智能云控制台再次确认自己的序列号是实例数鉴权模式,仅实例数鉴权需要进行下面的变量或者源码设置) 实例数鉴权环境变量设置方法
1export EDGE_CONTROLLER_KEY_AUTH_MODE=2
2export EDGE_CONTROLLER_KEY_INSTANCE_UPDATE_INTERVAL=30
实例数鉴权源码设置方法
1global_controller()->set_config(easyedge::params::CONTROLLER_KEY_AUTH_MODE, 2)
2global_controller()->set_config(easyedge::params::CONTROLLER_KEY_INSTANCE_UPDATE_INTERVAL, 300)
基于预编译二进制测试图片推理和http服务
测试图片推理
模型资源文件默认已经打包在开发者下载的SDK包中。
对于硬件使用为:Intel Movidius MyRIAD2 / MyRIAD X / IGPU on Linux x86_64 / armv7hf / aarch64,在编译或运行demo程序前执行以下命令:
source ${cpp_kit位置路径}/thirdparty/openvino/bin/setupvars.sh
或者执行
source ${cpp_kit位置路径}/thirdparty/openvino/setupvars.sh
(openvino-2022.1+)
如果SDK内不包含setupvars.sh脚本,请忽略该提示
运行预编译图片推理二进制,依次填入模型文件路径(RES文件夹路径)、推理图片、序列号(序列号尽首次激活需要使用,激活后可不用填序列号也能运行二进制)
1#./easyedge_image_inference {model_dir} {image_name or image_directory} {serial_num}
2LD_LIBRARY_PATH=../lib ./easyedge_image_inference ../../../RES /xxx/cat.jpeg "1111-1111-1111-1111"
demo运行效果:
1 > ./easyedge_image_inference ../../../../RES 2.jpeg
22019-02-13 16:46:12,659 INFO [EasyEdge] [easyedge.cpp:34] 140606189016192 Baidu EasyEdge Linux Development Kit 0.2.1(20190213)
32019-02-13 16:46:14,083 INFO [EasyEdge] [paddlev2_edge_predictor.cpp:60] 140606189016192 Allocate graph success.
42019-02-13 16:46:14,326 DEBUG [EasyEdge] [paddlev2_edge_predictor.cpp:143] 140606189016192 Inference costs 168 ms
51, 1:txt_frame, p:0.994905 loc: 0.168161, 0.153654, 0.920856, 0.779621
6Done
启动http服务
bin目录下提供编译好的启动http服务二进制文件,可直接运行
1# 推荐使用 edgekit_serving 启动模型服务
2LD_LIBRARY_PATH=../lib ./edgekit_serving --cfg=./edgekit_serving.yml
3
4# 也可以使用 easyedge_serving 启动模型服务
5# ./easyedge_serving {res_dir} {serial_key} {host, default 0.0.0.0} {port, default 24401}
6# LD_LIBRARY_PATH=../lib ./easyedge_serving ../../../RES "1111-1111-1111-1111" 0.0.0.0 24401
后,日志中会显示
1HTTP(or Webservice) is now serving at 0.0.0.0:24401
字样,此时,开发者可以打开浏览器,http://{设备ip}:24401
,选择图片来进行测试,网页右侧会展示模型推理结果
同时,可以调用HTTP接口来访问服务。
请求http服务
以图像预测场景为例(非语义分割模型场景,语义分割请求方式参考后面小节详细文档),提供一张图片,请求模型服务的示例参考如下demo
1import requests
2
3with open('./1.jpg', 'rb') as f:
4 img = f.read()
5
6## params 为GET参数 data 为POST Body
7result = requests.post('http://127.0.0.1:24401/', params={'threshold': 0.1},
8 data=img).json()
9print(result)
1FileStream fs = new FileStream("./img.jpg", FileMode.Open);
2BinaryReader br = new BinaryReader(fs);
3byte[] img = br.ReadBytes((int)fs.Length);
4br.Close();
5fs.Close();
6string url = "http://127.0.0.1:8402?threshold=0.1";
7HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
8request.Method = "POST";
9Stream stream = request.GetRequestStream();
10stream.Write(img, 0, img.Length);
11stream.Close();
12
13WebResponse response = request.GetResponse();
14StreamReader sr = new StreamReader(response.GetResponseStream());
15Console.WriteLine(sr.ReadToEnd());
16sr.Close();
17response.Close();
1// 使用示例代码如下,需要安装curl
2#include <sys/stat.h>
3#include <curl/curl.h>
4#include <iostream>
5#include <string>
6#define S_ISREG(m) (((m) & 0170000) == (0100000))
7#define S_ISDIR(m) (((m) & 0170000) == (0040000))
8
9size_t write_callback(void *ptr, size_t size, size_t num, void *data) {
10 std::string *str = dynamic_cast<std::string *>((std::string *)data);
11 str->append((char *)ptr, size*num);
12 return size*num;
13}
14
15int main(int argc, char *argv[]) {
16 const char *post_data_filename = "./img.jpg";
17 FILE *fp = NULL;
18 std::string response;
19 struct stat stbuf = { 0, };
20 fp = fopen(post_data_filename, "rb");
21 if (!fp) {
22 fprintf(stderr, "Error: failed to open file \"%s\"\n", post_data_filename);
23 return -1;
24 }
25 if (fstat(fileno(fp), &stbuf) || !S_ISREG(stbuf.st_mode)) {
26 fprintf(stderr, "Error: unknown file size \"%s\"\n", post_data_filename);
27 return -1;
28 }
29 CURL *curl;
30 CURLcode res;
31 curl_global_init(CURL_GLOBAL_ALL);
32 curl = curl_easy_init();
33 if (curl != NULL) {
34 curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:24401?threshold=0.1");
35 curl_easy_setopt(curl, CURLOPT_POST, 1L);
36 curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE,(curl_off_t)stbuf.st_size);
37 curl_easy_setopt(curl, CURLOPT_READDATA, (void *)fp);
38 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
39 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
40 res = curl_easy_perform(curl);
41
42 if (res != CURLE_OK) {
43 fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
44 }
45 std::cout << response << std::endl; // response即为返回的json数据
46 curl_easy_cleanup(curl);
47 }
48 curl_global_cleanup();
49 fclose(fp);
50 return 0;
51}
1import java.io.*;
2import java.net.HttpURLConnection;
3import java.net.URL;
4
5public class Main {
6 public static void main(String[] args) {
7 String result = doPostFile("http://127.0.0.1:24401/?threshold=0.1", "./1.jpg");
8 System.out.println(result);
9 }
10
11 private static String doPostFile(String reqUrl, String fileUrl) {
12 HttpURLConnection url_con = null;
13 String responseContent = null;
14 try {
15 URL url = new URL(reqUrl);
16 url_con = (HttpURLConnection) url.openConnection();
17 url_con.setRequestMethod("POST");
18 url_con.setDoOutput(true);
19 byte[] data = Util.readFileByBytes(fileUrl);
20 url_con.getOutputStream().write(data, 0, data.length);
21 url_con.getOutputStream().flush();
22 url_con.getOutputStream().close();
23 InputStream in = url_con.getInputStream();
24 BufferedReader rd = new BufferedReader(new InputStreamReader(in , "UTF-8"));
25 String tempLine = rd.readLine();
26 StringBuffer tempStr = new StringBuffer();
27 String crlf = System.getProperty("line.separator");
28 while (tempLine != null) {
29 tempStr.append(tempLine);
30 tempStr.append(crlf);
31 tempLine = rd.readLine();
32 }
33 responseContent = tempStr.toString();
34 rd.close();
35 in.close();
36 } catch (IOException e) {
37 System.out.println("请求错误信息:" + e.getMessage());
38 } finally {
39 if (url_con != null) {
40 url_con.disconnect();
41 }
42 }
43 return responseContent;
44 }
45
46 static class Util {
47 static byte[] readFileByBytes(String fileUrl) throws IOException {
48 InputStream in = null;
49 ByteArrayOutputStream out = null;
50 try {
51 in = new FileInputStream(fileUrl);
52 out = new ByteArrayOutputStream();
53 byte[] buffer = new byte[1024];
54 int len;
55 while ((len = in.read(buffer)) != -1) {
56 out.write(buffer, 0, len);
57 }
58 return out.toByteArray();
59 } finally {
60 if (in != null) {
61 in.close();
62 }
63 if (out != null) {
64 out.close();
65 }
66 }
67 }
68 }
69}
关于http接口的详细介绍参考下面集成文档http服务章节的相关内容
集成文档
使用该方式,将运行库嵌入到开发者的程序当中。
编译demo项目
SDK src目录下有完整的demo工程,用户可参考该工程的代码实现方式将SDK集成到自己的项目中,demo工程可直接编译运行:
1cd src
2mkdir build && cd build
3cmake .. && make
4./easyedge_image_inference {模型RES文件夹} {测试图片路径}
5# 如果是NNIE引擎,使用sudo运行
6sudo ./easyedge_image_inference {模型RES文件夹} {测试图片路径}
(可选) SDK包内一般自带opencv库,可忽略该步骤。如果希望SDK自动编译安装所需要的OpenCV库,修改cmake的optionEDGE_BUILD_OPENCV
为ON
即可。
SDK会自动从网络下载opencv源码,并编译需要的module、链接。注意,此功能必须需联网。
1cmake -DEDGE_BUILD_OPENCV=ON .. && make -j16
若需自定义library search path或者gcc路径,修改CMakeList.txt即可。
对于硬件使用为Intel Movidius MyRIAD2 / MyRIAD X 的,如果宿主机找不到神经计算棒Intel® Neural Compute Stick,需要执行以下命令添加USB Rules:
1cp ${cpp_kit位置路径}/thirdparty/openvino/deployment_tools/inference_engine/external/97-myriad-usbboot.rules /etc/udev/rules.d/
2sudo udevadm control --reload-rules
3sudo udevadm trigger
4sudo ldconfig
使用流程
请优先参考Demo的使用流程。遇到错误,请优先参考文件中的注释解释,以及日志说明。
1 // step 1: 配置运行参数
2 EdgePredictorConfig config;
3 config.model_dir = {模型文件目录};
4
5 // step 2: 创建并初始化Predictor;这这里选择合适的引擎
6 auto predictor = global_controller()->CreateEdgePredictor(config);
7
8 // step 3-1: 预测图像
9 auto img = cv::imread({图片路径});
10 std::vector<EdgeResultData> results;
11 predictor->infer(img, results);
12
13 // step 3-2: 预测视频
14 std::vector<EdgeResultData> results;
15 FrameTensor frame_tensor;
16 VideoConfig video_config;
17 video_config.source_type = static_cast<SourceType>(video_type); // source_type 定义参考头文件 easyedge_video.h
18 video_config.source_value = video_src;
19 /*
20 ... more video_configs, 根据需要配置video_config的各选项
21 */
22 auto video_decoding = CreateVideoDecoding(video_config);
23 while (video_decoding->next(frame_tensor) == EDGE_OK) {
24 results.clear();
25 if (frame_tensor.is_needed) {
26 predictor->infer(frame_tensor.frame, results);
27 render(frame_tensor.frame, results, predictor->model_info().kind);
28 }
29 //video_decoding->display(frame_tensor); // 显示当前frame,需在video_config中开启配置
30 //video_decoding->save(frame_tensor); // 存储当前frame到视频,需在video_config中开启配置
31 }
对于口罩检测模型,将 EdgePredictorConfig config
修改为PaddleMultiStageConfig config
即可。
口罩检测模型请注意输入图片中人脸大小建议保持在 88到9696像素之间,可根据场景远近程度缩放图片后再传入SDK。
SDK参数配置
SDK的参数通过EdgePredictorConfig::set_config
和global_controller()->set_config
配置。set_config
的所有key在easyedge_xxxx_config.h
中。其中
PREDICTOR
前缀的key是不同模型相关的配置,通过EdgePredictorConfig::set_config
设置CONTROLLER
前缀的key是整个SDK的全局配置,通过global_controller()->set_config
设置
以序列号为例,KEY的说明如下:
1/**
2 * @brief 序列号设置;序列号不设置留空时,SDK将会自动尝试使用本地已经激活成功的有效期内的序列号
3 * 值类型:string
4 * 默认值:空
5 */
6static constexpr auto PREDICTOR_KEY_SERIAL_NUM = "PREDICTOR_KEY_SERIAL_NUM";
使用方法如下:
1EdgePredictorConfig config;
2config.model_dir = ...;
3config.set_config(params::PREDICTOR_KEY_SERIAL_NUM, "1DB7-1111-1111-D27D");
具体支持的运行参数可以参考开发工具包中的头文件的详细说明。
初始化
- 接口
1auto predictor = global_controller()->CreateEdgePredictor(config);
2predictor->init();
若返回非0,请查看输出日志排查错误原因。
预测图像
- 接口
1 /**
2 * @brief
3 * 通用接口
4 * @param image: must be BGR , HWC format (opencv default)
5 * @param result
6 * @return
7 */
8 virtual int infer(
9 cv::Mat& image, std::vector<EdgeResultData>& result
10 ) = 0;
图片的格式务必为opencv默认的BGR, HWC格式。
- 返回格式
EdgeResultData
中可以获取对应的分类信息、位置信息。
1struct EdgeResultData {
2 int index; // 分类结果的index
3 std::string label; // 分类结果的label
4 float prob; // 置信度
5
6 // 物体检测活图像分割时才有
7 float x1, y1, x2, y2; // (x1, y1): 左上角, (x2, y2): 右下角; 均为0~1的长宽比例值。
8
9 // 图像分割时才有
10 cv::Mat mask; // 0, 1 的mask
11 std::string mask_rle; // Run Length Encoding,游程编码的mask
12};
关于矩形坐标
x1 * 图片宽度 = 检测框的左上角的横坐标
y1 * 图片高度 = 检测框的左上角的纵坐标
x2 * 图片宽度 = 检测框的右下角的横坐标
y2 * 图片高度 = 检测框的右下角的纵坐标
关于图像分割mask
1cv::Mat mask为图像掩码的二维数组
2{
3 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
4 {0, 0, 0, 1, 1, 1, 0, 0, 0, 0},
5 {0, 0, 0, 1, 1, 1, 0, 0, 0, 0},
6 {0, 0, 0, 1, 1, 1, 0, 0, 0, 0},
7 {0, 0, 0, 1, 1, 1, 0, 0, 0, 0},
8 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
9}
10其中1代表为目标区域,0代表非目标区域
关于图像分割mask_rle
该字段返回了mask的游程编码,解析方式可参考 http demo
以上字段可以参考demo文件中使用opencv绘制的逻辑进行解析
预测视频
SDK 提供了支持摄像头读取、视频文件和网络视频流的解析工具类VideoDecoding
,此类提供了获取视频帧数据的便利函数。通过VideoConfig
结构体可以控制视频/摄像头的解析策略、抽帧策略、分辨率调整、结果视频存储等功能。对于抽取到的视频帧可以直接作为SDK infer 接口的参数进行预测。
- 接口
classVideoDecoding
:
1 /**
2 * @brief 获取输入源的下一帧
3 * @param frame_tensor
4 * @return
5 */
6 virtual int next(FrameTensor &frame_tensor) = 0;
7
8 /**
9 * @brief 显示当前frame_tensor中的视频帧
10 * @param frame_tensor
11 * @return
12 */
13 virtual int display(const FrameTensor &frame_tensor) = 0;
14
15 /**
16 * @brief 将当前frame_tensor中的视频帧写为本地视频文件
17 * @param frame_tensor
18 * @return
19 */
20 virtual int save(FrameTensor &frame_tensor) = 0;
21
22 /**
23 * @brief 获取视频的fps属性
24 * @return
25 */
26 virtual int get_fps() = 0;
27 /**
28 * @brief 获取视频的width属性
29 * @return
30 */
31 virtual int get_width() = 0;
32
33 /**
34 * @brief 获取视频的height属性
35 * @return
36 */
37 virtual int get_height() = 0;
struct VideoConfig
1/**
2 * @brief 视频源、抽帧策略、存储策略的设置选项
3 */
4struct VideoConfig {
5 SourceType source_type; // 输入源类型
6 std::string source_value; // 输入源地址,如视频文件路径、摄像头index、网络流地址
7 int skip_frames{0}; // 设置跳帧,每隔skip_frames帧抽取一帧,并把该抽取帧的is_needed置为true
8 int retrieve_all{false}; // 是否抽取所有frame以便于作为显示和存储,对于不满足skip_frames策略的frame,把所抽取帧的is_needed置为false
9 int input_fps{0}; // 在采取抽帧之前设置视频的fps
10 Resolution resolution{Resolution::kAuto}; // 采样分辨率,只对camera有效
11
12 bool enable_display{false};
13 std::string window_name{"EasyEdge"};
14 bool display_all{false}; // 是否显示所有frame,若为false,仅显示根据skip_frames抽取的frame
15
16 bool enable_save{false};
17 std::string save_path; // frame存储为视频文件的路径
18 bool save_all{false}; // 是否存储所有frame,若为false,仅存储根据skip_frames抽取的frame
19
20 std::map<std::string, std::string> conf;
21};
source_type
:输入源类型,支持视频文件、摄像头、网络视频流三种,值分别为1、2、3。
source_value
: 若source_type
为视频文件,该值为指向视频文件的完整路径;若source_type
为摄像头,该值为摄像头的index,如对于/dev/video0
的摄像头,则index为0;若source_type
为网络视频流,则为该视频流的完整地址。
skip_frames
:设置跳帧,每隔skip_frames帧抽取一帧,并把该抽取帧的is_needed置为true,标记为is_needed的帧是用来做预测的帧。反之,直接跳过该帧,不经过预测。
retrieve_all
:若置该项为true,则无论是否设置跳帧,所有的帧都会被抽取返回,以作为显示或存储用。
input_fps
:用于抽帧前设置fps。
resolution
:设置摄像头采样的分辨率,其值请参考easyedge_video.h
中的定义,注意该分辨率调整仅对输入源为摄像头时有效。
conf
:高级选项。部分配置会通过该map来设置。
注意:
1.如果使用VideoConfig
的display
功能,需要自行编译带有GTK选项的opencv,默认打包的opencv不包含此项。
2.使用摄像头抽帧时,如果通过resolution
设置了分辨率调整,但是不起作用,请添加如下选项:
1video_config.conf["backend"] = "2";
3.部分设备上的CSI摄像头尚未兼容,如遇到问题,可以通过工单、QQ交流群或微信交流群反馈。
具体接口调用流程,可以参考SDK中的demo_video_inference
。
设置序列号
请在网页控制台中申请序列号,并在init初始化前设置。 LinuxSDK 首次使用需联网授权。
1EdgePredictorConfig config;
2config.set_config(easyedge::params::PREDICTOR_KEY_SERIAL_NUM, "this-is-serial-num");
日志配置
设置 EdgeLogConfig
的相关参数。具体含义参考文件中的注释说明。
1EdgeLogConfig log_config;
2log_config.enable_debug = true;
3global_controller()->set_log_config(log_config);
http服务
1. 开启http服务
http服务的启动参考demo_serving.cpp
文件。
1 /**
2 * @brief 开启一个简单的demo http服务。
3 * 该方法会block直到收到sigint/sigterm。
4 * http服务里,图片的解码运行在cpu之上,可能会降低推理速度。
5 * @tparam ConfigT
6 * @param config
7 * @param host
8 * @param port
9 * @param service_id service_id user parameter, uri '/get/service_id' will respond this value with 'text/plain'
10 * @param instance_num 实例数量,根据内存/显存/时延要求调整
11 * @return
12 */
13 template<typename ConfigT>
14 int start_http_server(
15 const ConfigT &config,
16 const std::string &host,
17 int port,
18 const std::string &service_id,
19 int instance_num = 1);
2. http接口详细说明
http 请求方式一:无额外编码
URL中的get参数:
参数 | 说明 | 默认值 |
---|---|---|
threshold | 阈值过滤, 0~1 | 如不提供,则会使用模型的推荐阈值 |
HTTP POST Body即为图片的二进制内容(无需base64, 无需json)
Python请求示例 (图片测试, 针对图像分类、物体检测、实例分割等模型)
1import requests
2
3with open('./1.jpg', 'rb') as f:
4 img = f.read()
5 result = requests.post(
6 'http://127.0.0.1:24401/',
7 params={'threshold': 0.1},
8 data=img).json()
Python请求示例 (图片测试, 仅针对语义分割模型,同其他CV模型不同,语义分割模型输出为灰度图)
1import requests
2
3with open('./1.jpg', 'rb') as f:
4 img_data = f.read()
5 res = requests.post('http://127.0.0.1:24401/',
6 data=img_data)
7 with open("gray_result.png", "wb") as fb:
8 fb.write(res.content) # 语义分割模型是像素点级别输出,可将api返回结果保存为灰度图,每个像素值代表该像素分类结果
Python请求示例 (视频测试, 注意:区别于图片预测,需指定Content-Type;否则会调用图片推理接口)
1import requests
2
3with open('./1.mp4', 'rb') as f:
4 video_data = f.read()
5 result = requests.post(
6 'http://127.0.0.1:24401/',
7 params={'threshold': 0.1},
8 headers={'Content-Type': 'video'},
9 data=video_data).json()
http 请求方法二:json格式,图片传base64格式字符串
HTTP方法:POST Header如下:
参数 | 值 |
---|---|
Content-Type | application/json |
Body请求填写:
- 图像分类网络: body 中请求示例
1{
2 "image": "<base64数据>",
3 "top_num": 5
4}
body中参数详情
参数 | 是否必选 | 类型 | 可选值范围 | 说明 |
---|---|---|---|---|
image | 是 | string | - | 图像数据,base64编码,要求base64图片编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式 注意去掉头部 |
top_num | 否 | number | - | 返回分类数量,不填该参数,则默认返回全部分类结果 |
- 物体检测和实例分割网络: Body请求示例:
1{
2 "image": "<base64数据>",
3 "threshold": 0.3
4}
body中参数详情:
参数 | 是否必选 | 类型 | 可选值范围 | 说明 |
---|---|---|---|---|
image | 是 | string | - | 图像数据,base64编码,要求base64图片编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式 注意去掉头部 |
threshold | 否 | number | - | 默认为推荐阈值,也可自行根据需要进行设置 |
- 语义分割网络: Body请求示例:
1{
2 "image": "<base64数据>"
3}
body中参数详情(语义分割由于模型特殊性,不支持设置threshold值,设置了也没有意义):
参数 | 是否必选 | 类型 | 可选值范围 | 说明 |
---|---|---|---|---|
image | 是 | string | - | 图像数据,base64编码,要求base64图片编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/png/bmp格式 注意去掉头部 |
Python请求示例 (非语义分割模型参考如下代码)
1import base64
2import requests
3
4
5def main():
6 with open("1.jpg 【图片路径】", 'rb') as f:
7 result = requests.post("http://{服务ip地址}:24401/", json={
8 "image": base64.b64encode(f.read()).decode("utf8")
9 })
10 # print(result.request.body)
11 # print(result.request.headers)
12 print(result.content)
13
14
15if __name__ == '__main__':
16 main()
Python 请求示例 (针对语义分割模型,同其他CV模型不同,语义分割模型输出为灰度图)
1import base64
2import requests
3def main():
4 with open("1.jpg 【图片路径】", 'rb') as f:
5 res = requests.post("http://{服务ip地址}:24401/", json={"image": base64.b64encode(f.read()).decode("utf8")})
6 with open("gray_result.png", "wb") as fb:
7 fb.write(res.content) # 语义分割模型是像素点级别输出,可将api返回结果保存为灰度图,每个像素值代表该像素分类结果
8if __name__ == '__main__':
9 main()
http 返回数据
字段 | 类型说明 | 其他 |
---|---|---|
error_code | Number | 0为成功,非0参考message获得具体错误信息 |
results | Array | 内容为具体的识别结果。其中字段的具体含义请参考预测图像-返回格式 一节 |
cost_ms | Number | 预测耗时ms,不含网络交互时间 |
返回示例
1{
2 "cost_ms": 52,
3 "error_code": 0,
4 "results": [
5 {
6 "confidence": 0.94482421875,
7 "index": 1,
8 "label": "IronMan",
9 "x1": 0.059185408055782318,
10 "x2": 0.18795496225357056,
11 "y1": 0.14762254059314728,
12 "y2": 0.52510076761245728,
13 "mask": "...", // 图像分割模型字段
14 "trackId": 0, // 目标追踪模型字段
15 },
16
17 ]
18}
其他配置
1. 日志名称、HTTP 网页标题设置
通过global_controller的set_config
方法设置:
1global_controller()->set_config(easyedge::params::KEY_LOG_BRAND, "MY_BRAND");
效果如下:
FAQ
1. 如何处理一些 undefined reference / error while loading shared libraries?
如:./easyedge_demo: error while loading shared libraries: libeasyedge.so.1: cannot open shared object file: No such file or directory 这是因为二进制运行时ld无法找到依赖的库。如果是正确cmake && make 的程序,会自动处理好链接,一般不会出现此类问题。
遇到该问题时,请找到具体的库的位置,设置LD_LIBRARY_PATH。
示例一:libverify.so.1: cannot open shared object file: No such file or directory 链接找不到libveirfy.so文件,一般可通过 export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:../../lib 解决(实际冒号后面添加的路径以libverify.so文件所在的路径为准)
示例二:libopencv_videoio.so.4.5: cannot open shared object file: No such file or directory 链接找不到libopencv_videoio.so文件,一般可通过 export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:../../thirdparty/opencv/lib 解决(实际冒号后面添加的路径以libopencv_videoio.so所在路径为准)
2. EasyDL SDK与云服务效果不一致,如何处理?
后续我们会消除这部分差异,如果开发者发现差异较大,可联系我们协助处理。
3. 如何将我的模型运行为一个http服务?
目前cpp sdk暂未集成http运行方式;
0.4.7版本之后,可以通过start_http_server方法开启http服务。
4. 运行NNIE引擎报permission denied
日志显示:
1open sys: Permission denied
2open err
3: Permission denied
4open err
5: Permission denied
请使用sudo在root下运行。
5. 运行SDK报错 Authorization failed
情况一:日志显示 Http perform failed: null respond
在新的硬件上首次运行,必须联网激活。
SDK 能够接受HTTP_PROXY
的环境变量通过代理处理自己的网络请求。如
1export HTTP_PROXY="http://192.168.1.100:8888"
2./easyedge_demo ...
情况二:日志显示failed to get/check device id(xxx)
或者Device fingerprint mismatch(xxx)
此类情况一般是设备指纹发生了变更,包括(但不局限于)以下可能的情况:
- MAC地址变化
- 磁盘变更
- BIOS重刷
以及系统相关信息。
遇到这类情况,请确保硬件无变更,如果想更换序列号,请先删除 ~/.baidu/easyedge 目录
,再重新激活。
6. 使用libcurl请求http服务时,速度明显变慢
这是因为libcurl请求continue导致server等待数据的问题,添加空的header即可
1headers = curl_slist_append(headers, "Expect:");
7. 运行NNIE引擎报错 std::bad_alloc
检查开发板可用内存,一些比较大的网络占用内存较多,推荐内存500M以上
8. 运行二进制时,提示 libverify.so cannot open shared object file
可能cmake没有正确设置rpath, 可以设置LD_LIBRARY_PATH为sdk的lib文件夹后,再运行:
1LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../lib ./easyedge_demo
9. 编译时报错:file format not recognized
可能是因为在复制SDK时文件信息丢失。请将整个压缩包复制到目标设备中,再解压缩、编译