大概的思路如下

  1. 就是利用mobile-ssd轻量化模型进行目标检测,识别我的人脸和杯子,如果离的比较近就认为已经喝水了(毕竟杯子到嘴边的情况只能是喝水的时候才会发生,谁没事和杯子贴贴?)
  2. 在树莓派4B上安装环境部署模型
  3. 写出逻辑,进行实时图像判断

其实除了目标检测,还可以使用图像分类(判断是否是喝水的状态,理论上效果会更好)、人体关键点检测(非常具有挑战性的一种方法)等等,我是因为目标检测比较熟悉且有进一步开发的需要,所以采用了目标检测的方法。

模型训练

本次模型采用了mobile-ssd模型,为了保证模型的准确率,训练所用的数据均为自己采集的。训练好的模型会放在ssd-model下,预训练模型在pretrained-model下。

数据准备

为了平衡计算速度和准确率,本次采集了640×480大小的图片,推理的时候也会使用这个大小。
我是用Windows自带的相机应用,选择640×480的大小,来采集数据集,更方便一点,如下图所示:

PaddleNLp能否在手机端部署_PaddleNLp能否在手机端部署

实现判断喝水的需求,没有必要标注整个脸部,我选择标注month和cup,如下图所示:

PaddleNLp能否在手机端部署_计算机视觉_02

总共采集了44张图片,经过了水平翻转、高斯噪点、随机亮度的增强,最终得到了264条数据

如果时间足够,想保证稳定性,一定要多采一些!!

如果不是数据特别少,请慎重考虑高斯噪点增强,这会增加不少训练时间

最后的数据集是放在了work目录下:

-work
--voc
---label_list
---image
---annotation

label_list自己手动新建一下,重命名加后缀名.txt就可以修改,修改完之后再删掉后缀名就行,里面存放你的标签名,一行一个标签名

我的label_list的内容如下

background
mouth
cup

我们还需要生成train.txt和eval.txt,生成方法在下面的代码块里

# 在work生成train.txt 和 eval.txt, 这两个txt和image、annotation目录同级,每行的内容是:图片的相对路径、空格、标签的相对路径,注意是相对txt的路径
# 要保证work/下的目录和我上面的目录一直,否则这个代码块块会报错

import os 
import random
l = os.listdir("work/voc/image/")
random.shuffle(l)  
r = 0.2 # 这个是验证集占数据集的比例,可以适当的修改
for i in range(0, len(l)):
    if i < r * len(l):
        with open("work/voc/eval.txt", "a") as f:
            f.write("image/%s annotation/%s\n" % (l[i], l[i].replace("png", "xml").replace("jpg", "xml")))
    else:
        with open("work/voc/train.txt", "a") as f:
            f.write("image/%s annotation/%s\n" % (l[i], l[i].replace("png", "xml").replace("jpg", "xml")))

开始训练

  1. 训练的脚本就是train.py,需要修改以下几点
  2. train.py line 26,包含背景类,比如你有四类,就要加上一类背景类,写5
  3. data_dir 就是你的train.txt eval.txt image annotation所在的目录
  4. 模型会用data_dir + file_list 找到train.txt, 再根据train.txt里面的相对路径,找到图片和标签
  5. train.py line 46,batch_size可以修改的大一点,因为需要的显存特别小,bs可以非常大,也可以去train.txt 和eval.txt里,调整一下训练集和验证集的数据量
  6. 建议在32G环境下训练,一般30分钟内就能出结果。
%cd /home/aistudio/
!python train.py
# 我们需要把已经训练好的模型进行转换,转成可以在树莓派上使用的格式,先安装paddlelite
!pip install paddlelite==2.9
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
Collecting paddlelite==2.9
  Downloading https://pypi.tuna.tsinghua.edu.cn/packages/77/5e/2f463252bcf7de6d594cff2a89f76c643088946203846fbdb845f9cb5378/paddlelite-2.9-cp37-cp37m-manylinux1_x86_64.whl (45.8 MB)
