使用手机终端即可实现的人员反光衣穿戴检测报警系统

本文正在参加『In AidLux,To AIoT』AI应用案例征集活动

基于AidLux平台和七牛云、喵提醒工具实现施工人员反光衣穿戴检测报警系统

0. 前言

在施工场景中,反光衣的穿戴可以有效提升施工人员的作业安全,减少相应的安全事故。然而由于施工场地较大、人数较多,监管人员无法对工地现场中所有施工人员的反光衣穿戴情况进行监控。

本项目基于AidLux平台和七牛云、喵提醒工具实现了人员反光衣穿戴检测报警系统,其中算法部分主要使用了YOLOv5进行人员反光衣的穿戴检测。该系统可以较容易地部署在手机等终端上,并通过七牛云、喵提醒工具实现监控中台的反光衣未穿戴报警功能。具体实现方案参考了江大白老师的教程万字长文手把手教你Yolov5制作家庭安防告警系统

1.AidLux平台简介

使用Aidlux,可以将PC端编写的Python代码,直接迁移到Aidlux平台,测试发布。

Aidlux软件使用方便,可以安装在手机、PAD、ARM开发板等边缘端设备上。

Aidlux开发过程中,既支持在边缘设备的本机开发,也支持通过Web浏览器访问边缘端桌面进行开发。

2.项目方案介绍

2.1系统功能介绍

本项目主要用到目标检测算法采用yolov5,对区域人员进行反光衣检测,可以用于对未穿戴反光衣人员的违规监管。整套代码可以部署在手机等终端上,通过七牛云、喵提醒工具展示远程报警功能,其系统流程框图如下所示。

2.2 关键技术介绍

本项目主要应用了目标检测技术,对图片和视频中的反光衣进行了检测。反光衣的类别主要为黄绿色反光衣、红橙色反光衣等,如下图所示。

3.具体实现

3.1 反光衣目标检测:Yolov5模型训练+验证+推理

Yolov5的官方链接为:https://github.com/ultralytics/yolov5

准备好反光衣数据集,新建文件RV220830.yaml,并对其内容做如下添加:

path: /path/to/RV220830
train: train.txt
val: val.txt
nc: 2  # number of classes
names: ['NRV', 'RV'] 

训练、验证、推理命令和结果如下:

python train.py --data RV220830.yaml --weights yolov5s.pt --epochs 100 --batch-size 128 --name RV220830_wyolov5s_e100_tra
python val.py --weights runs/train/RV220830_wyolov5s_e100_tra/weights/best.pt --data RV220830.yaml
python detect.py --weights runs/train/RV220830_wyolov5s_e100_tra/weights/best.pt

3.2 图片云服务:七牛云图片上传下发

官网:https://www.qiniu.com/

a、注册并创建空间。注意:需要“手机注册-邮箱认证-真人认证”。真人认证时,最好在白天光线好的环境下,背景为白墙。

b、图片上传及外链获取代码测试。注意:需要修改access_keysecret_keybucket_name(空间名称)和 bucket_url(外链域名)。

access_key = "Kc97irLY32yRwKBBZw7XqXzBiFDoXdWdKkSVzQq5"
secret_key = "bDcC1_ROlDssVERGucBfAnaFBUw2wtqx7fcf9RIt"
bucket_name = "aidlux2022ltl"
bucket_url = "rhb1xxcw3.hb-bkt.clouddn.com"

c、安装七牛云依赖代码库。pip install qiniu

3.3 微信提醒:喵提醒服务开发

远程提醒工具可使用‘喵提醒’公众号实现。先将告警图片先推送到七牛云服务器上,再将图片的外网地址通过‘喵提醒’发送到手机上。

a、注册并创建提醒。起名为“安防告警系统-反光衣”,获得个人喵码。

b、激活48小时。在公众号回复任意内容激活。

c、喵提醒代码测试。填写个人喵码。

3.4 PC代码整合:人体检测+七牛云+喵提醒

图片检测告警操作。

代码如下:

# YOLOv5 ? by Ultralytics, GPL-3.0 license

import argparse
import os
import platform
import sys
from pathlib import Path

import torch
import torch.backends.cudnn as cudnn

FILE = Path(file).resolve()
ROOT = FILE.parents[0] # YOLOv5 root directory
if str(ROOT) not in sys.path:
sys.path.append(str(ROOT)) # add ROOT to PATH
ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative

