- 资产集市
- 教学
- 实践
- AI说
- 案例库
- 生态合作
- 专区
中国站
简体中文发布于30个月以前
通过上两个教程的介绍,我们了解了ModelBox
应用、通用功能单元的基本结构和开发流程,本文将使用ModelBox
开发一个AI应用:读取摄像头,解码出视频帧,在画面中检测手部并框出来,再输出画面到本地屏幕。读者将了解到ModelBox
中推理功能单元的基本结构和开发流程。
对于这样的应用,因为涉及到模型推理,除了OpenCV,我们还需要模型推理库,伪代码如下:
import cv2
import onnx_model
# 初始化摄像头,参数0表示设备的第一个摄像头
cap = cv2.VideoCapture(0)
# 判断初始化是否成功
if not cap.isOpened():
print('failed to open camera 0')
exit()
# 初始化手部检测模型
hand_det = onnx_model('./hand_det.onnx')
while True:
# 读取一帧视频图像,ret表示读取是否成功
ret, frame = cap.read()
# 图像预处理
img_pre = hand_det.preprocess(frame)
# 手部检测推理
output = hand_det.infer(img_pre)
# 检测结果后处理与画框
hand_bboxes = hand_det.postprocess(output)
draw_hand_bboxes(frame, hand_bboxes)
# 打开一个名为hand_det的窗口,显示图像
cv2.imshow('hand_det', frame)
# 阻塞等待键盘响应1ms,如果按下q键则跳出循环
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 释放摄像头资源
cap.release()
# 关闭所有窗口
cv2.destroyAllWindows()
这里我们以ONNXRuntime推理库为例,需要使用ONNXRuntime相关的api实现onnx模型的加载、推理等逻辑,另外模型推理前的预处理、推理后的后处理等逻辑也需要实现。也就是说,在传统开发模式中,开发者需要学习推理框架的使用,如果模型格式有变动,可能需要换用其他推理框架。
这个应用对应的ModelBox
版本已经做成模板放在华为云OBS中,可以用sdk中的solution.bat
工具下载,接下来我们给出该应用在ModelBox
中的完整开发过程:
执行.\solution.bat -l
可看到当前公开的技能模板:
PS ███> .\solution.bat -l
...
Solutions name:
mask_det_yolo3
...
hand_det_yolox
hand_tracking_yolox
single_hand_pose_yolox_mbv2
multi_hand_pose_yolox_mbv2
结果中的hand_det_yolox即为手部检测应用模板,可使用如下命令下载模板:
PS ███> .\solution.bat -s hand_det_yolox
...
solution.bat
工具的参数中,-l
代表list
,即列出当前已有的模板名称;-s
代表solution-name
,即下载对应名称的模板。下载下来的模板资源,将存放在ModelBox
核心库的solution
目录下。
在ModelBox
sdk目录下使用create.bat
创建hand_det
工程
PS ███> .\create.bat -t server -n hand_det -s hand_det_yolox
sdk version is modelbox-xxx
success: create hand_det in ███\modelbox\workspace
与之前创建工程的命令相比,末尾多了-s
参数,表示将使用后面参数值代表的模板创建工程,而不是创建空的工程。
workspace
目录下将创建出hand_det
工程,工程内容如下所示:
hand_det
|--bin
│ |--main.bat:应用执行入口
│ |--mock_task.toml:应用在本地执行时的输入输出配置,此应用默认使用本地视频文件为输入源,最终结果输出到另一本地视频文件,可根据需要修改
|--CMake:存放一些自定义CMake函数
|--data:存放应用运行所需要的图片、视频、文本、配置等数据
│ |--hand.mp4:手部检测测试用视频文件
|--dependence
│ |--modelbox_requirements.txt:应用运行依赖的外部库在此文件定义
|--etc
│ |--flowunit:应用所需的功能单元存放在此目录
│ │ |--cpp:存放C++功能单元编译后的动态链接库,此应用没有C++功能单元
│ │ |--draw_hand_bbox:手部检测框画图功能单元
│ │ |--yolox_post:手部检测使用的是YOLOX模型,此处即为后处理功能单元
|--flowunit_cpp:存放C++功能单元的源代码,此应用没有C++功能单元
|--graph:存放流程图
│ |--hand_det.toml:默认流程图,使用本地视频文件作为输入源
│ |--hand_det_camera.toml:摄像头输入对应的流程图
│ |--modelbox.conf:modelbox相关配置
|--hilens_data_dir:存放应用输出的结果文件、日志、性能统计信息
|--model:推理功能单元目录
│ |--detect_hand:手部检测推理功能单元
│ │ |--detect_hand.toml:手部检测推理功能单元的配置文件
│ │ |--yolox_hand.onnx:手部检测onnx模型
|--build_project.sh:应用构建脚本
|--CMakeLists.txt
|--rpm:打包rpm时生成的目录,将存放rpm包所需数据
|--rpm_copyothers.sh:rpm打包时的辅助脚本
hand_det
应用包含模型推理部分,前面章节有介绍,在传统开发模式中,开发者需要学习推理框架的使用;而ModelBox
内置了主流的推理引擎,如TensorFlow,TensorRT,LibTorch,Ascend ACL,MindSpore,以及Windows版本中所用的ONNXRuntime。在开发推理功能单元时,只需要准备模型文件,并配置对应的toml文件,即可完成推理功能单元的开发,无需掌握推理引擎的开发接口。
hand_det_yolox模板中已经内置了手部检测模型,这个模型采用的是YOLOX网络结构,训练数据集用的是开源的100DOH,在ModelArts
的Notebook
环境中训练后,再转换成对应平台的模型格式:onnx格式可以用在Windows设备上,RK系列设备上需要转换为rknn格式。YOLOX是YOLO系列的优化版本,引入了解耦头、数据增强、无锚点以及标签分类等目标检测领域的优秀进展,拥有较好的精度表现,同时对工程部署友好。
模型的训练与转换教程已经开放在AI Gallery
中,其中包含训练数据、训练代码、模型转换脚本,以及详细的指导文档。开发者如果希望尝试自己训练模型,或者对模板中提供的模型效果不满意,可以进入 YOLOX手部检测模型训练与转换 页面,点击右上角的Run in ModelArts按钮,跟随教程一步步操作,也可以修改其中的代码、更换新的数据集训练出自己的模型。
本应用已经包含了推理功能单元,如果需要创建新的推理功能单元,命令如下:
PS ███> .\create.bat -t infer -n xxx_infer -p hand_det
create.bat
工具使用时,-t infer
即表示创建的是推理功能单元;-n xxx_infer
表示创建的功能单元名称为xxx_infer
;-p hand_det
表示所创建的功能单元属于hand_det
应用。
hand_det
工程的model
目录(此目录将作为推理功能单元的默认存放目录)下,生成了detect_hand
文件夹,其中包含推理功能单元所需的模型文件和toml配置文件,即推理功能单元目录结构如下:
[flowunit_name]
|--[flowunit_name].toml # 推理功能单元属性配置文件
|--[model_name].onnx # 模型文件,此处以onnx模型为例,根据需求也可以使用其他模型格式
推理功能单元的模型路径和输入输出节点信息定义在配置文件中,打开detect_hand.toml
,看到其内容为:
# 基础配置
[base]
name = "detect_hand" # 功能单元名称
device = "cpu" # 功能单元运行的设备类型
version = "1.0.0" # 功能单元版本号
type = "inference" # 功能单元类型,推理功能单元此处为固定值inference
virtual_type = "onnx" # 推理引擎类型,Windows当前只支持onnx模型的推理
group_type = "Inference" # 功能单元分组信息, 推理功能单元默认为Inference
description = "yolox model for hand detection" # 功能单元的描述信息
entry = "./yolox_hand.onnx" # 模型文件路径,默认在当前路径下
# 模型输入节点描述:yolox onnx模型输入是图片经过预处理后的float数据
[input]
[input.input1]
name = "input"
type = "float"
device = "cpu"
# 模型输出节点描述:yolox模型输出是float格式的特征向量
[output]
[output.output1]
name = "output"
type = "float"
即detect_hand
推理功能单元使用同路径下的yolox_hand.onnx模型文件,该模型包含一个名为input、接收float格式数据的输入节点,一个名为output、输出float格式数据的输出节点。
推理功能单元无需编译,通常会直接将其应用到流程图中运行查看效果,推理功能单元路径需添加到流程图的扫描路径driver.dir中,流程图运行时,会自动扫描该路径并加载推理功能单元。
hand_det
工程graph
目录下包含两个流程图,其中与工程同名的hand_det.toml
流程图,其内容为(以Windows版ModelBox
为例):
[driver]
# 功能单元的扫描路径,包含在[]中,多个路径使用,分隔
# ${HILENS_APP_ROOT} 表示当前应用的实际路径
# ${HILENS_MB_SDK_PATH} 表示ModelBox核心库的实际路径
dir = [
"${HILENS_APP_ROOT}/etc/flowunit",
"${HILENS_APP_ROOT}/etc/flowunit/cpp",
"${HILENS_APP_ROOT}/model",
"${HILENS_MB_SDK_PATH}/flowunit",
]
skip-default = true
[profile]
# 通过配置profile和trace开关启用应用的性能统计
profile = false # 是否记录profile信息,每隔60s记录一次统计信息
trace = false # 是否记录trace信息,在任务执行过程中和结束时,输出统计信息
dir = "${HILENS_DATA_DIR}/mb_profile" # profile/trace信息的保存位置
[flow]
desc = "hand detection example using yolox for local video or rtsp video stream" # 应用的简单描述
[graph]
format = "graphviz" # 流程图的格式,当前仅支持graphviz
graphconf = """digraph hand_detection {
node [shape=Mrecord]
queue_size = 4
batch_size = 1
# 定义节点,即功能单元及其属性
input1[type=input, flowunit=input, device=cpu, deviceid=0]
data_source_parser[type=flowunit, flowunit=data_source_parser, device=cpu, deviceid=0]
video_demuxer[type=flowunit, flowunit=video_demuxer, device=cpu, deviceid=0]
video_decoder[type=flowunit, flowunit=video_decoder, device=cpu, deviceid=0, pix_fmt="bgr"]
image_resize[type=flowunit, flowunit=resize, device=cpu, deviceid=0, image_width=320, image_height=320]
color_transpose[type=flowunit, flowunit=packed_planar_transpose, device=cpu, deviceid=0]
normalize[type=flowunit flowunit=normalize device=cpu deviceid=0 standard_deviation_inverse="1., 1., 1."]
detect_hand[type=flowunit, flowunit=detect_hand, device=cpu, deviceid=0]
yolox_post[type=flowunit, flowunit=yolox_post, device=cpu, deviceid=0]
draw_hand_bbox[type=flowunit, flowunit=draw_hand_bbox, device=cpu, deviceid=0]
video_out[type=flowunit, flowunit=video_out, device=cpu, deviceid=0]
# 定义边,即功能间的数据传递关系
input1:input -> data_source_parser:in_data
data_source_parser:out_video_url -> video_demuxer:in_video_url
video_demuxer:out_video_packet -> video_decoder:in_video_packet
video_decoder:out_video_frame -> image_resize:in_image
image_resize:out_image -> color_transpose:in_image
color_transpose:out_image -> normalize:in_data
normalize:out_data -> detect_hand:input
detect_hand:output -> yolox_post:in_feat
video_decoder:out_video_frame -> draw_hand_bbox:in_image
yolox_post:out_data -> draw_hand_bbox:in_bbox
draw_hand_bbox:out_image -> video_out:in_video_frame
}"""
除了之前教程中介绍过的input、data_source_parser、video_out等功能单元,本应用还使用了如下ModelBox
内置的功能单元:
width
/height
)、缩放操作插值方法(interpolation
,默认为"inter_nearest",此外还支持"inter_linear"、“inter_cubic”、"inter_area"等)standard_deviation_inverse
,参数顺序和数据维度对应除了这些内置功能单元,以及前文介绍的detect_hand
推理功能单元外,还包括通用Python功能单元yolox_post,负责手部检测推理后的数据后处理,即从推理结果中解码出检测框;以及draw_hand_bbox,负责把检测框画到原始图像上。通用Python功能单元的开发前面的教程已有介绍,此处不赘述。
查看任务配置文件bin/mock_task.toml
,可以看到其中的任务输入和任务输出配置为如下内容::
[input]
type = "url"
url = "${HILENS_APP_ROOT}/data/hand.mp4" # 表示输入源为本地视频文件
[output]
type = "local"
url = "${HILENS_APP_ROOT}/hilens_data_dir/hand_detection_result.mp4" # 表示输出为本地视频文件
该流程图在本地运行时的逻辑过程是:data_source_parser解析bin/mock_task.toml
文件中输入配置的data/hand.mp4
文件,video_demuxer和video_decoder对该文件进行解码,resize、packed_planar_transpose、normalize对原始图像进行缩放、转码、归一化等预处理,然后detect_hand在预处理后的图像上进行手部检测,yolox_post从推理结果中解码出检测框,draw_hand_bbox把检测框画到原始图像上,最后video_out将图像输出到bin/mock_task.toml
文件中输出配置的hilens_data_dir/hand_detection_result.mp4
文件中。
启动应用前执行.\build_project.sh
进行工程构建,该脚本将编译自定义的C++功能单元(本应用不涉及)、将应用运行时会用到的配置文件转码为Unix格式(防止执行过程中的格式错误):
PS ███> .\build_project.sh
...
PS ███>
然后执行.\bin\main.bat
运行应用:
PS ███> .\bin\main.bat
...
运行结束后在hilens_data_dir
目录下生成了hand_detection_result.mp4
文件,可以打开查看:
除了测试视频文件外,我们还可以使用PC自带或者外接的USB摄像头进行手部实时检测,对应的流程图为hand_det_camera.toml
,其内容如下:
[driver]
# 功能单元的扫描路径,包含在[]中,多个路径使用,分隔
# ${HILENS_APP_ROOT} 表示当前应用的实际路径
# ${HILENS_MB_SDK_PATH} 表示ModelBox核心库的实际路径
dir = [
"${HILENS_APP_ROOT}/etc/flowunit",
"${HILENS_APP_ROOT}/etc/flowunit/cpp",
"${HILENS_APP_ROOT}/model",
"${HILENS_MB_SDK_PATH}/flowunit",
]
skip-default = true
[profile]
# 通过配置profile和trace开关启用应用的性能统计
profile = false # 是否记录profile信息,每隔60s记录一次统计信息
trace = false # 是否记录trace信息,在任务执行过程中和结束时,输出统计信息
dir = "${HILENS_DATA_DIR}/mb_profile" # profile/trace信息的保存位置
[flow]
desc = "hand detection example using yolox for local USB camera" # 应用的简单描述
[graph]
format = "graphviz" # 流程图的格式,当前仅支持graphviz
graphconf = """digraph hand_detection {
node [shape=Mrecord]
queue_size = 4
batch_size = 1
# 定义节点,即功能单元及其属性
input1[type=input, flowunit=input, device=cpu, deviceid=0]
data_source_parser[type=flowunit, flowunit=data_source_parser, device=cpu, deviceid=0]
local_camera[type=flowunit, flowunit=local_camera, device=cpu, deviceid=0, pix_fmt="bgr", cam_width=1280, cam_height=720]
image_resize[type=flowunit, flowunit=resize, device=cpu, deviceid=0, image_width=320, image_height=320]
color_transpose[type=flowunit flowunit=packed_planar_transpose device=cpu deviceid=0]
normalize[type=flowunit flowunit=normalize device=cpu deviceid=0 standard_deviation_inverse="1., 1., 1."]
detect_hand[type=flowunit, flowunit=detect_hand, device=cpu, deviceid=0]
yolox_post[type=flowunit, flowunit=yolox_post, device=cpu, deviceid=0]
draw_hand_bbox[type=flowunit, flowunit=draw_hand_bbox, device=cpu, deviceid=0]
video_out[type=flowunit, flowunit=video_out, device=cpu, deviceid=0]
# 定义边,即功能间的数据传递关系
input1:input -> data_source_parser:in_data
data_source_parser:out_video_url -> local_camera:in_camera_packet
local_camera:out_camera_frame -> image_resize:in_image
image_resize:out_image -> color_transpose:in_image
color_transpose:out_image -> normalize:in_data
normalize:out_data -> detect_hand:input
detect_hand:output -> yolox_post:in_feat
local_camera:out_camera_frame -> draw_hand_bbox:in_image
yolox_post:out_data -> draw_hand_bbox:in_bbox
draw_hand_bbox:out_image -> video_out:in_video_frame
}"""
与hand_det.toml
相比,这个流程图使用local_camera替换了video_demuxer和video_decoder功能单元,其他部分是一致的。
打开工程目录下bin/mock_task.toml
文件,修改其中的任务输入和任务输出配置为如下内容:
[input]
type = "url"
url = "0" # 表示0号摄像头,即PC自带摄像头,若PC无摄像头需外接USB摄像头
[output]
type = "local"
url = "0:hand_det" # 表示名为```hand_det```的本地窗口
即使用编号为0的摄像头(默认为PC自带的摄像头),输出画面显示到名为hand_det
的本地屏幕窗口中。
执行.\bin\main.bat camera
运行应用,将会自动弹出实时的手部检测画面:
bin/main.bat
脚本执行时,默认启动的是与工程同名的流程图,如果工程graph
目录下带有多个流程图,配置文件modelbox.conf
中的flow_path
参数指定了技能运行时的流程图路径:
flow_path = "${HILENS_APP_ROOT}/graph/hand_det${HILENS_MB_GRAPH_TYPE}.toml"
其中环境变量${HILENS_APP_ROOT}
在运行技能时将自动替换为当前工程的实际路径,而${HILENS_MB_GRAPH_TYPE}
可以在执行bin/main.bat
脚本时输入流程图后缀名作为参数进行配置,打开bin/main.bat
脚本看到相关参数内容为:
if "%1" == "default" (
set HILENS_MB_GRAPH_TYPE=
) else if "%1" == "" (
set HILENS_MB_GRAPH_TYPE=
) else (
set HILENS_MB_GRAPH_TYPE=_%1
)
即如果启动命令后跟上一个字符串xxx参数,则启动名为“工程名_xxx”的流程图。
如果PC带独立显卡,ModelBox
将使用独显进行模型推理加速,可以打开“任务管理器”-“性能”版块查看(笔者的笔记本自带MX150独显,即图中的GPU1):
通过本教程,我们可以看到,使用ModelBox
框架开发推理模块时,开发者不需要掌握推理库的使用方法,对于不同的推理引擎,ModelBox
定义了统一的推理功能单元结构,开发者只需要准备模型文件,并配置对应的toml文件,即可实现推理功能。
另外,我们也可以看到功能单元作为可插拔的组件,可以自由组合构成不同的流程图,开发好的功能单元,也可以非常方便的用到不同的应用中,有助于沉淀功能模块,提升开发效率。
如果自带独显的PC推理很慢,“任务管理器”中显示集成显卡占用很高,独显却占用为0或者很低,此时需要打开“NVIDIA控制面板”为ModelBox
应用开启独显加速,设置完成后重启应用,将会看到更流畅的效果。
如果想要关闭这个实时推理应用,关闭手部检测窗口是不行的,请点击右下角Terminal中的kill按钮结束此应用:
根据本次课程学习的内容,在自己的环境进行复现,并将复现的程序运行界面,连同网页右上方的华为云账号截图发至打卡贴上,详细打卡规则参见活动页面或参考2022ModelBox实战营活动打卡 (huaweicloud.com)