本文分享自华为云社区《ModelBox 开发案例 - 疲劳驾驶检测》,作者: 俯卧撑手抓饼 。
本案例将使用开源项目中的 Face Detect 与 Face Mesh 两个模型,实现一个简单的疲劳驾驶检测应用并提醒,最终效果如下所示:
案例所需资源(代码、模型、测试数据等) 均可从 obs 桶下载。
开发背景
"道路千万条,安全第一条",交通安全是我们出行首要关注的问题,而疲劳驾驶一直是引发交通事故的一个主要原因。
在 AI 算法惠及生活的现在,我们想利用 ModelBox 开发板快速实现、便利调试开发的特点,实现一个疲劳驾驶提醒应用案例 demo,实现基本的疲劳驾驶检测与辅助提醒效果。希望大家能从此案例得到启发与参考,开发出更多丰富、有趣的应用。
算法原理
该项目主要原理为:通过面部检测模块定位出面部区域,将其输入到面部关键点模块检测出面部关键点,并对其中的眼睛、嘴巴等关键点计算其 EAR 值与时长判断其是否处于疲劳状态并进行提醒。
从摄像头检测出面部区域
从面部检测出关键点
通过公式计算出 EAR 值,并根据实时滑动窗口计算占比,超过阈值则判断为疲劳应该对其进行提醒
ModelBox 介绍
ModelBox 是一个适用于端边云场景的 AI 推理应用开发框架,提供了基于 Pipeline 的并行执行流程,能帮助 AI 应用开发者较快的开发出高效,高性能,以及支持软硬协同优化的 AI 应用。
易于开发
AI推理业务可视化编排开发,功能模块化,丰富组件库;c++,python多语言支持。
易于集成
集成云上对接的组件,云上对接更容易。
高性能,高可靠
pipeline并发运行,数据计算智能调度,资源管理调度精细化,业务运行更高效。
软硬件异构
CPU,GPU,NPU多异构硬件支持,资源利用更便捷高效。
全场景
视频,语音,文本,NLP全场景,专为服务化定制,云上集成更容易,端边云数据无缝交换。
易于维护
服务运行状态可视化,应用,组件性能实时监控,优化更容易。
复制代码
模型训练
我们可以使用面向开发者的一站式 AI 开发平台 ModelArts 进行模型的训练:
ModelArts 提供了包括数据标注,训练环境,预置算法在内的丰富的功能,甚至可以通过订阅预置算法实现零代码的模型训练工作。当然你也可以在本地训练自己的模型。我们假设你现在已经拥有了训练好的模型,接下来我们需要将训练好的模型转换成为可以在开发板上运行的模型。本案例可以直接用预训练好的模型进行初期应用的搭建即可。
模型转换
我们发布了开发板模型转换案例,参见 RK3568 模型转换验证案例 :
在这个案例中我们演示了从环境适配到模型的转换验证的全流程样例代码,开发者可以通过 “Run in ModelArts” 一键将 Notebook 案例在 ModelArts 控制台快速打开、运行以及进行二次开发等操作。
开发环境部署
使用开发板进行 ModelBox AI 应用开发有两种方式,一是开发板连接显示器和键盘鼠标,安装 Ubuntu 桌面,直接在开发板上进行开发;二是使用远程连接工具(如 VS Code 中的 Remote-SSH)从 PC 端登录开发板进行开发。这里我们推荐第二种方式,因为 PC 端可以使用功能更丰富、界面更友好的 IDE。请参考此案例的开发环境部署先进行环境搭建:
应用开发
接下来我们会以疲劳驾驶检测 demo 为例,介绍如何使用 ModelBox 开发 AI 应用。
创建工程
SDK 提供了工程脚本 create.py,可以使用./create.py -h 查看脚本帮助:
rock@rock-3a:~/yourpath/v1.0.8.21$ sudo ./create.py -h
Usage: Create ModelBox project and flowunit
NOTE : you must firstly use bellow cmd to create a project in workspace
create.py -t server -n your_proj_name {option: -s name, create this project from a solution}, support hilens deployment
or create.py -t project -n your_proj_name {option: -s name, create this project from a solution}, generally not use
AND : use bellow cmd to create [c++|python|infer] flowunit in this project
create.py -t c++ -n your_flowunit_name -p your_proj_name
AND : call workspace/your_proj_name/build_project.sh to build your project, call bin/main.sh[bat] to run
FINAL: create.py -t rpm -n your_proj_name to package your project (the same folder with create.py) if upload to hilens
NOTE: create.py -t editor {option: -i ip or ip:port to start editor server in your config ip:port}
NOTE: create.py -t demo to create solutions to runnable demo
for ex: create.py -t server -n my_det -s car_det
-h or --help:show help
-t or --template [c++|python|infer|yolo|project|server|rpm|editor|demo] create a template or package to rpm ...
-n or --name [your template name]
-p or --project [your project name when create c++|python|infer|yolo]
-s or --solution [the solution name when create project] create a project from solution
-c or --cfg [flowunit configure json, it's used by UI, you can use it too, but too complicated]
-v or --version:show sdk version
复制代码
ModelBox 提供了可视化图编排工具:Editor,可以使用./create.py -t editor 开启图编排服务:服务默认 ip 即为 192.168.2.111,如需配置其他 ip 或端口,可以通过 - i ip:port 参数进行配置。
在浏览器输入 ip:port/editor 即可进入可视化编辑界面,我们点击编排进入工程开发界面,如果进一步了解 ModelBox 相关内容,可以点击右上角帮助:
进入编排界面,点击右上角新建项目:
我们在 workspace 的项目路径下,建立项目名称 fatigue_detect,点击确认:
从默认的模板中可以看到,我们初始具备了一个 http 收发单元的默认图:
其中,区域 1 为 SDK 预置的高性能通用流单元,区域 2 为可视化编排界面,区域 3 为对应的图配置文件内容。同时,VS Code 对应目录下也出现了 fatigue_detect 项目:
创建推理功能流单元
接下来,我们通过 UI 界面创建推理流单元:
点击右上角功能单元,创建单元
对于面部检测模型,我们将流单元命名为 face_detection,模型文件名即为转换好的检测模型名:face_detect.rknn,此模型输入为图像,输出为特征向量,所以我们添加 int 类型的输入端口与 float 类型的输出端口。关于开发板的推理流单元创建,在处理类型时我们选择 cuda,即为 npu 推理,推理引擎可选任意一款,目前开发板 SDK 可以自动进行识别转换。最后将功能单元分组修改为 inference,点击确认,即可看到,在左侧 inference 页签下出现了新添加的推理流单元
同时,在 VS Code 工程 model 目录下可以看到创建好的推理流单元:
类似的,我们创建 face_landmark_detection 推理流单元:
创建后处理功能单元
除了推理流单元外,疲劳驾驶检测 demo 还需要一些通用功能单元:面部检测的后处理单元、感兴趣区域(面部检测区域)提取单元、疲劳逻辑判断单元,我们新建三个 python 功能单元来满足上述需求。
对于面部检测的后处理单元,我们希望通过原图和 face_detection 的两个输出向量解码出面部检测框,所以该单元应该有三个输入。此外,对于画幅中有无检测到面部两种状态,我们希望该功能单元分情况处理,检测到面部时,将面部检测结果送入感兴趣区域提取单元,没有检测到面部时,直接返回,因此功能单元类型选择:IF_ELSE。新建单元如下:
同样的,根据输入输出与功能单元状态,我们创建 extract_roi 和 fatigue_detect 两个功能单元:
流程图编排
拖拽需要的功能单元全部创建好后,我们可以着手编排流程图,我们编排一个视频处理的图,可以删除不需要的 http 收发单元:
在 Generic 列表下将虚拟输入单元 input 和我们刚刚创建的三个功能单元拖入画布:
在 Image 列表下将模型推理需要用到的预处理单元 resize 拖入画布,因为面部检测模型与面部关键点模型都 resize 处理,把两个 resize 单元拖入:
值得注意的是,resize 单元需要适配模型参数,需要点击该单元进行配置:
在 Input 列表下拖入输入解析单元 data_source_parser 作为视频流的输入源
在 Video 列表下拖入视频处理需要的单元 video_demuxer、video_decoder、video_out:
最后,在 Inference 列表下拖入我们创建的两个推理单元:
编排
将功能单元按照处理逻辑进行连接:
点击上个流单元并拖动箭头可以连接下一个流单元
虚拟输入 input 连接输入解析 data_source_parser,解析后送入视频解包 video_demuxer 与解码单元 video_decoder:
解码输出送入预处理后可直接进行推理,面部检测输出连接后处理模块,用于提取坐标输入到感兴趣区域,若没检测到面部则跳过面部关键点检测与疲劳驾驶判断:
最后,把 ROI 区域图 resize 后输入到面部关键点检测,最后把面部关键点输入到疲劳驾驶判断模块给出结果:
这样,我们就完成了流程图的编排,可以看到在 GraphViz 区域也出现了完整的图表述:
保存项目,我们转到 VS Code 每个单元的代码实现进行调整与补充:
代码补全
可视化编排中,创建的推理单元位于项目的 model 目录下,通用单元位于 etc/flowunit 目录下,流程图位于 graph 目录下,可以看到创建的单元与图都已同步过来:
其中,我们需要在主流图代码里修改 video_decoder 需要指定类型:
`video_decoder [ type=flowunit flowunit=video_decoder device=rknpu deviceid="0" pix_fmt=bgr label="{{<in_video_packet> in_video_packet}|video_decoder|{<out_video_frame> out_video_frame}}" ]`
**推理单元**
复制代码
首先完善推理单元,对于推理功能单元,只需要提供独立的 toml 配置文件,指定推理功能单元的基本属性即可,目录结构为:
[flowunit-name]
|---[flowunit-name].toml #推理功能单元配置
|---[model].rknn #模型文件
|---[infer-plugin].so #推理自定义插件
复制代码
ModelBox 框架在初始化时,会扫描目录中的 toml 后缀的文件,并读取相关的推理功能单元信息。[infer-plugin].so 是推理所需插件,推理功能单元支持加载自定义插件,开发者可以实现自定义算子。
将模型拷入对应文件夹,以 face_detection 为例我们看一下推理功能单元配置文件:
因为推理单元输入为图像,注意修改其输入类型为 uint8,配置文件中有一些单元类型、模型名称、输入输出的基本配置,可以酌情修改。
通用单元
Python 通用单元需要提供独立的 toml 配置文件,指定 python 功能单元的基本属性。一般情况,目录结构为:
[FlowUnitName]
|---[FlowUnitName].toml
|---[FlowUnitName].py
|---xxx.py
复制代码
相较于推理单元而言,通用单元不但有配置文件,还需要完善具体的功能代码,以 face_detect_post 为例,首先是功能单元配置文件:
Basic config 是一些单元名等基本配置,Flowunit Type 是功能单元类型,face_detect_post 是一个条件单元,所以可以看到 condition 为 true,此外还有一些展开、归拢等性质,可以在 AI Gallery ModelBox) 板块下看到更多案例。
此外,输入输出 type 根据实际逻辑可能进行一些修改。
接下来,我们查看 face_detect_post.py,可以看到创建单元时已经生成了基本接口:
# Copyright (c) Huawei Technologies Co., Ltd. 2022. All rights reserved.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import _flowunit as modelbox
class face_detect_postFlowUnit(modelbox.FlowUnit):
# Derived from modelbox.FlowUnit
def __init__(self):
super().__init__()
def open(self, config):
# Open the flowunit to obtain configuration information
return modelbox.Status.StatusCode.STATUS_SUCCESS
def process(self, data_context):
# Process the data
in_data = data_context.input("in_1")
out_data = data_context.output("out_1")
# yolox_post process code.
# Remove the following code and add your own code here.
for buffer in in_data:
response = "Hello World " + buffer.as_object()
result = response.encode('utf-8').strip()
add_buffer = modelbox.Buffer(self.get_bind_device(), result)
out_data.push_back(add_buffer)
return modelbox.Status.StatusCode.STATUS_SUCCESS
def close(self):
# Close the flowunit
return modelbox.Status()
def data_pre(self, data_context):
# Before streaming data starts
return modelbox.Status()
def data_post(self, data_context):
# After streaming data ends
return modelbox.Status()
def data_group_pre(self, data_context):
# Before all streaming data starts
return modelbox.Status()
def data_group_post(self, data_context):
# After all streaming data ends
return modelbox.Status()
复制代码
如果功能单元的工作模式是 stream = false 时,功能单元会调用 open、process、close 接口;如果功能单元的工作模式是 stream = true 时,功能单元会调用 open、data_group_pre、data_pre、process、data_post、data_group_post、close 接口;用户可根据实际需求实现对应接口。
根据单元性质,我们主要需要完善 open、process 接口:
def process(self, data_context):
# Process the data
in_image = data_context.input("image")
in_feat = data_context.input("in_feat")
in_score = data_context.input("in_score")
has_face = data_context.output("has_face")
no_face = data_context.output("no_face")
# face_detect_post process code.
# Remove the following code and add your own code here.
for buffer_img, buffer_feat, buffer_score in zip(in_image, in_feat, in_score):
width = buffer_img.get('width')
height = buffer_img.get('height')
channel = buffer_img.get('channel')
img_data = np.array(buffer_img.as_object(), copy=False)
img_data = img_data.reshape((height, width, channel))
feat_data = np.array(buffer_feat.as_object(), copy=False)
feat_data = feat_data.reshape(896,16)
feat_score = np.array(buffer_score.as_object(), copy=False)
feat_score = feat_score.reshape(896,1)
dets = process_cpu(feat_data, feat_score, self.anchors)
if dets.shape[0] > 0:
box = [int(dets[0]*width), int(dets[1]*height),int(dets[2]*width),int(dets[3]*height)]
box = self.expend(box, height, width)
buffer_img.set("bboxes", box)
has_face.push_back(buffer_img)
else:
img_buffer = modelbox.Buffer(self.get_bind_device(), img_data)
img_buffer.copy_meta(buffer_img)
no_face.push_back(img_buffer)
return modelbox.Status.StatusCode.STATUS_SUCCESS
复制代码
可以看到,在 open 中我们进行了一些参数获取,process 进行逻辑处理,输入输出可以通过 data_context 来获取,值得注意的是输出时我们返回的是图,在检测到手时为图附加了检测框信息,该信息可以被下一单元获取。
同样的,完善其余通用功能单元,具体可以参考我们提供的代码。
应用运行
我们需要准备一个 mp4 文件拷贝到 data 文件夹下,我们提供了测试视频 fatigue.mp4,然后打开工程目录下 bin/mock_task.toml 文件,修改其中的任务输入和任务输出配置为如下内容:
# 用于本地mock文件读取任务,脚本中已经配置了IVA_SVC_CONFIG环境变量, 添加了此文件路径
########### 请确定使用linux的路径类型,比如在windows上要用 D:/xxx/xxx 不能用D:\xxx\xxx ###########
# 任务的参数为一个压缩并转义后的json字符串
# 直接写需要转义双引号, 也可以用 content_file 添加一个json文件,如果content和content_file都存在content会被覆盖
# content_file支持绝对路径或者相对路径,不支持解析环境变量(包括${HILENS_APP_ROOT}、${HILENS_DATA_DIR}等)
[common]
content = "{\"param_str\":\"string param\",\"param_int\":10,\"param_float\":10.5}"
# 任务输入,mock模拟目前仅支持一路rtsp或者本地url
# rtsp摄像头,type = "rtsp", url里面写入rtsp地址
# 其它用"url",比如可以是本地文件地址, 或者httpserver的地址,(摄像头 url = "0")
[input]
type = "url"
url = "../data/fatigue.mp4"
# 任务输出,目前仅支持"webhook", 和本地输出"local"(输出到屏幕,url="0", 输出到rtsp,填写rtsp地址)
# (local 还可以输出到本地文件,这个时候注意,文件可以是相对路径,是相对这个mock_task.toml文件本身)
[output]
type = "local"
url = "../hilens_data_dir/fatigue.mp4"
复制代码
配置好后在工程路径下执行 build_project.sh 进行工程构建:
rock@rock-3a:~/yourpath/v1.0.8.21/workspace/fatigue_detect$ sudo ./build_project.sh
./build_project.sh: line 26: dos2unix: command not found
./build_project.sh: line 26: dos2unix: command not found
./build_project.sh: line 26: dos2unix: command not found
./build_project.sh: line 26: dos2unix: command not found
./build_project.sh: line 26: dos2unix: command not found
./build_project.sh: line 26: dos2unix: command not found
./build_project.sh: line 26: dos2unix: command not found
./build_project.sh: line 26: dos2unix: command not found
build success: you can run main.sh in ./bin folder复制
复制代码
构建完成后运行项目:
rock@rock-3a:~/yourpath/v1.0.8.21/workspace/fatigue_detect$ sudo ./bin/main.sh复制
复制代码
等待稍许即可以在 hilens_data_dir 文件夹下看到运行结果:
打开 mp4 文件也可以看到叠加了提醒效果的视频
打包部署
调试完成后,create.py 脚本将应用打包发布,详情请参考打包部署章节
点击关注,第一时间了解华为云新鲜技术~
评论