from models.common import DetectMultiBackend
from utils.augmentations import letterbox
from utils.general import (LOGGER, check_file, check_img_size, check_imshow, check_requirements, colorstr, cv2,
increment_path, non_max_suppression, print_args, scale_coords, strip_optimizer, xyxy2xywh)
from utils.torch_utils import select_device, time_sync
import numpy as np

from qiniu import Auth, put_file
from qiniu import BucketManager
import time
import requests

配置七牛云信息

access_key = "Kc97irLY32yRwKBBZw7XqXzBiFDoXdWdKkSVzQq5"
secret_key = "bDcC1_ROlDssVERGucBfAnaFBUw2wtqx7fcf9RIt"
bucket_name = "aidlux2022ltl"
bucket_url = "rhb1xxcw3.hb-bkt.clouddn.com"
q = Auth(access_key, secret_key)
bucket = BucketManager(q)

将本地图片上传到七牛云中

def upload_img(bucket_name, file_name, file_path):
# generate token
token = q.upload_token(bucket_name, file_name, 3600)
put_file(token, file_name, file_path)

获得七牛云服务器上file_name的图片外链

def get_img_url(bucket_url, file_name):
img_url = ‘http://%s/%s’ % (bucket_url, file_name)
return img_url

@torch.no_grad()
def run(
weights=ROOT / ‘yolov5s.pt’, # model.pt path(s)
source=ROOT / ‘data/images’, # file/dir/URL/glob, 0 for webcam
data=ROOT / ‘data/coco128.yaml’, # dataset.yaml path
imgsz=(640, 640), # inference size (height, width)
conf_thres=0.25, # confidence threshold
iou_thres=0.45, # NMS IOU threshold
max_det=1000, # maximum detections per image
device=‘’, # cuda device, i.e. 0 or 0,1,2,3 or cpu
view_img=False, # show results
save_txt=False, # save results to *.txt
save_conf=False, # save confidences in --save-txt labels
save_crop=False, # save cropped prediction boxes
nosave=False, # do not save images/videos
classes=None, # filter by class: --class 0, or --class 0 2 3
agnostic_nms=False, # class-agnostic NMS
augment=False, # augmented inference
visualize=False, # visualize features
update=False, # update all models
project=ROOT / ‘runs/detect’, # save results to project/name
name=‘exp’, # save results to project/name
exist_ok=False, # existing project/name ok, do not increment
line_thickness=3, # bounding box thickness (pixels)
hide_labels=False, # hide labels
hide_conf=False, # hide confidences
half=False, # use FP16 half-precision inference
dnn=False, # use OpenCV DNN for ONNX inference
):

# Load model
device = select_device(device)
model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)
stride, names, pt = model.stride, model.names, model.pt
names = ['N', 'Y']
imgsz = check_img_size(imgsz, s=stride)  # check image size

# load image and inference
images_list = os.listdir(source)
for image_name in images_list:
    person = 0
    image_path = os.path.join(source, image_name)
    im = cv2.imread(image_path)  # BGR
    im0 = im.copy()
    img = letterbox(im, imgsz, stride=32, auto=True)[0]

    # Convert
    img = img.transpose((2, 0, 1))[::-1]  # HWC to CHW, BGR to RGB
    im = np.ascontiguousarray(img)
    im = torch.from_numpy(im).to(device)
    im = im.half() if half else im.float()  # uint8 to fp16/32
    im /= 255  # 0 - 255 to 0.0 - 1.0
    if len(im.shape) == 3:
        im = im[None]  # expand for batch dim

    # Inference
    pred = model(im, augment=augment, visualize=visualize)

    # NMS
    pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)
    for i, det in enumerate(pred):  # per image
        if len(det):
            # Rescale boxes from img_size to im0 size
            det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round()
            for *xyxy, conf, cls in reversed(det):
                cls = int(cls)  # integer class
                if cls != 0 and cls != 1:
                    continue
                if cls == 0:
                    person = 1
                label = None if hide_labels else (names[cls] if hide_conf else f'{names[cls]} {conf:.2f}')
                p1, p2 = (int(xyxy[0]), int(xyxy[1])), (int(xyxy[2]), int(xyxy[3]))
                cv2.rectangle(im0, p1, p2, (0,255,0), 2)
                cv2.putText(im0, label, (p1[0], p1[1] - 2), cv2.FONT_HERSHEY_PLAIN,2, (0, 255, 255), 2)

    if person == 1:
        cv2.imwrite("detect_image.jpg", im0)

        # 需要上传到七牛云上面的图片的路径
        image_up_name = "detect_image.jpg"
        # 上传到七牛云后,保存成的图片名称
        image_qiniu_name = "detect_image_2022.jpg"
        # 将图片上传到七牛云,并保存成image_qiniu_name的名称
        upload_img(bucket_name, image_qiniu_name, image_up_name)
        # 取出和image_qiniu_name一样名称图片的url
        url_receive = get_img_url(bucket_url, image_qiniu_name)
        print(url_receive)

        # 填写对应的喵码
        id = 'tfHKWHC'
        # 填写喵提醒中,发送的消息,这里放上前面提到的图片外链
        text = "告警图片:" + url_receive
        ts = str(time.time())  # 时间戳
        type = 'json'  # 返回内容格式
        request_url = "http://miaotixing.com/trigger?"

        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36 Edg/87.0.664.47'}

        result = requests.post(request_url + "id=" + id + "&text=" + text + "&ts=" + ts + "&type=" + type,
                               headers=headers)

    cv2.imshow("image",im0)
    cv2.waitKey(0)

