OpenCV实战项目 -- 口罩识别

news2024/11/27 22:23:01

每次我忘记戴口罩去食堂吃饭的时候,门口都会有志愿者学生提醒你:“你好,麻烦戴下口罩。” 进门后里面那块大屏幕还会发出声音:“请佩戴口罩”。

上次博客仿照宿舍楼下那块大屏幕写了个人脸考勤,所以这次我打算弄一个口罩识别。


实现口罩识别的方式有很多种,也并非是一件难事。重要的是要学会把学到的知识运用到生活中去。为此,我特地翻出了吃灰的“树莓派”主板,打算放到宿舍门口。无他,卷一卷督促室友学习。

一、简单流程思路分析

相信大多数初学人工智能的小伙伴,最早接触的一定是CNN处理图像分类的问题,本次项目的核心也就是CNN分类了。所以,这只是前面所学知识的结合,不做过多赘述。

A. 训练模型 

  • 在此之前,我们要弄到数据集:佩戴口罩未佩戴口罩佩戴口罩不规范 的图片。
  • 然后对数据集进行预处理。
  • 搭建CNN网络模型并进行图片分类。
  • 此时,我们便拿到了模型权重文件!

B. 处理摄像头输入

  • 打开摄像头,然后捕捉到我们的照片。
  • 处理输入图片,裁剪出人脸区域。
  • 裁剪后的图片作为神经网络的输入,得到网络预测分类结果。
  • 将结果显示在屏幕上。

C. 树莓派部署

  • 不难,但是有点花时间,这里不做过多说明。

二、CNN实现口罩佩戴图片分类

1. 数据集处理

无论是机器学习还是深度学习,最重要的便是数据集,之后才是相关的模型和算法。

在这里,我们首先处理图片,把每一张佩戴口罩的照片裁剪出人脸部分,之后为了便于计算和训练我们将图片进行压缩。相关方法之前博客已经介绍过这里不做赘述。

大家可以自己到网上下载数据集,也可以使用我参考的这份,数据量比较小,用于演示:链接

# 人脸检测函数
def face_detect(img):
    #转为Blob
    img_blob = cv2.dnn.blobFromImage(img,1,(300,300),(104,177,123),swapRB=True)
    # 输入
    face_detector.setInput(img_blob)
    # 推理
    detections = face_detector.forward()
    # 获取原图尺寸
    img_h,img_w = img.shape[:2]
    
    # 人脸框数量
    person_count = detections.shape[2]

    for face_index in range(person_count):
        # 通过置信度选择
        confidence = detections[0,0,face_index,2]
        if confidence > 0.5:
            locations = detections[0,0,face_index,3:7] * np.array([img_w,img_h,img_w,img_h])
            # 获得坐标 记得取整
            l,t,r,b = locations.astype('int')
            return img[t:b,l:r]
    return None

效果:

# 转为Blob格式函数
def imgBlob(img):
    # 转为Blob
    img_blob = cv2.dnn.blobFromImage(img,1,(100,100),(104,177,123),swapRB=True)
    # 维度压缩
    img_squeeze = np.squeeze(img_blob).T
    # 旋转
    img_rotate = cv2.rotate(img_squeeze,cv2.ROTATE_90_CLOCKWISE)
    # 镜像
    img_flip =  cv2.flip(img_rotate,1)
    # 去除负数,并归一化
    img_blob = np.maximum(img_flip,0) / img_flip.max()
    return img_blob

效果:

 有了这两个函数,我们就可以进行数据集的处理了:

import tqdm
import os,glob

labels = os.listdir('images/')

img_list = []
label_list = []
for label in labels:
    # 获取每类文件列表
    file_list =glob.glob('images/%s/*.jpg' % (label))
    
    for img_file in tqdm.tqdm( file_list ,desc = "处理文件夹 %s "  % (label)):
        # 读取文件
        img = cv2.imread(img_file)
        # 裁剪人脸
        img_crop = face_detect(img)
        # 转为Blob
        if img_crop is not None:
            img_blob = imgBlob(img_crop)
            img_list.append(img_blob)
            label_list.append(label)

 最后,我们将其转换为npz格式文件:

X = np.asarray(img_list)
Y = np.asarray(label_list)

np.savez('./data/imageData.npz',X,Y)

2. 模型训练

首先我们读取之前保存的npz文件:

