一、实验要求:
检测眨眼次数。要求做成一个pyqt5 GUI 界面,点击按键“开始”时,就开始显示视频帧,并检测图像中是否有人的眨眼动作,有眨眼时将眨眼次数加1,并显示在图像的左上角位置。点击按键“停止”时,就停止显示视频帧。
二、设计思路描述
为了构建我们的眨眼检测器,我们将计算一个称为 眼睛纵横比(EAR) 的指标。当确定视频流中是否发生眨眼时,我们需要计算眼睛的长宽比。如果眼睛长宽比低于一定的阈值,然后超过阈值,那么我们将记录一个“眨眼” - EYE_AR_THRESH 是这个阈值,我们默认它的值为 0.3,您也可以为自己的应用程序调整它。另外,我们有一个重要的常量,EYE_AR_CONSEC_FRAME ,这个值被设置为 3,表明眼睛长宽比小于3时,接着三个连续的帧一定发生眨眼动作。同样,取决于视频的帧处理吞吐率,数字大小可以自己调控。
- 利用opencv首先对每一帧进行人脸检测
- 检测眼睛的区域的六个特征点,计算EAR,这些点在眼睛开和闭时有所不同,可用来检测
- 设置阈值,比较EAR和阈值的大小,当EAR大小小于阈值时,判定接连若干帧一定发生眨眼动作,使计数器数量加一
三、关键代码
class Wink_Window(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Wink_Window, self).__init__(parent)
self.timer_camera = QtCore.QTimer()
self.cap = cv2.VideoCapture()
self.cam_num = 0
self.set_ui()
self.slot_init()
self.__flag_work = 0
self.x = 0
self.count = 0
# 调用人脸特征点检测模型
self.shape_detector_path = '../shape_predictor_68_face_landmarks.dat'
# 人脸检测器
self.detector = dlib.get_frontal_face_detector()
# 人脸特征点检测器
self.predictor = dlib.shape_predictor(self.shape_detector_path)
self.EYE_AR_THRESH = 0.35 # 设置阈值
# 当EAR大小小于阈值时,接连多少帧一定发生眨眼动作
self.EYE_AR_CONSEC_FRAMES = 8
# 对应特征点的序号
self.RIGHT_EYE_START = 37 - 1
self.RIGHT_EYE_END = 42 - 1
self.LEFT_EYE_START = 43 - 1
self.LEFT_EYE_END = 48 - 1
self.face =True
def set_ui(self):
self.__layout_main = QtWidgets.QHBoxLayout()
self.__layout_fun_button = QtWidgets.QVBoxLayout()
self.__layout_data_show = QtWidgets.QVBoxLayout()
self.button_open_camera = QtWidgets.QPushButton(u'开始检测')
self.button_close = QtWidgets.QPushButton(u'停止检测')
# Button 的颜色修改
button_color = [self.button_open_camera, self.button_close]
for i in range(2):
button_color[i].setStyleSheet("QPushButton{color:black}"
"QPushButton:hover{color:red}"
"QPushButton{background-color:rgb(78,255,255)}"
"QPushButton{border:2px}"
"QPushButton{border-radius:10px}"
"QPushButton{padding:2px 5px}")
# move()方法移动窗口在屏幕上的位置到x = 500,y = 300坐标。
self.move(500, 300)
# 信息显示
self.button_open_camera.setMinimumHeight(50)
self.button_close.setMinimumHeight(50)
self.label_show_camera = QtWidgets.QLabel()
self.label_move = QtWidgets.QLabel()
self.label_move.setFixedSize(100, 50)
self.label_show_camera.setFixedSize(641, 481)
self.label_show_camera.setAutoFillBackground(False)
self.__layout_fun_button.addWidget(self.button_open_camera)
self.__layout_fun_button.addWidget(self.button_close)
self.__layout_fun_button.addWidget(self.label_move)
self.__layout_main.addLayout(self.__layout_fun_button)
self.__layout_main.addWidget(self.label_show_camera)
self.setLayout(self.__layout_main)
self.label_move.raise_()
self.setWindowTitle(u'眨眼检测')
def slot_init(self):
self.button_open_camera.clicked.connect(self.button_open_camera_click)
self.timer_camera.timeout.connect(self.show_camera)
self.button_close.clicked.connect(self.close)
def button_open_camera_click(self):
if self.timer_camera.isActive() == False:
self.frame_counter = 0 # 连续帧计数
self.blink_counter = 0 # 眨眼计数
flag = self.cap.open(self.cam_num)
if flag == False: # 错误警告
msg = QtWidgets.QMessageBox.warning(self, u"⚠警告", u"请检测相机是否连接正确",
buttons=QtWidgets.QMessageBox.Ok,
defaultButton=QtWidgets.QMessageBox.Ok)
else:
self.timer_camera.start(30)
self.button_open_camera.setText(u'关闭摄像头')
else:
self.timer_camera.stop()
self.cap.release()
self.label_show_camera.clear()
self.button_open_camera.setText(u'打开摄像头')
def show_camera(self):
flag, self.image = self.cap.read()
if self.face:
self.detect_eyes()
self.show = cv2.resize(self.image, (640, 480))
self.show = cv2.cvtColor(self.show, cv2.COLOR_BGR2RGB)
showImage = QtGui.QImage(self.show.data, self.show.shape[1],
self.show.shape[0],
QtGui.QImage.Format_RGB888)
self.label_show_camera.setPixmap(QtGui.QPixmap.fromImage(showImage))
def detect_eyes(self): #寻找并绘出两眼轮廓
gray = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)
rects = self.detector(gray, 0) # 人脸检测
for rect in rects: # 遍历每一个人脸
shape = self.predictor(gray, rect) # 检测特征点
points = face_utils.shape_to_np(shape)
leftEye = points[self.LEFT_EYE_START:self.LEFT_EYE_END + 1] # 取出左眼对应的特征点
rightEye = points[self.RIGHT_EYE_START:self.RIGHT_EYE_END + 1] # 取出右眼对应的特征点
leftEAR = self.eye_aspect_ratio(leftEye) # 计算左眼EAR
rightEAR = self.eye_aspect_ratio(rightEye) # 计算右眼EAR
ear = (leftEAR + rightEAR) / 2.0 # 求左右眼EAR的均值
leftEyeHull = cv2.convexHull(leftEye) # 寻找左眼轮廓
rightEyeHull = cv2.convexHull(rightEye) # 寻找右眼轮廓
cv2.drawContours(self.image, [leftEyeHull], -1, (0, 255, 0), 1) # 绘制左眼轮廓
cv2.drawContours(self.image, [rightEyeHull], -1, (0, 255, 0), 1) # 绘制右眼轮廓
# 只有连续帧计数超过EYE_AR_CONSEC_FRAMES时才会计做一次眨眼
if ear < self.EYE_AR_THRESH:
self.frame_counter += 1
else:
if self.frame_counter >= self.EYE_AR_CONSEC_FRAMES:
self.blink_counter += 1
self.frame_counter = 0
# 在图像上显示出眨眼次数blink_counter
cv2.putText(self.image, "blink_counts:{0}".format(self.blink_counter), (250, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
def eye_aspect_ratio(self,eye):
A = distance.euclidean(eye[1], eye[5])
B = distance.euclidean(eye[2], eye[4])
C = distance.euclidean(eye[0], eye[3])
ear = (A + B) / (2.0 * C)
return ear
四、实验结果
开始运行:
五、经验和教训
当确定视频流中是否发生眨眼时,我们需要计算眼睛的长宽比。所以当检测眨眼的时候会受到很多外界因素的影响,例如光线、眨眼速度、摄像头帧率、清晰度等。
而在实验过程中遇到的第一个问题就是下载安装dlib:
报错:is not a supported wheel on this platform.
原因:安装的不是对应python版本的库
解决办法:在cmd后中输入
①python
②import pip
③print(pip.pep425tags.get_supported())
即可以获取到pip支持的文件名还有版本。在pip 安装中必须要确认自己的版本信息,对应好再安装。
注:检测时眨眼放慢点,那样效果会好一点。眼镜反光也可能会导致检测眼睛轮廓时出错,计算出的结果也会是错的。