def parse_opt():
parser = argparse.ArgumentParser()
# parser.add_argument(‘–weights’, nargs=‘+’, type=str, default=ROOT / ‘models/yolov5s.pt’, help=‘model path(s)’)
parser.add_argument(‘–weights’, nargs=‘+’, type=str, default=ROOT / ‘models\RV220830_wyolov5s_e100_tra.pt’, help=‘model path(s)’)
parser.add_argument(‘–source’, type=str, default=ROOT / ‘data/images2’, help=‘file/dir/URL/glob, 0 for webcam’)
parser.add_argument(‘–data’, type=str, default=ROOT / ‘data/coco128.yaml’, help=‘(optional) dataset.yaml path’)
parser.add_argument(‘–imgsz’, ‘–img’, ‘–img-size’, nargs=‘+’, type=int, default=[640], help=‘inference size h,w’)
parser.add_argument(‘–conf-thres’, type=float, default=0.25, help=‘confidence threshold’)
parser.add_argument(‘–iou-thres’, type=float, default=0.45, help=‘NMS IoU threshold’)
parser.add_argument(‘–max-det’, type=int, default=1000, help=‘maximum detections per image’)
parser.add_argument(‘–device’, default=‘’, help=‘cuda device, i.e. 0 or 0,1,2,3 or cpu’)
parser.add_argument(‘–view-img’, action=‘store_true’, help=‘show results’)
parser.add_argument(‘–save-txt’, action=‘store_true’, help=‘save results to *.txt’)
parser.add_argument(‘–save-conf’, action=‘store_true’, help=‘save confidences in --save-txt labels’)
parser.add_argument(‘–save-crop’, action=‘store_true’, help=‘save cropped prediction boxes’)
parser.add_argument(‘–nosave’, action=‘store_true’, help=‘do not save images/videos’)
parser.add_argument(‘–classes’, nargs=‘+’, type=int, help=‘filter by class: --classes 0, or --classes 0 2 3’)
parser.add_argument(‘–agnostic-nms’, action=‘store_true’, help=‘class-agnostic NMS’)
parser.add_argument(‘–augment’, action=‘store_true’, help=‘augmented inference’)
parser.add_argument(‘–visualize’, action=‘store_true’, help=‘visualize features’)
parser.add_argument(‘–update’, action=‘store_true’, help=‘update all models’)
parser.add_argument(‘–project’, default=ROOT / ‘runs/detect’, help=‘save results to project/name’)
parser.add_argument(‘–name’, default=‘exp’, help=‘save results to project/name’)
parser.add_argument(‘–exist-ok’, action=‘store_true’, help=‘existing project/name ok, do not increment’)
parser.add_argument(‘–line-thickness’, default=3, type=int, help=‘bounding box thickness (pixels)’)
parser.add_argument(‘–hide-labels’, default=False, action=‘store_true’, help=‘hide labels’)
parser.add_argument(‘–hide-conf’, default=False, action=‘store_true’, help=‘hide confidences’)
parser.add_argument(‘–half’, action=‘store_true’, help=‘use FP16 half-precision inference’)
parser.add_argument(‘–dnn’, action=‘store_true’, help=‘use OpenCV DNN for ONNX inference’)
opt = parser.parse_args()
opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1 # expand
print_args(vars(opt))
return opt

def main(opt):
check_requirements(exclude=(‘tensorboard’, ‘thop’))
run(**vars(opt))

if name == "main":
opt = parse_opt()
main(opt)

注意修改内容:access_key、secret_key、bucket_name、bucket_url;个人喵码;--weight、--source。

3.5 Android部署:Aidlux平台软件部署

a、下载安装Aidlux软件。首先在安卓手机上下载一个Aidlux软件,在华为手机的应用商城中搜索“aidlux”,即可安装下载,大小约2.1个G,首次进入后会自动下载一些相关依赖,此时不要中断。在本文中,使用的测试手机为华为荣耀30s,运行内存6GB,处理器kirin820。

b、将手机的wifi网络和电脑的网络连接到一起,打开安装好的手机上的Aidlux软件,点击第一排第二个Cloud_ip。手机界面上会跳出可以在电脑上登录的IP网址,在电脑上登录,如我的地址为http://192.168.2.167:8000/。默认登陆用户名root,默认登录密码是“aidlux”。

3.7 Android部署:Aidlux中Yolov5优化加速

a、上传代码到Aidlux。

安装相应的依赖库。使用pip安裝軟需要的Python包,方法和PC端一样。

pip3 install qiniu  -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

b、自有模型移植部署。

pt模型转换,将训练好的pt模型转换成tflite模型。AidLux使用tflite格式进行加速推理,常用的Pytorch训练的模型,使用tflite进行部署,需要完成pytorch转为tflite。可使用yolov5工程代码中自带的export.py脚本进行转换。

conda install tensorflow
python export.py --weights runs/train/RV220830_wyolov5s_e100_tra/weights/best.pt
python export.py --weights models/RV220830_wyolov5s_e100_tra.pt

修改推理代码中的信息,主要修改模型的路径model_path,和输入输出信息。

yolov5文件中的pt模型是CoCo数据集训练出的模型,主要有80个类别,因此out_shape中有一个85,即1(前景背景概率)+4(检测框坐标信息)+80(类别信息)。由于我们的类别为2,故将out_shape修改为7。相关修改内容如下。

model_path = 'RV220830_wyolov5s_e100_tra-fp16.tflite'
out_shape = [1 * 25200 * 7 * 4, 1 * 3 * 80 * 80 * 7 * 4, 1 * 3 * 40 * 40 * 7 * 4, 1 * 3 * 20 * 20 * 7 * 4]
    pred = pred.reshape(1, 25200, 7)[0]
    if person == 1 or person == 2:

上传代码和模型到Aidlux。

c、运行代码查看效果。

直接采用手机端的aidlux来进行启动。

报警图中的方框定位了人员上衣所在部分,颜色和字母代表了是否穿戴反光衣的情况。其中,红色框表示该人员“未穿反光衣”,同时方框上方用“NRV”标注;黄色框表示该人员“穿反光衣”,同时方框上方用“RV”标注。

实现该系统的部署后,在手机终端上打开该应用,当手机摄像头视野内出现行人时,屏幕会显示其中的行人是否穿反光衣,该项目效果如下图。

同时,当出现“未穿反光衣”的违规人员时,手机会自动收到一条提醒,并能查看监控场景中人员反光衣的穿戴情况,如下图所示。

5. 总结和改进

5.1 总结

本项目主要基于AidLux平台和七牛云、喵提醒工具实现施工人员反光衣穿戴检测报警系统。应用目标检测经典算法YOLOv5,将反光衣检测的功能在手机终端上进行了完整实现。除了反光衣检测,目标检测算法也可应用在其它各种场景中,满足各种定制化项目的需求。而基于AidLux的优势,相关算法可以在手机终端上完成便捷开发和部署实现。

5.2 改进

1、手机算力有限。实际测试中,会产生发热现象以及卡顿现象。如果使用盒子来测试,应该效果会更好。

2、喵提醒连续发送提醒时容易漏发送。在实际测试中,对视频进行测试可能会产生较为频繁的报警提醒,若

相邻时间间隔较短,会遗漏部分提醒。

3、七牛云或者微信有缓存,导致接收到的图片不对。实际测试中也发现无论发送多少条提醒,打开的url链接一直是同一张图片,因为缓存原因,需要复制到浏览器再进行打开。

5.3 展望

期待伙伴们可以基于Aidlux提供的算法平台,开发出更多更有趣的应用,使AI赋能我们生活的方方面面。

欢迎大家在帖子下方留言交流,希望生活可以变得更加美好!

? ? ?

可以