import numpy as np
arr = np.load('./data/imageData.npz')
img_list = arr['arr_0']
label_list =arr['arr_1']
print(img_list.shape,label_list.shape)
((5328, 100, 100, 3), (5328,))

设置为onehot独热编码:

from sklearn.preprocessing import OneHotEncoder

onehot = OneHotEncoder()
# 编码
y_onehot =onehot.fit_transform(label_list.reshape(-1,1))
y_onehot_arr = y_onehot.toarray()

划分数据集:

from sklearn.model_selection import train_test_split

x_train,x_test,y_train,y_test=train_test_split(img_list,y_onehot_arr,test_size=0.2,random_state=123)

x_train.shape,x_test.shape,y_train.shape,y_test.shape
((4262, 100, 100, 3), (1066, 100, 100, 3), (4262, 3), (1066, 3))

构建并编译模型:

from tensorflow       import keras
from tensorflow.keras import layers,models
import tensorflow        as tf

gpus = tf.config.list_physical_devices("GPU")

if gpus:
    gpu0 = gpus[0]                                        #如果有多个GPU,仅使用第0个GPU
    tf.config.experimental.set_memory_growth(gpu0, True)  #设置GPU显存用量按需使用
    tf.config.set_visible_devices([gpu0],"GPU")


model = models.Sequential([
    layers.Conv2D(16,3,padding='same',input_shape=(100,100,3),activation='relu'),
    layers.MaxPool2D(),
    layers.Conv2D(32,3,padding='same',activation='relu'),
    layers.MaxPool2D(),
    layers.Conv2D(64,3,padding='same',activation='relu'),
    layers.MaxPool2D(),
    layers.Flatten(),
    layers.Dense(166,activation='relu'),
    layers.Dense(22,activation='relu'),
    layers.Dense(3,activation='sigmoid')
])

# 编译模型
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
              loss=tf.keras.losses.categorical_crossentropy,
              metrics=['accuracy'])
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 100, 100, 16)      448       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 50, 50, 16)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 50, 50, 32)        4640      
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 25, 25, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 25, 25, 64)        18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 12, 12, 64)        0         
_________________________________________________________________
flatten (Flatten)            (None, 9216)              0         
_________________________________________________________________
dense (Dense)                (None, 166)               1530022   
_________________________________________________________________
dense_1 (Dense)              (None, 22)                3674      
_________________________________________________________________
dense_2 (Dense)              (None, 3)                 69        
=================================================================
Total params: 1,557,349
Trainable params: 1,557,349
Non-trainable params: 0
_________________________________________________________________

训练模型:

history = model.fit(x=x_train,
                    y=y_train,
                    validation_data=(x_test,y_test),
                    batch_size=30,
                    epochs=15)

模型评估:

acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs_range = range(len(loss))

plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()

保存模型:

model.save('./data/face_mask_model')

三、模型测试

我们上面可以看到,简单的数据集和模型已经可以使准确率达到98%了,我们接下来就可以打开摄像头,然后获取自己的图片放入模型进行预测了!

在这之前,可以简单测试一下模型:

# 加载模型
model = tf.keras.models.load_model('./data/face_mask_model/')

# 挑选测试图片
img = cv2.imread('./images/2.no/0_0_caizhuoyan_0009.jpg')

plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))
plt.axis("off")

由于我们训练的时候,数据集是什么样的,做过什么处理,我们输入就要对其做同样的处理,才能保证预测的准确率:

# 裁剪人脸
img_crop = face_detect(img)

# 转为Blob
img_blob = imgBlob(img_crop)

# reshape
img_input = img_blob.reshape(1,100,100,3)

# 预测
result = model.predict(img_input)

预测结果:

labels = os.listdir('./images/')
labels[result.argmax()]

四、处理摄像头输入

就像上面说的,模型的输入要与训练时一致,所以我们同样要对其进行裁剪、格式转换、压缩、归一化的操作。

下面直接附上完整代码。

五、项目代码

权重文件和数据集:链接

大家可以根据自己需求更改代码。 

import cv2
import time
import numpy as np
import tensorflow as tf