[2K     [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m45.8/45.8 MB[0m [31m2.5 MB/s[0m eta [36m0:00:00[0m00:01[0m00:01[0m
[?25hInstalling collected packages: paddlelite
Successfully installed paddlelite-2.9

[1m[[0m[34;49mnotice[0m[1;39;49m][0m[39;49m A new release of pip available: [0m[31;49m22.1.2[0m[39;49m -> [0m[32;49m22.2.2[0m
[1m[[0m[34;49mnotice[0m[1;39;49m][0m[39;49m To update, run: [0m[32;49mpip install --upgrade pip[0m
%cd /home/aistudio/
# 使用Opt优化工具将导出的模型文件转换为Paddle-Lite所用的模型文件同时可进行模型量化等优化方式
!paddle_lite_opt --model_file=ssd-model/mobilenet-ssd-model \
                 --param_file=ssd-model/mobilenet-ssd-params \
                 --optimize_out_type=naive_buffer \
                 --valid_targets=arm \
                 --optimize_out=fer_opt 

# 运行之后,在你的/home/aistudio目录下有一个fer_opt.nb的文件,这个文件就是我们在树莓派上使用的模型文件
/home/aistudio
Loading topology data from ssd-model/mobilenet-ssd-model
Loading params data from ssd-model/mobilenet-ssd-params
1. Model is successfully loaded!
2. Model is optimized and saved into fer_opt.nb successfully

功能实现

注意,这个地方我放的源码是在树莓派上可以正常运行的,强烈建议大家提前去看一下Paddle Lite的文档,很详细

代码编写

在实际运行的过程中发现,逻辑判断只需要判断杯子就可以,因为杯子放在桌子上,摄像头根本看不到,平时拿起杯子就是喝水的,所以逻辑判断写的很简单。

# 构建检测器
# 引入必要的库
from paddlelite.lite import *
import numpy as np 
import cv2
import time


cap = cv2.VideoCapture(0)
cap.set(3, 640)
cap.set(4, 480)


ssd_args = {
    "shape": [1, 3, 300, 300],
    "ms": [127.5, 0.007843]
}


def ssd_preprocess(src):
    shape = ssd_args["shape"]
    src = cv2.resize(src, (shape[3], shape[2]))
    src = cv2.cvtColor(src, cv2.COLOR_BGR2RGB)
    # img = img.astype(np.float32)
    inp = np.array(src).astype(np.float32)
    inp = np.expand_dims(inp, axis=0)
    inp = inp.transpose(0, 3, 1, 2)
    inp -= 127.5
    inp *= 0.007843
    return inp


class HeShui_detector:
    def __init__(self):
        # 1. Set config information
        config = MobileConfig()
        # 2. Set the path to the model generated by opt tools
        config.set_model_from_file("model/fer_opt.nb")
        # 3. Create predictor by config
        self.predictor = create_paddle_predictor(config)

    def predict(self, input):
        input_tensor = self.predictor.get_input(0)
        input_tensor.from_numpy(input.astype("float32"))
        self.predictor.run()
        output_tensor = self.predictor.get_output(0)
        output_data = output_tensor.numpy()
        # print(output_data)
        # 返回的列表,第一个元素表示嘴,第二个元素表示杯子
        results = [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
        for i in output_data:
            if i[1] > results[int(i[0]) - 1][0]:
                results[int(i[0]) - 1][0] = i[1]
                results[int(i[0]) - 1][1] = i[2] * 640
                results[int(i[0]) - 1][2] = i[3] * 480
                results[int(i[0]) - 1][3] = i[4] * 640
                results[int(i[0]) - 1][4] = i[5] * 480
        return results


def main():
    heshuidetector = HeShui_detector()
    T1 = time.time()
    # 开始进行逻辑判断
    isHeshui = 0
    while True:
        ret, frame = cap.read()
        inp = ssd_preprocess(frame)
        results = heshuidetector.predict(inp)
        if results[1][0] > 0.8:
            isHeshui += 1
        else:
            isHeshui = 0
        if isHeshui == 2:
            T1 = time.time()
            print("正在喝水,咕噜咕噜~")
        else:
            T2 = time.time()
            if T2 - T1 > 30:
                print('上次喝水是在%s秒前, 该喝水了' % (int(T2 - T1)))
            else:
                print('上次喝水是在%s秒前' % (int(T2 - T1)))
  

if __name__ == '__main__':
)
            else:
                print('上次喝水是在%s秒前' % (int(T2 - T1)))
  

if __name__ == '__main__':
    main()

效果展示

测试30秒不喝水就进行提醒。

其实有一点点小遗憾,屏幕上的文字提醒终究是不如图像或者声音舒服,然后手上没有合适的小屏幕,有没有声音播放工具,遗憾只能留到下次解决了。

PaddleNLp能否在手机端部署_PaddleNLp能否在手机端部署_03