• OpenCV实战项目 -- 口罩识别


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

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


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

    一、简单流程思路分析

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

    A. 训练模型 

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

    B. 处理摄像头输入

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

    C. 树莓派部署

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

    权重文件和整体代码在文章最后 

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

    1. 数据集处理

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

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

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

    1. face_detector = cv2.dnn.readNetFromCaffe('./weights/deploy.prototxt.txt','weights/res10_300x300_ssd_iter_140000.caffemodel')
    2. # 人脸检测函数
    3. def face_detect(img):
    4. #转为Blob
    5. img_blob = cv2.dnn.blobFromImage(img,1,(300,300),(104,177,123),swapRB=True)
    6. # 输入
    7. face_detector.setInput(img_blob)
    8. # 推理
    9. detections = face_detector.forward()
    10. # 获取原图尺寸
    11. img_h,img_w = img.shape[:2]
    12. # 人脸框数量
    13. person_count = detections.shape[2]
    14. for face_index in range(person_count):
    15. # 通过置信度选择
    16. confidence = detections[0,0,face_index,2]
    17. if confidence > 0.5:
    18. locations = detections[0,0,face_index,3:7] * np.array([img_w,img_h,img_w,img_h])
    19. # 获得坐标 记得取整
    20. l,t,r,b = locations.astype('int')
    21. return img[t:b,l:r]
    22. return None

    效果:

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

    效果:

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

    1. import tqdm
    2. import os,glob
    3. labels = os.listdir('images/')
    4. img_list = []
    5. label_list = []
    6. for label in labels:
    7. # 获取每类文件列表
    8. file_list =glob.glob('images/%s/*.jpg' % (label))
    9. for img_file in tqdm.tqdm( file_list ,desc = "处理文件夹 %s " % (label)):
    10. # 读取文件
    11. img = cv2.imread(img_file)
    12. # 裁剪人脸
    13. img_crop = face_detect(img)
    14. # 转为Blob
    15. if img_crop is not None:
    16. img_blob = imgBlob(img_crop)
    17. img_list.append(img_blob)
    18. label_list.append(label)

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

    1. X = np.asarray(img_list)
    2. Y = np.asarray(label_list)
    3. np.savez('./data/imageData.npz',X,Y)

    2. 模型训练

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

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

    设置为onehot独热编码:

    1. from sklearn.preprocessing import OneHotEncoder
    2. onehot = OneHotEncoder()
    3. # 编码
    4. y_onehot =onehot.fit_transform(label_list.reshape(-1,1))
    5. y_onehot_arr = y_onehot.toarray()

    划分数据集:

    1. from sklearn.model_selection import train_test_split
    2. x_train,x_test,y_train,y_test=train_test_split(img_list,y_onehot_arr,test_size=0.2,random_state=123)
    3. x_train.shape,x_test.shape,y_train.shape,y_test.shape
    ((4262, 100, 100, 3), (1066, 100, 100, 3), (4262, 3), (1066, 3))

    构建并编译模型:

    1. from tensorflow import keras
    2. from tensorflow.keras import layers,models
    3. import tensorflow as tf
    4. gpus = tf.config.list_physical_devices("GPU")
    5. if gpus:
    6. gpu0 = gpus[0] #如果有多个GPU,仅使用第0个GPU
    7. tf.config.experimental.set_memory_growth(gpu0, True) #设置GPU显存用量按需使用
    8. tf.config.set_visible_devices([gpu0],"GPU")
    9. model = models.Sequential([
    10. layers.Conv2D(16,3,padding='same',input_shape=(100,100,3),activation='relu'),
    11. layers.MaxPool2D(),
    12. layers.Conv2D(32,3,padding='same',activation='relu'),
    13. layers.MaxPool2D(),
    14. layers.Conv2D(64,3,padding='same',activation='relu'),
    15. layers.MaxPool2D(),
    16. layers.Flatten(),
    17. layers.Dense(166,activation='relu'),
    18. layers.Dense(22,activation='relu'),
    19. layers.Dense(3,activation='sigmoid')
    20. ])
    21. # 编译模型
    22. model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
    23. loss=tf.keras.losses.categorical_crossentropy,
    24. 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
    _________________________________________________________________

    训练模型:

    1. history = model.fit(x=x_train,
    2. y=y_train,
    3. validation_data=(x_test,y_test),
    4. batch_size=30,
    5. epochs=15)

    模型评估:

    1. acc = history.history['accuracy']
    2. val_acc = history.history['val_accuracy']
    3. loss = history.history['loss']
    4. val_loss = history.history['val_loss']
    5. epochs_range = range(len(loss))
    6. plt.figure(figsize=(12, 4))
    7. plt.subplot(1, 2, 1)
    8. plt.plot(epochs_range, acc, label='Training Accuracy')
    9. plt.plot(epochs_range, val_acc, label='Validation Accuracy')
    10. plt.legend(loc='lower right')
    11. plt.title('Training and Validation Accuracy')
    12. plt.subplot(1, 2, 2)
    13. plt.plot(epochs_range, loss, label='Training Loss')
    14. plt.plot(epochs_range, val_loss, label='Validation Loss')
    15. plt.legend(loc='upper right')
    16. plt.title('Training and Validation Loss')
    17. plt.show()

    保存模型:

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

    三、模型测试

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

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

    1. # 加载模型
    2. model = tf.keras.models.load_model('./data/face_mask_model/')
    3. # 挑选测试图片
    4. img = cv2.imread('./images/2.no/0_0_caizhuoyan_0009.jpg')
    5. plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))
    6. plt.axis("off")

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

    1. # 裁剪人脸
    2. img_crop = face_detect(img)
    3. # 转为Blob
    4. img_blob = imgBlob(img_crop)
    5. # reshape
    6. img_input = img_blob.reshape(1,100,100,3)
    7. # 预测
    8. result = model.predict(img_input)

    预测结果:

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

    四、处理摄像头输入

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

    下面直接附上完整代码。

    五、项目代码

    权重文件和数据集:链接

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

    1. import cv2
    2. import time
    3. import numpy as np
    4. import tensorflow as tf
    5. class MaskDetection:
    6. def __init__(self,mode='rasp'):
    7. """
    8. 加载人脸检测模型 和 口罩模型
    9. """
    10. gpus = tf.config.list_physical_devices("GPU")
    11. if gpus:
    12. gpu0 = gpus[0] #如果有多个GPU,仅使用第0个GPU
    13. tf.config.experimental.set_memory_growth(gpu0, True) #设置GPU显存用量按需使用
    14. tf.config.set_visible_devices([gpu0],"GPU")
    15. self.mask_model = tf.keras.models.load_model('./data/face_mask_model.h5')
    16. # 类别标签
    17. self.labels = ['正常','未佩戴','不规范']
    18. # 标签对应颜色,BGR顺序,绿色、红色、黄色
    19. self.colors = [(0,255,0),(0,0,255),(0,255,255)]
    20. # 获取label显示的图像
    21. self.zh_label_img_list = self.getLabelPngList()
    22. def getLabelPngList(self):
    23. """
    24. 获取本地label显示的图像的列表
    25. """
    26. overlay_list = []
    27. for i in range(3):
    28. fileName = './label_img/%s.png' % (i)
    29. overlay = cv2.imread(fileName,cv2.COLOR_RGB2BGR)
    30. overlay = cv2.resize(overlay,(0,0), fx=0.3, fy=0.3)
    31. overlay_list.append(overlay)
    32. return overlay_list
    33. def imageBlob(self,face_region):
    34. """
    35. 将图像转为blob
    36. """
    37. if face_region is not None:
    38. blob = cv2.dnn.blobFromImage(face_region,1,(100,100),(104,117,123),swapRB=True)
    39. blob_squeeze = np.squeeze(blob).T
    40. blob_rotate = cv2.rotate(blob_squeeze,cv2.ROTATE_90_CLOCKWISE)
    41. blob_flip = cv2.flip(blob_rotate,1)
    42. # 对于图像一般不用附属,所以将它移除
    43. # 归一化处理
    44. blob_norm = np.maximum(blob_flip,0) / blob_flip.max()
    45. return blob_norm
    46. else:
    47. return None
    48. def detect(self):
    49. """
    50. 识别
    51. """
    52. face_detector = cv2.dnn.readNetFromCaffe('./weights/deploy.prototxt.txt','./weights/res10_300x300_ssd_iter_140000.caffemodel')
    53. cap = cv2.VideoCapture(0)
    54. frame_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    55. frame_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    56. frameTime = time.time()
    57. videoWriter = cv2.VideoWriter('./record_video/out'+str(time.time())+'.mp4', cv2.VideoWriter_fourcc(*'H264'), 10, (960,720))
    58. while True:
    59. ret,frame = cap.read()
    60. frame = cv2.flip(frame,1)
    61. frame_resize = cv2.resize(frame,(300,300))
    62. img_blob = cv2.dnn.blobFromImage(frame_resize,1.0,(300,300),(104.0, 177.0, 123.0),swapRB=True)
    63. face_detector.setInput(img_blob)
    64. detections = face_detector.forward()
    65. num_of_detections = detections.shape[2]
    66. # 记录人数(框)
    67. person_count = 0
    68. # 遍历多个
    69. for index in range(num_of_detections):
    70. # 置信度
    71. detection_confidence = detections[0,0,index,2]
    72. # 挑选置信度
    73. if detection_confidence>0.5:
    74. person_count+=1
    75. # 位置坐标 记得放大
    76. locations = detections[0,0,index,3:7] * np.array([frame_w,frame_h,frame_w,frame_h])
    77. l,t,r,b = locations.astype('int')
    78. # 裁剪人脸区域
    79. face_region = frame[t:b,l:r]
    80. # 转为blob格式
    81. blob_norm = self.imageBlob(face_region)
    82. if blob_norm is not None:
    83. # 模型预测
    84. img_input = blob_norm.reshape(1,100,100,3)
    85. result = self.mask_model.predict(img_input)
    86. # softmax分类器处理
    87. result = tf.nn.softmax(result[0]).numpy()
    88. # 最大值索引
    89. max_index = result.argmax()
    90. # 最大值
    91. max_value = result[max_index]
    92. # 标签
    93. label = self.labels[max_index]
    94. # 对应中文标签
    95. overlay = self.zh_label_img_list[max_index]
    96. overlay_h,overlay_w = overlay.shape[:2]
    97. # 覆盖范围
    98. overlay_l,overlay_t = l,(t - overlay_h-20)
    99. overlay_r,overlay_b = (l + overlay_w),(overlay_t+overlay_h)
    100. # 判断边界
    101. if overlay_t > 0 and overlay_r < frame_w:
    102. overlay_copy=cv2.addWeighted(frame[overlay_t:overlay_b, overlay_l:overlay_r ],1,overlay,20,0)
    103. frame[overlay_t:overlay_b, overlay_l:overlay_r ] = overlay_copy
    104. 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)
    105. # 人脸框
    106. cv2.rectangle(frame,(l,t),(r,b),self.colors[max_index],5)
    107. now = time.time()
    108. fpsText = 1 / (now - frameTime)
    109. frameTime = now
    110. cv2.putText(frame, "FPS: " + str(round(fpsText,2)), (20, 40), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)
    111. cv2.putText(frame, "Person: " + str(person_count), (20, 60), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)
    112. videoWriter.write(frame)
    113. cv2.imshow('demo',frame)
    114. if cv2.waitKey(10) & 0xFF == ord('q'):
    115. break
    116. videoWriter.release()
    117. cap.release()
    118. cv2.destroyAllWindows()
    119. mask_detection = MaskDetection()
    120. mask_detection.detect()

    效果如下:

  • 相关阅读:
    ESP8266--SDK开发(延时、定时器)
    maven下载安装及IDEA配置、使用maven导出项目jar包并部署到服务器上
    题目0144-最大利润
    Linux下应用程序调试
    Midjourney绘画提示词Prompt参考教程
    今年618各云厂商的香港服务器优惠活动汇总
    51单片机驱动HMI串口屏,串口屏的下载方式
    抖音矩阵系统,。抖音矩阵系统,。抖音矩阵系统,。抖音矩阵系统,。抖音矩阵系统,。
    全面分析“由于找不到vcruntime140_1.dll,无法继续执行代码”问题的解决方法
    Flask部署pytorch服务
  • 原文地址:https://blog.csdn.net/suic009/article/details/127707064