使用CPU实现边缘视频推断
更新时间:2022-12-01
本章将使用已经开源的 ssd_mobilenet_v1_coco_2017_11_17 模型完整演示边缘视频AI的demo。
一、前提准备
- 有一个边缘节点设备,本文使用的是宸曜 POC-351VTC,安装ubuntu-18.04-desktop-amd64。
- 本实验不强制要求使用这个品牌型号的盒子,可以直接使用PC台式机,或者使用虚拟机安装ubuntu操作系统作为边缘节点。
- 不限定是x86_64架构的操作系统,也可以是ARM64架构的,本案例在NVIDIA Jetson AGX上也能够正常运行。
- 有一个摄像头,可以说USB摄像头,也可以是网络摄像头,本实验使用后者,通过边缘节点的POE口给网络摄像头供电。
- 有一个物体识别模型,本实验用的是 ssd_mobilenet_v1_coco_2017_11_17,该模型支检测90种物体,具体清单请参考:mscoco_label_map。
- 边缘节点已经按照快速入门教程连接到云端。
二、搭建边缘硬件环境
搭建边缘硬件环境,如下图所示:
搭建步骤如下:
- 给边缘节点硬件供电
- 网络摄像头连接到POE口
- 无线网卡插一个USB口
整体环境信息如下:
-
边缘节点
- 无线网卡IP:172.30.196.93
- 与网络摄像头连接的IP:192.168.100.14
-
网络摄像头IP
- 网络摄像头IP:192.168.100.10
-
rtsp流地址:
rtsp://b:a1234567@192.168.100.10:554/Streaming/channels/1/
,这个后续会用到。rtsp协议地址通用格式为
rtsp://<username>:<password>@<ip>:<port>/<Streaming/channels/stream_number>
,各参数解释如下:<username>
:摄像头登录用户名 ,一般可以在摄像头底座当中找到<password>
:摄像头登录密码,一般可以在摄像头底座当中找到<ip>
:路由器/交换机分配给摄像头的IP地址<port>
: RTSP 协议的端口号,一般默认为554
,<Streaming/channels/stream_number>
:摄像头信道
三、模拟场景
- 摄像头连接边缘盒子,实时探测视野范围内的物体。
- 当检测到目标物体以后,保存抽帧图像,并同步发送一条消息到边缘broker服务。如果没有检测到目标物体,丢弃抽帧图像。
- 支持检测多目标物体,本场景实验检测的物体包括:
剪刀
、笔记本电脑
、书本
、键盘
和人
。
四、边缘应用描述
除了边缘节点连接云端是自动部署的系统应用,还需要在边缘节点上部署以下三个应用
序号 | 应用名 | 用途 |
---|---|---|
1 | vi-function | 模型推断结果后处理函数,将模型推断结果解析成可识别数据 |
2 | video-infer | 模型推断应用,负责加载AI模型并执行AI推断 |
3 | remote-object | 将边缘推断图片上传到云端对象存储 |
最终边缘节点上将会有6个边缘应用,如下图所示:
五、边缘应用关系
各边缘模块之间的调用关系如下图所示:
整个视频推断流程如下:
- 视频抽帧
- 加载模型执行AI推断
- 调用函数对推断结果做后处理
- 基于函数返回结果,保存达到指定阈值的图片
- 将推断结果发送到
- 订阅本地broker消息,将满足条件的推断图片上传云端对象存储minio
六、边缘应用配置
vi-function配置
- 创建函数配置项:vi-function-code,如下图所示:
- 标签:baetyl-function: python3-opencv
- 配置数据变量名:analyse.py,此变量名
analyse
后续会作为函数入口 - 配置数据变量值为python代码,代码如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
function to analyse video infer result in python
"""
import time
import numpy as np
location = "var/lib/baetyl/image/{}.jpg"
classes = {
1: 'person',73: 'laptop',76: 'keyboard',77: 'cell phone',84: 'book',87: 'scissors'
}
def handler(event, context):
"""
function handler
"""
data = np.fromstring(event, np.float32)
mat = np.reshape(data, (-1, 7))
objects = []
scores = {}
for obj in mat:
# print("obj:", obj)
clazz = int(obj[1])
if clazz in classes:
score = float(obj[2])
if classes[clazz] not in scores or scores[classes[clazz]] < score:
scores[classes[clazz]] = score
if score < 0.6:
continue
objects.append({
'class': classes[clazz],
'score': score,
'left': float(obj[3]),
'top': float(obj[4]),
'right': float(obj[5]),
'bottom': float(obj[6])
})
res = {}
res["imageDiscard"] = len(objects) == 0
res["imageObjects"] = objects
res["imageScores"] = scores
path = location.format(time.time())
if len(objects) != 0:
res["imageLocation"] = path
res["publishTopic"] = "video/infer/result"
res["type"] = "UPLOAD"
content = {}
content["localPath"] = path
content["remotePath"] = path
res["content"] = content
return res
- 创建函数应用:vi-function,添加函数服务:vi-function-service,如下图所示:
- 服务名称:vi-function-service
- 服务类型:函数服务,非容器服务
- 函数配置项:使用之前创建的配置项:vi-function-conf
- 运行时:python3-opencv
- 函数入口:analyse.handler。函数入口表示执行函数,对于Python/Node运行时来说,由函数脚本和处理函数名组成。
函数的完整路径是 [服务名称/函数入口] ,则上述 analyse 这个python函数的完整调用路径就是:
vi-function-service/analyse
。
video-infer配置
- 创建配置项:video-infer-conf,如下图所示:
- 创建配置数据,变量名为:conf.yml,变量如下:
video:
uri: 'rtsp://b:a1234567@192.168.100.10:554/Streaming/channels/1/'
limit:
fps: 1
process:
before:
swaprb: true
width: 300
height: 300
infer:
model: var/lib/baetyl/model/frozen_inference_graph.pb
config: var/lib/baetyl/model/ssd_mobilenet_v1_coco_2017_11_17.pbtxt
after:
function:
name: vi-function-service/analyse
logger:
path: var/lib/baetyl/app-log/video-infer.log
level: debug
- video-infer执行AI推断以后会调用分析函数
vi-function-service/analyse
- 分析函数返回的消息会发布到本地MQTT Broker
tcp://baetyl-broker:1883
- 创建配置项:video-infer-model,如下图所示:
- endpoint:对象存储访问地址
- bucket名称:在对象存储当中创建的bucket名称,此处为:model-upload
- 文件名称:模型文件在对象存储当中的文件名,如果在在目录当中,则这里需要填写目录名称+文件名称,此处为:ssd_mobilenet_v1_coco_2017_11_17.zip
- AK/SK:对象存储访问凭证
- MD5:模型文件ssd_mobilenet_v1_coco_2017_11_17.zip的MD5值为:
acc050a4e8fcea32edcb30ab510e63b7
。边缘节点在下载完ssd_mobilenet_v1_coco_2017_11_17.zip模型文件以后,会对模型文件执行MD5校验,以验证下载文件的完整性。 - 是否解压:选择 是 ,解压格式为ZIP。边缘节点下载模型文件ssd_mobilenet_v1_coco_2017_11_17.zip以后,会负责将其解压。在本案例当中,ssd_mobilenet_v1_coco_2017_11_17.zip解压以后会得到frozen_inference_graph.pb和ssd_mobilenet_v1_coco_2017_11_17.pbtxt这里两个模型文件。
- 创建应用:video-infer,添加容器服务:video-infer-openvino,如下图所示:
- 服务名:video-infer-openvino
- 镜像:baetyltech/video-infer:v2.1.1
-
卷配置:
- conf:添加 video-infer-conf 配置项,作为模块配置
- model:添加 video-infer-model 配置项,边缘节点会自动下载ssd_mobilenet_v1_coco_2017_11_17模型文件并解压至
/var/lib/baetyl/model
目录,然后video-infer应用会从此处加载模型文件frozen_inference_graph.pb和ssd_mobilenet_v1_coco_2017_11_17.pbtxt - image:将AI推断保存的图片从容器内目录
/var/lib/baetyl/image
映射到宿主机目录。此处参数填写的是video-infer-image
,对应宿主机目录/var/lib/baetyl/app-data/video-infer-image
-
特权选项:
- 启用特权模式:否。CPU推断,不需要开启特权模式,只有在使用AI加速卡的场景下,才可能需要特权模式。
remote-object配置
- 创配配置项:remote-mino-conf,如下图所示:
添加配置数据变量名为:conf.yml,对应的变量值如下所示:
clients:
- name: minio
kind: S3
endpoint: 'http://ip:port' # 替换为自己的对象存储地址
ak: minioadmin
sk: minioadmin
timeout: 10m
pool:
worker: 100
idletime: 30s
bucket: image-upload
rules:
- name: remote-minio-1
source:
topic: video/infer/result
qos: 1
target:
client: minio
logger:
path: var/lib/baetyl/app-log/minio.log
level: debug
- 创建应用:remote-object,然后添加容器服务:remote-object,如下图所示:
- 服务名称:remote-object
- 镜像地址:docker.io/baetyltech/remote-object:v2.1.0
-
卷配置
- minio-conf:挂载remote-object服务的配置项
- image:将宿主机当中的推断图片映射至容器内目录,这样remote-object服务才能拿到图片将其上传至minio对象存储当中。
- log:如果需要将模块的日志文件映射到宿主机,需要配置此项。
七、验证边缘AI检测结果
step1:应用部署至边缘节点
- 将上述应用全部部署至边缘节点,如下图所示:
- 检查边缘节点上的应用部署情况,如下图所示:
step2:使用MQTT Box订阅边缘节点本地broker服务
在模拟场景当中提到“当检测到目标物体以后,保存抽帧图像,并同步发送一条消息到边缘broker服务”,为了监测发送到broker服务的消息,我们使用MQTT Box工具提前订阅 video/infer/result
这个topic的消息,如下图所示:
step3:使用摄像头检测物体
- 手持摄像头,旋转一周,让摄像头能够扫描到办公桌上的
剪刀
、笔记本电脑
、书本
、键盘
,以及坐在工位上的人
。 - 实时查看订阅了
hub
模块的MQTT Box的消息界面,每检测到一个目标物体,MQTT Box就能订阅到一条消息。
- 将MQTTBox订阅的消息进行Json格式化,得到如下结果:
{
"content":{
"localPath":"var/lib/baetyl/image/1602639487.0222735.jpg",
"remotePath":"var/lib/baetyl/image/1602639487.0222735.jpg"
},
"imageCaptureTime":"2020-10-14T01:38:06.971394704Z",
"imageDiscard":false,
"imageHight":720,
"imageInferenceTime":0.046391136,
"imageLocation":"var/lib/baetyl/image/1602639487.0222735.jpg",
"imageObjects":[
{
"bottom":0.9267578125,
"class":"scissors",
"left":0.166259765625,
"right":0.505859375,
"score":0.97412109375,
"top":0.233154296875
}
],
"imageProcessTime":0.082386501,
"imageScores":{
"book":0.09637451171875,
"cell phone":0.0443115234375,
"person":0.0282135009765625,
"scissors":0.97412109375
},
"imageWidth":1280,
"publishTopic":"video/infer/result",
"type":"UPLOAD"
}
通过上述消息,可以得出如下结论:
- 检测到物体是剪刀:
"class": "scissors"
- AI推断为剪刀的得分是0.974:
"scissors": 0.974121093751
- 图片已保存:
"imageLocation": "var/lib/baetyl/image/1602639487.0222735.jpg"
,对应宿主机目录:/var/lib/baetyl/app-data/video-infer-image/1602639487.0222735.jpg
step4:验证被保存的图片当中的对象
- SSH登录到边缘节点,查看已经保存了多张抽帧图片,如下图所示:
- 将
1602639487.0222735.jpg
下载到本地电脑,确认该图片当中物体是剪刀,与MQTT Box接收到消息一致,如下图所示:
step5:验证推断结果图片上传至云端对象存储
打开minio控制台,进入到选择image-upload这个bucket,然后进度到目录var/lib/baetyl/image,我们可以看到从边缘节点上传的图片,如下图所示: