2020年如今国内的硝烟渐渐弥散,但人们仍然不能懈怠。口罩依然是能让你进入公共场所的“门票”。
佩戴口罩是阻断呼吸道病毒传播的方式之一,但难免会有些人不会自觉佩戴口罩。当人与人最舒适的距离是“你离我远点”的情况下,AI正以一种温暖的方式靠近人们,带来特殊的安全感。如今,在AI的加持下,口罩作为外出必备的保护伞的同时,也不再是“刷脸”通行的障碍物,能让你在公司、校园、小区等场景中畅通无阻……
前段时间在Paddle Lite的QQ群里面看到小伙伴们对在树莓派上部署实时的口罩识别很感兴趣,想着目前这个时期,能用低成本部署口罩识别系统对很多场景的帮助还是很高的。可以应用在学校、公司等等,节约了人力,也能督促人们出门一定要带好口罩。正好手里有个树莓派4B,可以搭建一个简易版本的口罩识别系统,下面将我的实验过程一步步与大家分享。
口罩模型介绍
PyramidBox-Lite是基于2018年百度发表于计算机视觉顶级会议ECCV 2018的论文PyramidBox而研发的轻量级模型,模型基于主干网络FaceBoxes,对于光照、口罩遮挡、表情变化、尺度变化等常见问题具有很强的鲁棒性。该PaddleHub Module是针对于移动端优化过的模型,适合部署于移动端或者边缘检测等算力受限的设备上,并基于WIDER FACE数据集和百度自采人脸数据集进行训练,支持预测。而针对此次疫情,百度宣布免费开源业内首个口罩人脸检测及分类模型。该模型可以有效检测在密集人流区域中携带和未携戴口罩的所有人脸,同时判断该人员是否佩戴口罩。目前已通过飞桨PaddleHub开源出来,广大开发者用几行代码即可快速上手,免费调用。
关于pyramidbox_lite_mobile_mask模型链接: https://www.paddlepaddle.org.cn/hubdetail?name=pyramidbox_lite_mobile_mask&en_category=ObjectDetection
通过python执行以下代码,即可通过PaddleHub获取并保存pyramidbox_lite_mobile_mask口罩识别模型。
import paddlehub as hub
pyramidbox_lite_mobile_mask = hub.Module(name="pyramidbox_lite_mobile_mask")
# 将模型保存在test_program文件夹之中
pyramidbox_lite_mobile_mask.processor.save_inference_model(dirname="test_program")
Paddle Lite预测库介绍
预测库编译
ArmLinux目前官网未提供编译好的预测库,所以需要自行编译。这里使用的预测库是Paddle-Lite最新版本v2.3.0:
wget https://github.com/PaddlePaddle/Paddle-Lite/archive/v2.3.0.zip
ArmLinux编译预测库有两种方式,交叉编译和本地编译(直接在树莓派上编译)
交叉编译速度更快,本地编译更加方便,两种方式任选其一即可。详细的编译方法可参考Lite的官方文档: https://paddle-lite.readthedocs.io/zh/latest/user_guides/source_compile.html
模型转换
使用opt工具将模型转为Paddle-Lite的模型
./opt
--model_file=./__model__
--param_file=./__params__
--optimize_out_type=naive_buffer
--optimize_out=model
Paddle Lite预测接口说明: C++代码调用Paddle-Lite执行预测库仅需以下五步:
(1)引用头文件和命名空间
#include "paddle_api.h"
using namespace paddle::lite_api;
(2)指定模型文件,创建Predictor
MobileConfig config;
config.set_model_from_file(model_file_path);
std::shared_ptr predictor =
CreatePaddlePredictor(config);
(3)设置模型输入 (下面以全一输入为例)
std::unique_ptr input_tensor(std::move(predictor->GetInput(0)));
input_tensor->Resize({1, 3, 224, 224});
auto* data = input_tensor->mutable_data();
for (int i = 0; i < ShapeProduction(input_tensor->shape()); ++i) {
data[i] = 1;
}
(4)执行预测
predictor->Run();
(5)获得预测结果
std::unique_ptr output_tensor(
std::move(predictor->GetOutput(0)));
auto output_data=output_tensor->data();
系统实现
1.硬件环境
首先是硬件准备,我们需要一块嵌入式开发板以及摄像头。这里我使用的是树莓派4B和500W像素的CSI摄像头。
图1 树莓派4B
图2 500W CSI摄像头
2.软件环境
以下已被删除,仅供老版本代码参考:
*操作系统我选用的是Debian-Pi-Aarch64,注意这是一个64位的系统(官方的Raspberry Pi系统是32位的)。
附上链接:https://gitee.com/openfans-community/Debian-Pi-Aarch64/*
进入系统后首先我们给摄像头enable一下,这个系统开启摄像头的方式与Raspberry Pi是不一样的。
*打开终端sudo gedit /boot/config.txt文件中将start_x=1的注释去掉后重启即可开启CSI摄像头。*
目前由于使用mjpg-streamer已经更换系统环境,当前使用的系统是ubuntu-18.04.4-server 此系统软件环境配置参考我的博客: https://blog.csdn.net/fuck_hang/article/details/105766070#comments_12091741
进入系统后首先我们给摄像头enable一下,这个系统开启摄像头的方式与Raspberry Pi是不一样的。
打开终端sudo gedit /boot/firmware/config.txt文件中将start_x=1的注释去掉后重启即可开启CSI摄像头。
接下来安装一下必要的软件:
sudo apt-get update
sudo apt-get install gcc g++ make wget unzip libopencv-dev pkg-config
wget https://www.cmake.org/files/v3.10/cmake-3.10.3.tar.gz && tar -zxvf cmake-3.10.3.tar.gz
cd cmake-3.10.3
./configure && make -j4 && sudo make install
操作方法
如图3所示,软件系统的整体流程是先获取视频流,然后转成图像,基于图像,做人脸检测,如果检测到人脸,再基于口罩识别模型判断人脸是否佩戴口罩;根据识别结果,画框并显示口罩是否佩戴。核心的思想是先进行人脸检测,再去分类是否佩戴口罩。
图3 软件流程图
0.分别指定人脸检测模型与口罩分类模型文件,设置工作线程,设置能耗模式,并分别创建Predictor
// Detection
MobileConfig config;
config.set_threads(CPU_THREAD_NUM);
config.set_power_mode(CPU_POWER_MODE);
config.set_model_dir(detect_model);
MobileConfig config2;
config2.set_threads(CPU_THREAD_NUM);
config2.set_power_mode(CPU_POWER_MODE);
config2.set_model_dir(classify_model);
// Create Predictor For Detction Model
std::shared_ptr predictor = CreatePaddlePredictor(config);
// Create Predictor2 For Classification Model
std::shared_ptr predictor2 = CreatePaddlePredictor(config2);
1.捕捉视频流 首先准备要传入的视频数据,这里我们需要用到OpenCV。 创建一个VideoCapture对象。
cv:: VideoCapture cap(-1);
设置一下分辨率,为了保证性能可以不要设置太高。
cap.set(CV_CAP_PROP_FRAME_WIDTH, 640);
cap.set(CV_CAP_PROP_FRAME_HEIGHT, 480);
将输入的视频流取帧
创建一个Mat对象,将cap进行视频读取和显示传入input_image
cv::Mat input_image;
cap >> input_image;
这里调用RunModel()函数,将input_image,人脸检测模型,口罩分类模型作为参数传入,然后将视频显示出来。
cv::Mat img = RunModel(input_image,predictor,predictor2);
cv::imshow("Mask Detection Demo", img);
3.RunModel()函数简述
缩放因子shrink, 检测阈值detect_threshold, 分类阈值classify_threshold可供自由配置:
缩放因子越大,模型运行速度越慢,检测准确率越高。
检测阈值越高,人脸筛选越严格,检测出的人脸框可能越少。
分类阈值越高,佩戴口罩分类越严格,检测出的wear mask可能越少。
float shrink = 0.2; //设置缩放因子,越大模型运行速度越慢,检测准确率越高。
float detect_threshold = 0.7; //设置检测人脸框的阈值
float classify_threshold = 0.73; //设置检测人脸框的阈值
4.人脸检测
对输入图片进行预处理,调用Lite API,人脸模型进行人脸检测,生成人脸检测框。
// Prepare
float shrink = 0.2; //设置缩放因子,越大模型运行速度越慢,检测准确率越高。
int width = img.cols;
int height = img.rows;
int s_width = static_cast(width * shrink);
int s_height = static_cast(height * shrink);
// Get Input Tensor
std::unique_ptr input_tensor0(std::move(predictor->GetInput(0)));
input_tensor0->Resize({1, 3, s_height, s_width});
auto* data = input_tensor0->mutable_data();
// Do PreProcess
std::vector detect_mean = {104.f, 117.f, 123.f};
std::vector detect_scale = {0.007843, 0.007843, 0.007843};
pre_process(img, s_width, s_height, detect_mean, detect_scale, data, false);
// Detection Model Run
predictor->Run();
// Get Output Tensor
std::unique_ptr output_tensor0(
std::move(predictor->GetOutput(0)));
auto* outptr = output_tensor0->data();
auto shape_out = output_tensor0->shape();
int64_t out_len = ShapeProduction(shape_out);
// Filter Out Detection Box
float detect_threshold = 0.7; //设置检测人脸框的阈值
std::vector detect_result;
for (int i = 0; i < out_len / 6; ++i) {
if (outptr[1] >= detect_threshold) {
Object obj;
int xmin = static_cast(width * outptr[2]);
int ymin = static_cast(height * outptr[3]);
int xmax = static_cast(width * outptr[4]);
int ymax = static_cast(height * outptr[5]);
int w = xmax - xmin;
int h = ymax - ymin;
cv::Rect rec_clip =
cv::Rect(xmin, ymin, w, h) & cv::Rect(0, 0, width, height);
obj.rec = rec_clip;
detect_result.push_back(obj);
}
outptr += 6;
}
5.口罩分类
对输入图片进行预处理,调用Lite API,对生成的检测框中的人脸是否佩戴口罩进行分类。
// Get Input Tensor
std::unique_ptr input_tensor1(std::move(predictor2->GetInput(0)));
int classify_w = 128;
int classify_h = 128;
input_tensor1->Resize({1, 3, classify_h, classify_w});
auto* input_data = input_tensor1->mutable_data();
int detect_num = detect_result.size();
std::vector classify_mean = {0.5f, 0.5f, 0.5f};
std::vector classify_scale = {1.f, 1.f, 1.f};
float classify_threshold = 0.73; //设置检测人脸框的阈值
for (int i = 0; i < detect_num; ++i) {
cv::Rect rec_clip = detect_result[i].rec;
cv::Mat roi = img(rec_clip);
// Do PreProcess
pre_process(roi,
classify_w,
classify_h,
classify_mean,
classify_scale,
input_data,
true);
// Classification Model Run
predictor2->Run();
// Get Output Tensor
std::unique_ptr output_tensor1(
std::move(predictor2->GetOutput(1)));
auto* outptr = output_tensor1->data();
结果展示
使用cv::Scala框选结果,绿色的框选中人脸表示佩戴口罩,红色的框选中人脸表示未佩戴口罩
// Draw Detection and Classification Results
bool flag_mask = outptr[1] > classify_threshold;
cv::Scalar roi_color;
if (flag_mask) {
roi_color = cv::Scalar(0, 255, 0);
} else {
roi_color = cv::Scalar(0, 0, 255);
}
cv::rectangle(img, rec_clip, roi_color, 2, cv::LINE_AA);
std::string text = outptr[1] > classify_threshold ? "wear mask" : "no mask";
7.效果演示
图4 未佩戴口罩
图5 佩戴口罩(图未更新)
(gif无法上传,可移步AiStudio项目中查看效果)
图6 手遮挡口罩测试(图未更新)
最后,代码仅做部分演示,完整项目公开在AI Studio上,欢迎Fork ~ https://aistudio.baidu.com/aistudio/projectdetail/315730
目前AiStudio代码是V1版本代码,优化后的V2代码已经上传github,处子之作,跪求一star~ https://github.com/hang245141253/raspi4B_mask_detection_runtime
更新日志:
1.优化了代码,fps提升了4-7倍!流畅度极大提升。优化前fps3帧左右,优化后能达12-27帧。 高帧率条件是树苺派不能开启桌面环境,切需要mjpg-streamer利用电脑浏览器访问树苺派。 识别到的人数越多帧率越低,无识别目标时帧率能达27帧,如果在树苺派上直接运行帧率大概在17帧左右浮动。
2.关于将paddle-lite嵌入mjpg-streamer实现网络监控功能。此部分代码由于参加比赛,暂不进行公开。
如果您加入官方QQ群,您将遇上大批志同道合的深度学习同学。官方QQ群:703252161。
如果您想详细了解更多飞桨的相关内容,请参阅以下文档。
官网地址:https://www.paddlepaddle.org.cn
飞桨开源框架项目地址:
GitHub: https://github.com/PaddlePaddle/Paddle
Gitee: https://gitee.com/paddlepaddle/Paddle
赞赞赞!!! 侧脸都那么帅!(^_−)☆
厉害了,点赞收藏了
厉害厉害
怎么进行模型压缩?