飞道的博客

OpenCV实战项目 -- 口罩识别

279人阅读  评论(0)

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

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


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

一、简单流程思路分析

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

A. 训练模型 

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

B. 处理摄像头输入

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

C. 树莓派部署

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

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

1. 数据集处理

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

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

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


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

效果如下:

 

 


转载:https://blog.csdn.net/suic009/article/details/127707064
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场