class MaskDetection:

    def __init__(self,mode='rasp'):
        """
        加载人脸检测模型 和 口罩模型
        """
        gpus = tf.config.list_physical_devices("GPU")
        if gpus:
            gpu0 = gpus[0]                                        #如果有多个GPU,仅使用第0个GPU
            tf.config.experimental.set_memory_growth(gpu0, True)  #设置GPU显存用量按需使用
            tf.config.set_visible_devices([gpu0],"GPU")
         
        self.mask_model = tf.keras.models.load_model('./data/face_mask_model.h5')
        # 类别标签
        self.labels = ['正常','未佩戴','不规范']
        # 标签对应颜色,BGR顺序,绿色、红色、黄色
        self.colors = [(0,255,0),(0,0,255),(0,255,255)]

        # 获取label显示的图像
        self.zh_label_img_list = self.getLabelPngList()


    def getLabelPngList(self):
        """
        获取本地label显示的图像的列表
        """
        overlay_list = []
        for i in range(3):
            fileName = './label_img/%s.png' % (i)
            overlay = cv2.imread(fileName,cv2.COLOR_RGB2BGR)
            overlay = cv2.resize(overlay,(0,0), fx=0.3, fy=0.3)
            overlay_list.append(overlay)

        return overlay_list


    
    def imageBlob(self,face_region):
        """
        将图像转为blob
        """
        
        if face_region is not None:
            blob = cv2.dnn.blobFromImage(face_region,1,(100,100),(104,117,123),swapRB=True)
            blob_squeeze = np.squeeze(blob).T
            blob_rotate = cv2.rotate(blob_squeeze,cv2.ROTATE_90_CLOCKWISE)
            blob_flip = cv2.flip(blob_rotate,1)
            # 对于图像一般不用附属,所以将它移除
            # 归一化处理
            blob_norm = np.maximum(blob_flip,0) / blob_flip.max()
            return blob_norm
        else:
            return None

    def detect(self):
        """
        识别
        """

        face_detector = cv2.dnn.readNetFromCaffe('./weights/deploy.prototxt.txt','./weights/res10_300x300_ssd_iter_140000.caffemodel')
        
        cap = cv2.VideoCapture(0)

        frame_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        frame_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

        frameTime = time.time()

        videoWriter = cv2.VideoWriter('./record_video/out'+str(time.time())+'.mp4', cv2.VideoWriter_fourcc(*'H264'), 10, (960,720))


        while True:
            ret,frame = cap.read()
            frame = cv2.flip(frame,1)
            frame_resize = cv2.resize(frame,(300,300))

            img_blob = cv2.dnn.blobFromImage(frame_resize,1.0,(300,300),(104.0, 177.0, 123.0),swapRB=True)

            face_detector.setInput(img_blob)
            detections = face_detector.forward()
            num_of_detections = detections.shape[2]

            # 记录人数(框)
            person_count = 0

            # 遍历多个
            for index in range(num_of_detections):
                # 置信度
                detection_confidence = detections[0,0,index,2]
                # 挑选置信度
                if detection_confidence>0.5:

                    person_count+=1

                    # 位置坐标 记得放大
                    locations = detections[0,0,index,3:7] * np.array([frame_w,frame_h,frame_w,frame_h])
                    l,t,r,b  = locations.astype('int')
                    # 裁剪人脸区域
                    face_region = frame[t:b,l:r]
                    # 转为blob格式
                    blob_norm = self.imageBlob(face_region)

                    if blob_norm is not None:
                        # 模型预测
                        img_input = blob_norm.reshape(1,100,100,3)
                        result = self.mask_model.predict(img_input)
                        
                        # softmax分类器处理
                        result = tf.nn.softmax(result[0]).numpy()

                        # 最大值索引
                        max_index = result.argmax()
                        # 最大值
                        max_value = result[max_index]
                        # 标签
                        label = self.labels[max_index]

                        # 对应中文标签
                        overlay = self.zh_label_img_list[max_index]
                        overlay_h,overlay_w = overlay.shape[:2]

                        # 覆盖范围
                        overlay_l,overlay_t = l,(t - overlay_h-20)
                        overlay_r,overlay_b = (l + overlay_w),(overlay_t+overlay_h)

                        # 判断边界
                        if overlay_t > 0 and overlay_r < frame_w:
                            
                            overlay_copy=cv2.addWeighted(frame[overlay_t:overlay_b, overlay_l:overlay_r ],1,overlay,20,0)
                            frame[overlay_t:overlay_b, overlay_l:overlay_r ] = overlay_copy

                            cv2.putText(frame, str(round(max_value*100,2))+"%", (overlay_r+20, overlay_t+40), cv2.FONT_ITALIC, 0.8, self.colors[max_index], 2)

                    # 人脸框
                    cv2.rectangle(frame,(l,t),(r,b),self.colors[max_index],5)


            now = time.time()
            fpsText = 1 / (now - frameTime)
            frameTime = now

            cv2.putText(frame, "FPS:  " + str(round(fpsText,2)), (20, 40), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)
            cv2.putText(frame, "Person:  " + str(person_count), (20, 60), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)

            videoWriter.write(frame)

            cv2.imshow('demo',frame)

            if cv2.waitKey(10) & 0xFF == ord('q'):
                break

        videoWriter.release()
        cap.release()
        cv2.destroyAllWindows()
        

