基于图像的口罩佩戴自动检测系统设计
一、前言
学校的一门综合设计,在此仅作学习记录,在此仅作学习记录!在此仅作学习记录!再不记录一下自己都忘掉了,同时希望看到的友友指出不足,共同进步,如果有的话吼吼吼吼吼~。
二、背景
- 2020 年初,新型冠状肺炎疫情爆发,研究表明佩戴口罩相对于不佩戴口罩的感染风险大大降低。
- 设计实现一种可以基于视频图像的口罩佩戴正确与否的自动检测系统具有重要意义。
- 虽然现在似乎好像应该大概是有亿点过时,但是问题不大,浅记一下。
三、设计目标
- 进行人脸检测,将人脸进行框选
- 进行人脸物理特征识别,框选眼睛、鼻子、嘴巴等人脸特征信息
- 进行口罩佩戴判断,利用以上特征信息,通过是否检测到相关特征进行口罩佩戴情况的判断,佩戴情况分成三种:未佩戴,正确佩戴,错误佩戴。
四、实现方法
1. 基于特征检测的方法(本文方法)
- 输入视频图像,获取图像帧
- 背景减除法分离运动前景,即移动的人,目的是缩小检测氛围。
- 肤色提取分离人脸。
- Haar特征级联分类器进行人脸检测和特征提取,并框选人脸和相关特征。
- 根据是否检测到眼睛、鼻子、嘴巴等特征进行口罩佩戴情况分类,给出警示信息。
2. 深度学习的方法 (底子薄,没用这个)
咱就是一小小本科生,当时才开始接触深度学习,深度学习底子薄,用深度学习感觉难度大,就没选择这种方案。现在还是建议采用深度学习,准确率高,速度快,用yolo系列可以直接一步到位。
五、实现源代码
# Project: zonghesheji
# author:@hong
# Time: 2022/12
import numpy as np
import cv2 as cv
import random
#face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
#读取面部识别分类器
face_cascade = cv.CascadeClassifier('D:/opencv/opencv/sources/data/haarcascades/haarcascade_frontalface_default.xml')
#读取嘴巴识别分类器
mouth_cascade = cv.CascadeClassifier('D:/opencv/opencv/sources/data/haarcascades/haarcascade_mcs_mouth.xml')
#读取鼻子识别分类器
nose_cascade = cv.CascadeClassifier('D:/opencv/opencv/sources/data/haarcascades/haarcascade_mcs_nose.xml')
#读取眼睛识别分类器
eye_cascade = cv.CascadeClassifier('D:/opencv/opencv/sources/data/haarcascades/haarcascade_eye.xml')
#二值化最小阈值
black_white_threshold = 80
##初始化字体
font = cv.FONT_HERSHEY_SIMPLEX
#绘制字体框的大小
org = (30, 30)
#戴口罩字体颜色
weared_mask_font_color = (0, 255, 0)
#不戴口罩字体颜色
not_weared_mask_font_color = (0, 0, 255)
#错误佩戴口罩字体颜色
error_weared_mask_font_color = (30, 255, 255)
noface = (255, 255, 255)
thickness = 2
font_scale = 1
weared_mask = "Thank You for wearing MASK"
not_weared_mask = "Please wear MASK to defeat Corona"
error_weared_mask="Please wear MASK correctly"
def empty(a):
pass
# 更新背景照片
def getbackground():
cap = cv.VideoCapture(0)
num = 0
while True:
ok, frame = cap.read()
image = cv.GaussianBlur(frame, (3, 3), 0) # 高斯滤波
# cv2.imshow("gauss",image)
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY) # 将图片转化成灰度
num = num + 1
if num == 10:
backgound = gray
cv.imwrite("./backgound.jpg", backgound)
cv.imshow("backgound", backgound)
break
image = cv.imread("./withmask.jpg") # 读取初始照片
cv.imshow('orignal_image', image) # 展示
#cv.createTrackbar("Hmin", "orignal_image", 0, 90, empty) # 创建bar
#cv.createTrackbar("Hmax", "orignal_image", 25, 90, empty)
#定义背景颜色
colour = ((0, 205, 205), (154, 250, 0), (34, 34, 178), (211, 0, 148), (255, 118, 72), (137, 137, 139))
#打开摄像头
cap = cv.VideoCapture(0)
history=200 #训练帧数
bs=cv.createBackgroundSubtractorKNN(detectShadows=False) ##基于KNN的前景分割算法
#bs=cv.createBackgroundSubtractorMOG2(detectShadows=True) ##基于MOG2的前景分割算法,速度比KNN快,但是质量没有LNN好,因此选择KNN算法
#bs=cv.createb(detectShadows=True) ##基于KNN的前景分割算法 ##opencv库中没有GMG算法用于背景减除
bs.setHistory(history)
frames=0
#前景分割算法的前提是要先拍摄一张环境图作为背景
getbackground()#保存背景
backgound=cv.imread("./backgound.jpg")
backgound=cv.cvtColor(backgound, cv.COLOR_BGR2GRAY)#将图片转化成灰度
gray=backgound #k-1张
while True:
gray_last=gray #k-1张,方便后面考虑对比帧间差分法
#cv.imshow("gray_last", gray)
ret, img = cap.read()
#镜像翻转
img = cv.flip(img, 1)
cv.imshow("orignal_image", img) ##展示摄像头输入图像
###肤色区域检测 Ycrcb法
ycrcb = cv.cvtColor(img, cv.COLOR_BGR2YCR_CB) #将BGR色彩空间图片转化到YCrCb色彩空间
(y, cr, cb) = cv.split(ycrcb) #分别提取
cr1 = cv.GaussianBlur(cr, (5, 5), 0) #高斯模糊去除高斯噪声,降低细节层次
_, skin = cv.threshold(cr1, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU) #大津法(大阈值图像分割法)二值化处理
#cv.imshow("image raw", img)
#cv.imshow("image CR", cr1)
cv.imshow("Skin Cr+OTSU", skin)
dst = cv.bitwise_and(img, img, mask=skin)
cv.imshow("seperate", dst) #分离提取肤色区间
####显示肤色区域 HSV法---效果很差
"""""""""
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV) #将图片转化成HSV格式
H, S, V = cv.split(hsv)
Hmin = cv.getTrackbarPos("Hmin", 'orignal_image') # 获取bar
Hmax = cv.getTrackbarPos("Hmax", 'orignal_image')
if Hmin > Hmax:
Hmax = Hmin
thresh_h = cv.inRange(H, Hmin, Hmax) # 提取人体肤色区域
cv.imshow("thresh_h1", thresh_h)
"""
#高斯模糊去除噪声
#img = cv.GaussianBlur(dst, (3, 3), 0) ##检测不到人脸
img = cv.GaussianBlur(img, (3, 3), 0)
#cv.imshow("Gaussionblur", img)
#转为灰度图像
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
cv.imshow("gray_image", gray)
"""""""""
### 背景差分处理
fgmask1 = cv.absdiff(gray, backgound) # 背景差分
cv.imshow("fgmask1", fgmask1)
### 帧间差分处理
fgmask2 = cv.absdiff(gray, gray_last) # 帧间差分法
cv.imshow("fgmask2", fgmask2)
###按比例而调节的改进差分法
fgmask = cv.addWeighted(fgmask1, 0.3, fgmask2, 0.7, 0) # 按比例相加,相当于差分均值
cv.imshow("weight", fgmask)
"""
# 获取foreground mask 获取前景分割图
fg_mask = bs.apply(img) ##应用于前景掩模
cv.imshow("foreground mask", fg_mask)
if frames<history:
frames+=1
continue
#图像二值化
thresh, black_and_white= cv.threshold(fg_mask, 100, 255, cv.THRESH_BINARY)
cv.imshow("threshold_image", black_and_white)
""""""""""
fgmask = cv.dilate(black_and_white, cv.getStructuringElement(cv.MORPH_ELLIPSE,(8,3)), iterations=8) # 膨胀
cv.imshow('dilate', fgmask)
fgmask = cv.erode(fgmask, cv.getStructuringElement(cv.MORPH_ELLIPSE,(3,3)), iterations=5 ) # 腐蚀
cv.imshow('erode', fgmask)
"""""
# 腐蚀
fgmask = cv.erode(black_and_white, cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5)), iterations=1 )
#cv.imshow('erode', fgmask)
# 膨胀
fgmask = cv.dilate(fgmask, cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5)), iterations=20)
#cv.imshow('dilate', fgmask)
# 形态学去噪
element = cv.getStructuringElement(cv.MORPH_CROSS, (25, 25))
# 开运算去噪
fgmask = cv.morphologyEx(fgmask, cv.MORPH_CLOSE, element)
#cv.imshow('kaiyunsuan', fgmask)
# 寻找运动前景勾画轮廓
contours, hierarchy = cv.findContours(fgmask.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
cv.drawContours(img, contours, -1, (0, 0, 255), 3)
rect_array = []
count = 0
for cont in contours:
Area = cv.contourArea(cont) # 计算轮廓面积
if Area < 1500: # 过滤面积小于10的形状
continue
count += 1 # 计数加一
# print("{}-prospect:{}".format(count,Area),end=" ") #打印出每个前景的面积
rect = cv.boundingRect(cont) # 提取矩形坐标
# rect_array.append(rect)
# print("x:{} y:{}".format(rect[0],rect[1]))#打印坐标
#cv.rectangle(img, (rect[0], rect[1]), (rect[0] + rect[2], rect[1] + rect[3]), colour[count % 6],1) # 原图上绘制矩形
cv.rectangle(fgmask, (rect[0], rect[1]), (rect[0] + rect[2], rect[1] + rect[3]), (0xff, 0xff, 0xff), 1) # 黑白前景上绘制矩形
y = 10 if rect[1] < 10 else rect[1] # 防止编号到图片之外
cv.putText(img, str(count), (rect[0], y), cv.FONT_HERSHEY_COMPLEX, 0.4, (0, 255, 0), 1) # 在前景上写上编号
cv.putText(img, "count:", (5, 20), cv.FONT_HERSHEY_COMPLEX, 0.6, (0, 255, 0), 1) # 显示总数
cv.putText(img, str(count), (75, 20), cv.FONT_HERSHEY_COMPLEX, 0.6, (0, 255, 0), 1)
# 勾画前景后展示图像
# cv2.imshow("bitwise_and",fgmask)
cv.imshow("findcont", img)
#脸部检测,缩放系数1.1,4轮确定
faces = face_cascade.detectMultiScale(gray, 1.1, 10, 0, (100, 100))
faces_bw = face_cascade.detectMultiScale(black_and_white, 1.1, 10, 0, (100, 100))
if len(faces) == 0 and len(faces_bw) == 0:
cv.putText(img, "No face found...", org, font, font_scale, noface, thickness, cv.LINE_AA)
elif len(faces) == 0 and len(faces_bw) == 1:
cv.putText(img, weared_mask, org, font, font_scale, weared_mask_font_color, thickness, cv.LINE_AA)
else:
for (x, y, w, h) in faces:
cv.rectangle(img, (x, y), (x + w, y + h), (255, 255, 255), 2)
roi_gray = gray[y:y + h, x:x + w]
roi_color = img[y:y + h, x:x + w]
##检测到鼻子并标记
nose_rects = nose_cascade.detectMultiScale(gray,1.05,4,0,(50,50),(100,100))
for (nx,ny,nw,nh) in nose_rects:
cv.line(img,(int(nx+nw/2),ny),(int(nx+nw/2),int(ny+nh/2)),(0,255,0),2)
##检测到眼睛并标记
eyes_rects = eye_cascade.detectMultiScale(gray, 1.1, 3, 0, (50, 50),(100,100))
for (ex,ey,ew,eh) in eyes_rects:
cv.rectangle(img,(int(ex+ew/4),int(ey+eh/4)),(int(ex+ew*0.8),int(ey+eh*0.8)),(255,0,0),2)
##检测到嘴巴并标记
mouth_rects = mouth_cascade.detectMultiScale(gray,1.1,5,0,(80,80),(100,120))
for (mx,my,mw,mh) in mouth_rects:
cv.ellipse(img, (int(mx+mw/2), int(my+mh/2.5)), (int(mw/3),int(mh/3)),0,0,360,(0,0,255),1)
##没有检测到鼻子也没有检测到嘴巴,则是戴好了口罩
if (len(nose_rects) == 0 and len(mouth_rects)==0):
cv.putText(img, weared_mask, org, font, font_scale, weared_mask_font_color, thickness, cv.LINE_AA)
##只遮住了嘴巴或者只遮住了鼻子则是错误佩戴口罩
elif ((len(nose_rects) == 1 and len(mouth_rects)==0)or(len(nose_rects) == 0 and len(mouth_rects)==1)):
cv.putText(img, error_weared_mask, org, font, font_scale, error_weared_mask_font_color, thickness, cv.LINE_AA)
##其它情况认为没有佩戴口罩(检测到鼻子也检测到了嘴巴)
else:
for (mx, my, mw, mh) in nose_rects:
##对于鼻子在检测到人脸上,在多个人脸的情况
if (y < my < y + h):
cv.putText(img, not_weared_mask, org, font, font_scale, not_weared_mask_font_color, thickness,
cv.LINE_AA)
break
cv.imshow('Mask Detection', img)
k = cv.waitKey(30) & 0xff
if k == 27:
break
cap.release()
cv.destroyAllWindows()
六、效果说明与总结
由于这个并不是采用深度学习的方法做的,在此强调!!!效果一般般!!!该方案适用于学习使用,对于现实中或者精度速度要求较高的,采用深度学习方法更好。演示图片设及本人,咱就不上传啦~
PS:后继有时间再选用其它视频检测后再更新展示图片~。
最后欢迎交流,good good study,day day up !
参考文献:
[1] https://blog.csdn.net/allway2/article/details/120831486
[2] https://blog.csdn.net/weixin_45224869/article/details/105275628
[3] https://blog.csdn.net/qq_42807924/article/details/104116361
[4] https://blog.csdn.net/weixin_44255592/article/details/89505450
[5] https://blog.csdn.net/drippingstone/article/details/116186462
[6] https://blog.csdn.net/qq_22527639/article/details/81501565
[7] https://blog.csdn.net/xss20072754/article/details/110147675
[8] https://blog.csdn.net(广大博友)