EasyEdge平台EdgeBoard适配SDK开发文档
简介
本文档介绍 EasyEdge/EasyDL在EdgeBoard®边缘计算盒/Lite计算卡上的专用软件的使用流程。
EdgeBoard系列硬件可直接应用于AI项目研发与部署,具有高性能、易携带、通用性强、开发简单等四大优点。
详细硬件参数请在AI市场浏览。
EdgeBoard产品使用手册:https://ai.baidu.com/ai-doc/HWCE/Yk3b86gvp
软核版本
CPP-SDK版本 | 对应软核版本 |
---|---|
1.3.2、1.3.4、1.3.5 | 1.8.1 |
1.3.0、1.3.1、1.3.2、1.3.4 | 1.8 |
0.5.7-1.2.1 | 1.5 |
0.5.2+ | 1.4 |
SDK升级需配合EdgeBoard硬件软核升级,建议升级软核为SDK对应版本,否则可能出现结果错误或者其他异常。
可以通过
dmesg | grep "DRIVER Version"
命令获取EdgeBoard当前的软核版本
获取序列号
在EasyEdge/EasyDL生成SDK后,点击获取序列号进入控制台获取。EasyEdge控制台、EasyDL控制台、BML控制台。
如果是在AI市场购买的EdgeBoard-FZ软硬一体方案,也可以在AI市场订单详情获取序列号。
更换序列号、更换设备时,首次使用需要联网激活。激活成功之后,有效期内可离线使用。
Release Notes
注意*:升级完成相应的软核之后需要重启机器生效。 sdk对应的软核说明:
部署包中包含多版本SDK:
- baidueasyedge_linux_cpp_aarch64_EdgeBoardFZ1.8*:适用于EdgeBoard 1.5+软核
- baidueasyedge_linux_cpp_aarch64_EdgeBoardFZ1.4*:适用于EdgeBoard 1.4的软核
如果客户使用的软核是mobile版本的,需要使用FZ1.4的SDK;如果不是mobile 版本,可以选择FZ1.5+(目前最高版本更新至1.8.1)版本的SDK使用。
1.5+版本的软核以及cpp-sdk更新情况如下表所示:
时间 | cpp-sdk版本 | 说明 | EdgeBoardFZ1.5+(非mobile版本)对应的软核版本以及特性 | |
---|---|---|---|---|
2021.12.20 | 1.3.5 | 升级预测引擎为PaddleLite 1.8.1 | https://ai.baidu.com/ai-doc/HWCE/Fkuqounlk | |
2021.10.15 | 1.3.2、1.3.4、1.3.4 | 推理库支持了Ubuntu18.04文件系统 | https://ai.baidu.com/ai-doc/HWCE/Fkuqounlk(含有EB升级Ubuntu18.04系统的步骤) | |
2021.06.29 | 1.3.1 | 视频流解析支持分辨率调整;预测引擎升级 | https://ai.baidu.com/ai-doc/HWCE/Ikqgcqt5x | |
2021.05.14 | 1.3.0 | 新增视频流接入支持;展示已发布模型性能评估报告 | https://ai.baidu.com/ai-doc/HWCE/Ikqgcqt5x | |
2021.05.14 | 1.2.1 | 功能无更新 | https://ai.baidu.com/ai-doc/HWCE/okqiwkm32 | |
2020.10.29 | 0.5.7 | 预测引擎切换为PaddleLite 1.5 | - | |
2019.12.27 | 0.4.5 | 引擎升级,支持zu5/zu3,支持EasyDL 高精度检测模型 | - | |
2019.07.25 | 0.4.0 | EdgeBoard SDK Release! | - |
mobile软核以及cpp-sdk更新情况如下表所示: | 时间 | cpp-sdk版本 | 说明 |EdgeBoardFZ1.4(mobile版本)对应的软核版本以及特性 | | --- | ---- | ---- |---- |---- | |2021.05.14|1.2.1|功能无更新|https://ai.baidu.com/ai-doc/HWCE/okqiwkm32|https://ai.baidu.com/ai-doc/HWCE/Lkqiwlziw||
快速开始
开发者从EasyEdge/EasyDL下载的软件部署包中,包含了简单易用的SDK和Demo。只需简单的几个步骤,即可快速部署运行EdgeBoard计算盒。
SDK文件结构
baidu_easyedge_linux_cpp_aarch64_EdgeBoardFZ1.8_*
├── ReadMe.txt
├── bin
│ ├── easyedge_image_inference
│ ├── easyedge_serving
│ └── easyedge_video_inference
├── include
│ └── easyedge
├── lib
│ ├── libeasyedge.so -> libeasyedge.so.1
│ ├── libeasyedge.so.1 -> libeasyedge.so.1.3.1
│ ├── libeasyedge.so.1.3.1
│ ├── libeasyedge_static.a
│ ├── libeasyedge_videoio.so -> libeasyedge_videoio.so.1
│ ├── libeasyedge_videoio.so.1 -> libeasyedge_videoio.so.1.3.1
│ ├── libeasyedge_videoio.so.1.3.1
│ ├── libeasyedge_videoio_static.a
│ ├── libpaddle_full_api_shared.so -> libpaddle_full_api_shared.so.1.8.1
│ ├── libpaddle_full_api_shared.so.1.8.1
│ ├── libverify.so -> libverify.so.1
│ ├── libverify.so.1 -> libverify.so.1.0.0
│ └── libverify.so.1.0.0
├── now_sre.log
├── src
│ ├── CMakeLists.txt
│ ├── cmake
│ ├── common
│ ├── demo_image_inference
│ ├── demo_serving
│ └── demo_video_inference
└── thirdparty
└── opencv
1.1.0+的SDK自带OpenCV,src编译的时候会引用thirdparty/opencv路径下的头文件和库文件。
Demo使用流程
用户在AI市场购买计算盒之后,请参考以下步骤进行集成和试用。
1. 将计算盒连接电源
指示灯亮起,等待约1分钟。
- 参考EdgeBoard使用文档配置网口或串口连接。登录EdgeBoard计算盒。
- 加载驱动(开机加载一次即可)。
insmod /home/root/workspace/driver/{zu9|zu5|zu3}/fpgadrv.ko
根据购买的版本,选择合适的驱动。若未加载驱动,可能报错:
Failed to to fpga device: -1
- 设置系统时间(系统时间必须正确)
date --set "2019-5-18 20:48:00"
2. (可选)启动HTTP服务
部署包中附带了HTTP服务功能,开发者可以进入SDK根目录,运行easyedge_serving
程序启动HTTP服务。
# ./easyedge_serving {RES目录} {序列号} {绑定的host,默认0.0.0.0} {绑定的端口,默认24401}
cd ${SDK_ROOT}
export LD_LIBRARY_PATH=./lib
./demo/easyedge_serving ../../../RES "1111-1111-1111-1111"
日志显示
2019-07-18 13:27:05,941 INFO [EasyEdge] [http_server.cpp:136] 547974369280 Serving at 0.0.0.0:24401
则启动成功。此时可直接在浏览器中输入http://{EdgeBoard计算盒ip地址}:24401/
,在h5中测试模型效果。
同时,可以调用HTTP接口来访问盒子。具体参考下文接口说明。
EdgeBoard HTTP Server 目前使用的是单线程处理请求。
3. 使用序列号激活
修改demo/demo.cpp,将前面申请的序列号填入:
global_controller()->set_licence_key
4. 编译运行Demo
编译:
cd src
mkdir build && cd build
cmake .. && make
运行
./easyedge_image_inference {RES资源文件夹路径} {测试图片路径}
便可看到识别结果。
使用说明
使用流程
首次使用需要联网激活。激活成功之后,有效期内可离线使用。
- 设置序列号
global_controller()->set_licence_key
- 配置
PaddleFluidConfig
- 新建
Predictor
:global_controller()->CreateEdgePredictor(config);
- 初始化
predictor->init()
- 传入图片开始识别
predictor->infer(img, ...);
目前EdgeBoard暂不支持并行多模型计算。
接口说明
预测图片
/**
* @brief 同步预测接口
* inference synchronous
* Supported by most chip and engine
* @param image: must be BGR , HWC format (opencv default)
* @param result
* @param threshold
* @return
*/
virtual int infer(
cv::Mat &image, std::vector<EdgeResultData> &result, float threshold = 0.1
) = 0;
识别结果说明
EdgeResultData
中可以获取对应的分类信息、位置信息。
struct EdgeResultData {
int index; // 分类结果的index
std::string label; // 分类结果的label
float prob; // 置信度
// object detection field
float x1, y1, x2, y2; // (x1, y1): 左上角, (x2, y2): 右下角; 均为0~1的长宽比例值。
};
关于矩形坐标
x1 * 图片宽度 = 检测框的左上角的横坐标
y1 * 图片高度 = 检测框的左上角的纵坐标
x2 * 图片宽度 = 检测框的右下角的横坐标
y2 * 图片高度 = 检测框的右下角的纵坐标
可以参考demo文件中使用opencv绘制矩形的逻辑。
HTTP 私有服务请求说明
http 请求参数
URL中的get参数:
参数 | 说明 | 默认值 |
---|---|---|
threshold | 阈值过滤, 0~1 | 0.1 |
HTTP POST Body即为图片的二进制内容(无需base64, 无需json)
~~~codeset
import requests
with open('./1.jpg', 'rb') as f:
img = f.read()
result = requests.post(
'http://127.0.0.1:24401/',
params={'threshold': 0.1},
data=img).json()
FileStream fs = new FileStream("./img.jpg", FileMode.Open);
BinaryReader br = new BinaryReader(fs);
byte[] img = br.ReadBytes((int)fs.Length);
br.Close();
fs.Close();
string url = "http://127.0.0.1:8402?threshold=0.1";
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
request.Method = "POST";
Stream stream = request.GetRequestStream();
stream.Write(img, 0, img.Length);
stream.Close();
WebResponse response = request.GetResponse();
StreamReader sr = new StreamReader(response.GetResponseStream());
Console.WriteLine(sr.ReadToEnd());
sr.Close();
response.Close();
"""
需要安装curl
"""
#include <sys/stat.h>
#include <curl/curl.h>
#include <iostream>
#include <string>
#define S_ISREG(m) (((m) & 0170000) == (0100000))
#define S_ISDIR(m) (((m) & 0170000) == (0040000))
size_t write_callback(void *ptr, size_t size, size_t num, void *data) {
std::string *str = dynamic_cast<std::string *>((std::string *)data);
str->append((char *)ptr, size*num);
return size*num;
}
int main(int argc, char *argv[]) {
const char *post_data_filename = "./img.jpg";
FILE *fp = NULL;
std::string response;
struct stat stbuf = { 0, };
fp = fopen(post_data_filename, "rb");
if (!fp) {
fprintf(stderr, "Error: failed to open file "%s"
", post_data_filename);
return -1;
}
if (fstat(fileno(fp), &stbuf) || !S_ISREG(stbuf.st_mode)) {
fprintf(stderr, "Error: unknown file size "%s"
", post_data_filename);
return -1;
}
CURL *curl;
CURLcode res;
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if (curl != NULL) {
curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:24401?threshold=0.1");
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE,(curl_off_t)stbuf.st_size);
curl_easy_setopt(curl, CURLOPT_READDATA, (void *)fp);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s
", curl_easy_strerror(res));
}
std::cout << response << std::endl; // response即为返回的json数据
curl_easy_cleanup(curl);
}
curl_global_cleanup();
fclose(fp);
return 0;
}
[Java请求示例](http://ai.baidu.com/forum/topic/show/943765)
#### http 返回数据
| 字段 | 类型说明 | 其他 |
| ---------- | -------- | ------------------------------------------------------------ |
| error_code | Number | 0为成功,非0参考message获得具体错误信息 |
| results | Array | 内容为具体的识别结果。其中字段的具体含义请参考`预测图像-返回格式`一节 |
| cost_ms | Number | 预测耗时ms,不含网络交互时间 |
返回示例
```json
{
"cost_ms": 52,
"error_code": 0,
"results": [
{
"confidence": 0.94482421875,
"index": 1,
"label": "IronMan",
"x1": 0.059185408055782318,
"x2": 0.18795496225357056,
"y1": 0.14762254059314728,
"y2": 0.52510076761245728
},
{
"confidence": 0.94091796875,
"index": 1,
"label": "IronMan",
"x1": 0.79151463508605957,
"x2": 0.92310667037963867,
"y1": 0.045728668570518494,
"y2": 0.42920106649398804
}
]
}
```
### 预测视频
SDK 提供了支持摄像头读取、视频文件和网络视频流的解析工具类`VideoDecoding`,此类提供了获取视频帧数据的便利函数。通过`VideoConfig`结构体可以控制视频/摄像头的解析策略、抽帧策略、分辨率调整、结果视频存储等功能。对于抽取到的视频帧可以直接作为SDK infer 接口的参数进行预测。
* 接口
class`VideoDecoding`:
```
/**
* @brief 获取输入源的下一帧
* @param frame_tensor
* @return
*/
virtual int next(FrameTensor &frame_tensor) = 0;
/**
* @brief 显示当前frame_tensor中的视频帧
* @param frame_tensor
* @return
*/
virtual int display(const FrameTensor &frame_tensor) = 0;
/**
* @brief 将当前frame_tensor中的视频帧写为本地视频文件
* @param frame_tensor
* @return
*/
virtual int save(FrameTensor &frame_tensor) = 0;
/**
* @brief 获取视频的fps属性
* @return
*/
virtual int get_fps() = 0;
/**
* @brief 获取视频的width属性
* @return
*/
virtual int get_width() = 0;
/**
* @brief 获取视频的height属性
* @return
*/
virtual int get_height() = 0;
```
struct `VideoConfig`
```
/**
* @brief 视频源、抽帧策略、存储策略的设置选项
*/
struct VideoConfig {
SourceType source_type; // 输入源类型
std::string source_value; // 输入源地址,如视频文件路径、摄像头index、网络流地址
int skip_frames{0}; // 设置跳帧,每隔skip_frames帧抽取一帧,并把该抽取帧的is_needed置为true
int retrieve_all{false}; // 是否抽取所有frame以便于作为显示和存储,对于不满足skip_frames策略的frame,把所抽取帧的is_needed置为false
int input_fps{0}; // 在采取抽帧之前设置视频的fps
Resolution resolution{Resolution::kAuto}; // 采样分辨率,只对camera有效
bool enable_display{false};
std::string window_name{"EasyEdge"};
bool display_all{false}; // 是否显示所有frame,若为false,仅显示根据skip_frames抽取的frame
bool enable_save{false};
std::string save_path; // frame存储为视频文件的路径
bool save_all{false}; // 是否存储所有frame,若为false,仅存储根据skip_frames抽取的frame
std::map::string, std::string> conf;
};
```
`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`设置了分辨率调整,但是不起作用,请添加如下选项:
```
video_config.conf["backend"] = "2";
```
3.部分设备上的CSI摄像头尚未兼容,如遇到问题,可以通过工单、QQ交流群或微信交流群反馈。
具体接口调用流程,可以参考SDK中的`demo_video_inference`。
### 错误说明
SDK所有主动报出的错误,均覆盖在`EdgeStatus`枚举中。同时SDK会有详细的错误日志,开发者可以打开Debug日志查看额外说明:
```cpp
EdgeLogConfig log_config;
log_config.enable_debug = true;
global_controller()->set_log_config(log_config);
```
## FAQ
### 1. 如何处理一些 undefined reference?
> 如:undefined reference to `curl_easy_setopt@CURL_OPENSSL_3'
可以通过安装`libcurl3 libcurl-openssl1.0-dev`来解决。
如果开发者想不想使用低版本的openssl(如Ubuntu 18.04), 可以link静态库`easyedge_static.a`,自己指定需要的Library的版本。
示例:修改CMakeList.txt
```bash
find_package(CURL REQUIRED)
target_link_libraries(easyedge_demo ${OpenCV_LIBS} easyedge_static pthread ${CURL_LIBRARIES} paddle-mobile)
```
### 2. error while loading shared libraries: libeasyedge.so.0.4.0: cannot open shared object file: No such file or directory
类似错误包括`libpaddle-mobile.so`找不到。
直接运行SDK自带的二进制可能会有这个问题,设置LD_LIBRARY_PATH为SDK部署包中的lib目录即可。
开发者自行使用CMake编译的二进制可以有效管理.so的依赖。
### 3. 使用libcurl请求http服务时,速度明显变慢
这是因为libcurl请求continue导致server等待数据的问题,添加空的header即可
```bash
headers = curl_slist_append(headers, "Expect:");
```
### 4. 预测过程中报内存不足“Killed”
此问题仅出现在ZU5,因为FZ5A带vcu,给他预留的内存过大导致,如果用不到VCU可以把这部分改小。修改/run/media/mmcblk1p1/uEnv.txt:
```
ethaddr=00:0a:35:00:00:09
uenvcmd=fatload mmc 1 0x3000000 image.ub && bootm 0x3000000
bootargs=earlycon console=ttyPS0,115200 clk_ignore_unused cpuidle.off=1 root=/dev/mmcblk1p2 rw rootwait cma=128M
```
注意中间空行要保留。
### 5. 预测结果异常
如果购买的计算盒较早,驱动文件较旧,而SDK比较新(或SDK比较旧,但是计算盒较新),可能出现结果异常,如结果均为空或者`nan`。
请参考“内核版本”小节更新内核和驱动版本。
### 6. 编译过程报错file format not recognized
```
libeasyedge.so: file format not recognized; treating as linker script
```
下载的SDK zip包需要放到板子内部后,再解压、编译。
### 7. 提示 driver_version(1.4.0) not match paddle_lite_version(1.5.1)
需更新驱动,否则可能导致结果异常。参考“内核版本”小节。
### 8. 运行SDK报错 Authorization failed
#### 情况一:日志显示 `Http perform failed: null respond`
在新的硬件上首次运行,必须联网激活。
SDK 能够接受`HTTP_PROXY` 的环境变量通过代理处理自己的网络请求。如
```bash
export HTTP_PROXY="http://192.168.1.100:8888"
./easyedge_demo ...
```
#### 情况二:日志显示`failed to get/check device id(xxx)`或者`Device fingerprint mismatch(xxx)`
此类情况一般是设备指纹发生了变更,包括(但不局限于)以下可能的情况:
* MAC地址变化
* 磁盘变更
* BIOS重刷
以及系统相关信息。
遇到这类情况,请确保硬件无变更,如果想更换序列号,请先删除 `~/.baidu/easyedge 目录`,再重新激活。