mask_detection = MaskDetection()
mask_detection.detect()

效果如下:

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/3848.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

std::logic_error 错误的解决

今天测试一个程序&#xff0c;突然出现一个 std::logic_error 错误&#xff0c;详细如下&#xff1a; 这个应该是 std::string 相关的一个错误&#xff0c;具体的错误信息还得用 GDB 跟踪一下了。 看第 8 栈帧已经是系统库里的东西了&#xff0c;第 9 帧是我本地的代码&#x…

【C进阶】之定义结构体及使用typedf

1 结构体中包含函数指针类型成员 声明的格式&#xff1a; struct 结构体名 { 返回类型 (*函数指针名)(形参列表); }; 定义结构体类型的变量并对结构体中的函数指针成员进行初始化 struct 结构体名 结构体变量名; 结构体变量名.函数指针名 函数名; // 函数指针指向的函数具有相…

目标检测(7)—— YOLO系列V3

一、YOLOV3 多scale 三种scale&#xff1a; 为了检测到不同大小的物体&#xff0c;设计了3个scale。 特征融合不好。 感受野大的特征图预测大的&#xff0c;中的预测中的&#xff0c;小的预测小的。各自预测各自的&#xff0c;不用做特征融合。 三个候选框&#xff1a; 每个特…

「C++小游戏教程」基本技巧(2)——系统 DOS 命令

0. 引言 「C小游戏教程」基本技巧(1)——随机化 在 (1) 中&#xff0c;我在使用 random_shuffle() 时加了一个 system("pause");。其中 system() 是系统发出 DOS 命令的函数&#xff0c;原型为 int system(char *command);。我们今天就来谈谈这个函数的主要功能用途…

Redis持久化

目录 一、Redis高可用 1.持久化 2.主从复制 3.哨兵 4.Cluster集群 二、Redis持久化 三、RDB持久化 1.概念 2.触发条件 &#xff08;1&#xff09;手动触发 &#xff08;2&#xff09;自动触发 3.执行流程 4.启动时自动加载 四、AOF持久化 1.概念 2.开启AOF持久…

微信公众号获取openid流程

说明 微信公众号获取openid&#xff0c;在官方文档中称为网页授权&#xff0c;授权有两种scope&#xff0c;snsapi_base和snsapi_userinfo&#xff0c;snsapi_base是静默授权&#xff0c;不需要用户同意&#xff0c;以下要说的就是静默授权。 关于网页授权的两种 scope 的区别…

[CISCN2019 华北赛区 Day1 Web2]ikun

要买lv6 然后下一页下一页的找&#xff0c;也找不到 来个脚本爆破一下 #coding&#xff1a;utf-8 import requests import time for i in range(1,200):print(i)url http://17dfa3f4-2826-4b97-9d61-a920b6c8976f.node4.buuoj.cn:81/shop?page{}.format(i)r requests.get…

目标检测(6)—— YOLO系列V2

一、YOLOV2改进的概述 做的改进如下图&#xff1a; Batch Normalization 批量归一化层 不加BN层&#xff0c;网络可能学偏&#xff0c;加上归一化进行限制。 从今天来看&#xff0c;conv后加BN是标配。 更大的分辨率 V1训练的时候使用224224&#xff0c;测试用448448。 V2训…

胡扯推荐算法(协同)及其dome实现

文章目录前言推荐系统定义基本分类相似度计算欧式距离皮尔逊系数余弦相似度协同过滤案例数据定义相似度计算推荐svd奇异值分解优化完整代码总结前言 打瞌睡遇到送枕头的感觉真爽嘿嘿 BoyC啊 废话不多说&#xff0c;开始吧。 推荐系统定义 推荐系统(Recommendation System,…

张益唐与黎曼猜想

一个人活在世界上&#xff0c;可以有不同层次的贡献&#xff0c;有正的有负的。唐山烧烤店打人的那种人&#xff0c;就是典型的负的贡献。正的贡献有大有小&#xff0c;像我这样开发一个小软件&#xff0c;写一段小博文&#xff0c;这是小的贡献&#xff1b;像张益唐这样&#…

zlMediaKit 9 ThreadPool模块

semaphore.h TaskQueue.h threadgroup.h ThreadPool.h ThreadPool semaphore 基于条件变量和锁实现的信号量post和wait语义 #include <mutex> #include <condition_variable>namespace toolkit {class semaphore { public:explicit semaphore(size_t initia…

Linux命令从入门到实战 ----查找文件和目录压缩和解压缩

文章目录搜索查找find查找文件和目录locate快速定位文件路径grep 过滤查找| 管道符which命令用于查找文件。whereis压缩和解压缩gzip/gunzip 压缩zip/unzip压缩tar打包总结搜索查找 find查找文件和目录 find指令将从指令指定目录下向下遍历其各个子目录&#xff0c;将满足条件…

算法60天:day46

算法60天&#xff1a;day46动态规划-单词拆分动态规划-多重背包问题动态规划-背包总结动态规划-单词拆分 力扣链接 class Solution { public:bool wordBreak(string s, vector<string>& wordDict) {unordered_set<string> wordSet(wordDict.begin(),wordDict…

RabbitMQ消息发送和接收(实例)

消息发送&#xff1a; 1.首先启动rabbitmq 2.查看防火墙状态&#xff0c;如果是开启状态则需要关闭防火墙 3.通过浏览器访问rabbitmq控制台&#xff0c;ip15672端口号 &#xff0c;例如http://192.168.174.129:15672 登录时输入自己的此前设置的登录名和密码 4.打开idea,创建r…

农产品果蔬商城交易系统(Java+Web+MySQL)

目录 摘要 I Abstract II 前言 1 1 课题简介 2 1.1 选题背景 2 1.2 课题的意义 2 1.3 系统目标 3 2. 可行性研究 5 2.1 技术可行性 5 2.2 经济可行性 5 2.3 操作可行性 5 2.4 法律可行性 6 3. 需求分析 7 3.1 系统需要解决的主要问题 7 3.2 系统具备的基本功能 7 3.3 数据流图…

MySQL开篇:简单的库操作,表操作,数据类型

✨博客主页: 心荣~ ✨系列专栏:【MySQL】 ✨一句短话: 难在坚持,贵在坚持,成在坚持! 文章目录一. 什么是MySQL二. 基础库操作1. 创建数据库2. 查看所有数据库3. 选中数据库4. 删除数据库三. 设置数据库的编码字符集四. MySQL数据类型1. 数值类型2. 字符串类型3. 日期数据类型五…

_Linux 动态库

文章目录0. 前言1. 生成动态库1.1 我们把静态库和动态库打包1.2 当动静库同时存在的时候默认生成的是动态库1.3 -static2. 动态库的使用2.1 运行动态库的方法3. 库文件名称和引入库的名称0. 前言 链接&#xff1a;静态库文章 上一章我们讲解了静态库的生成和两种使用&#xff…

Netty架构设计

目录 Selector模型 SelectableChannel Channel注册到Selector SelectionKey 遍历SelectionKey 事件驱动 责任链模式 Selector模型 Java NIO是基于Selector模型来实现非阻塞IO&#xff0c;Netty底层基于Java NIO实现的&#xff0c;因此也使用了Selector模型。 Selector提…

Go语言五大主流web框架

以下 star数截止2022年11月份 1.Gin&#xff08;64.1K&#xff09; 项目简介&#xff1a;Gin 是一个用 Go (Golang) 编写的 HTTP Web 框架。 它具有类似 Martini 的 API&#xff0c;但性能比 Martini 快 40 倍。 仓库地址&#xff1a;https://github.com/gin-gonic/ginhttps…

TensorRt安装和命令行测试

1、选择TensorRt版本 安装tensorrt前&#xff0c;需要先了解自己的显卡算力、架构等&#xff0c;点击 算力列表链接 对号入座。 这里仅展示RTX和Titan系列&#xff0c;其他系列可在当前网页选择。 1.1、cuda版本 首先需要安装cuda&#xff0c;其版本并不是最新就好&#xf…