【项目背景】
上班族因工作量大、上网课的学生因学业繁重,长期久坐在电脑前,容易疲劳瞌睡。很多人在电脑前打瞌睡,既没有休息好,也没有工作、学习好。所以本人制作了一个“瞌睡AI掌控提醒器”,提醒去休息或提神。
【功能简介】
本项目采用Python语言利用人脸识别库,获取人眼坐标点(两眼共取12个坐标点),采用EAR开源算法判断是否瞌睡,通过Pinpong库连接掌控板,发提示音、闪灯、文字提示,并控制舵机进行“喷雾”提神。
【硬件连接】
注:因舵机工作时的电流较大,所以单独供电,但要注意,电源与掌控板要“共地”。
【软件程序】
一、测试舵机
要不断测试和调整,确保舵机转动时让装置“喷雾”。
代码如下:
# -*- coding: utf-8 -*-
import time
from pinpong.board import Board,Pin,Servo
Board("handpy").begin() #初始化,选择板型和端口号,不输入端口号则进行自动识别
#Board("handpy","COM36").begin() #windows下指定端口初始化
#Board("handpy","/dev/ttyACM0").begin() #linux下指定端口初始化
#Board("handpy","/dev/cu.usbmodem14101").begin() #mac下指定端口初始化
s1 = Servo(Pin(Pin.P0)) #将Pin传入Servo中初始化舵机引脚
while True:
s1.angle(30) #控制舵机转到0度位置
print("30")
time.sleep(0.5)
s1.angle(160) #控制舵机转到90度位置
print("160")
time.sleep(1)
《喷雾测试视频》
二、测试掌控板显示屏
显示屏显示“请去休息”
代码如下:
# -*- coding: utf-8 -*-
import time
from pinpong.board import Board
from pinpong.extension.handpy import *
Board("handpy").begin()#初始化,选择板型和端口号,不输入端口号则进行自动识别
#Board("handpy","COM36").begin() #windows下指定端口初始化
#Board("handpy","/dev/ttyACM0").begin() #linux下指定端口初始化
#Board("handpy","/dev/cu.usbmodem14101").begin() #mac下指定端口初始化
oled.DispChar("请去休息", 42, 22)
oled.show()
while True:
pass
三、测试“RGB LED”
使用随机函数random,生成0-255的随机数,让三个LED灯颜色不断变化。
代码如下:
# -*- coding: utf-8 -*-
import time
from pinpong.board import Board
from pinpong.extension.handpy import *
import random
Board("handpy").begin()#初始化,选择板型和端口号,不输入端口号则进行自动识别
#Board("handpy","COM36").begin() #windows下指定端口初始化
#Board("handpy","/dev/ttyACM0").begin() #linux下指定端口初始化
#Board("handpy","/dev/cu.usbmodem14101").begin() #mac下指定端口初始化
#rgb.disable(-1) #关闭LED灯,-1代表3个灯(可以填灯号0,1,2)
rgb.brightness(7) #设置LED灯的亮度,范围0-9
while True:
rgb[0] = (int(random.random()*255)+1, int(random.random()*255)+1, int(random.random()*255)+1)
rgb[1] = (int(random.random()*255)+1, int(random.random()*255)+1, int(random.random()*255)+1)
rgb[2] = (int(random.random()*255)+1, int(random.random()*255)+1, int(random.random()*255)+1)
rgb.write()
time.sleep(1)
四、测试掌控板播放音乐
# -*- coding: utf-8 -*-
import time
from pinpong.board import Board
from pinpong.extension.handpy import *
Board("handpy").begin()#初始化,选择板型和端口号,不输入端口号则进行自动识别
#Board("handpy","COM36").begin() #windows下指定端口初始化
#Board("handpy","/dev/ttyACM0").begin() #linux下指定端口初始化
#Board("handpy","/dev/cu.usbmodem14101").begin() #mac下指定端口初始化
tune = ["C4:4", "D4:4", "E4:4", "C4:4", "C4:4", "D4:4", "E4:4", "C4:4",
"E4:4", "F4:4", "G4:4", "E4:4", "F4:4", "G4:4"]
music.play(tune)
#music.set_tempo(4,60)
#设置每一拍等同于4分音符,每分钟节拍数
#music.play("C4:4")
#time.sleep(2)
#music.stop()
#停止后台播放
while True:
pass
五、获取人脸坐标
1、face_recognition人脸识别库
face_recognition使用世界上最简单的人脸识别库,在Python或命令行中识别和操作人脸。 使用dlib最先进的人脸识别技术构建而成,并具有深度学习功能。
2、安装:
pip install face_recognition
3、识别人脸关键点
加载图像后,调用face_recognition.face_landmarks(image)可识别出人脸关键点信息,包括眼睛、鼻子、嘴巴和下巴等,参数仍是加载的图像image,返回值是包含面部特征字典的列表,列表中每一项对应一张人脸,包括nose_bridge、right_eyebrow、right_eye、chine、left_eyebrow、bottom_lip、nose_tip、top_lip、left_eye几个部分,每个部分包含若干个特征点(x,y),总共有68个特征点。列表长度就是图中识别出的人脸数,可遍历此列表和字典的键值对,打印出所有面部特征点,也可在图像上画出来.
4、代码如下:
import numpy as np
import cv2
import face_recognition
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
frame = cv2.resize(frame, (0,0), fx=1, fy=1)
# Find all facial features in all the faces in the video
face_landmarks_list = face_recognition.face_landmarks(frame)
for face_landmarks in face_landmarks_list:
# Loop over each facial feature (eye, nose, mouth, lips, etc)
for name, list_of_points in face_landmarks.items():
hull = np.array(face_landmarks[name])
hull_landmark = cv2.convexHull(hull)
cv2.drawContours(frame, hull_landmark, -1, (0, 255, 0), 3)
cv2.imshow("Frame", frame)
ch = cv2.waitKey(1)
if ch & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
六、EAR开源算法判断瞌睡
人眼疲倦检测开源算法EAR(eye aspect ratio)计算函数
我们首先需要确定眼睛的位置,在确定眼睛位置之后,选择6个点来表示眼睛,具体如下图所示:
标号的顺序是从眼睛的左角开始,然后顺时针绕着眼睛进行编号。
根据这六个点我们便可以表示眼睛的睁开和闭上的状态。当开启的时候,上图中竖着的黄色箭头会变得比较高,而眼睛闭上(疲劳状态)这个箭头就会变矮。但是由于观看的距离不同,单纯用高度来表示状态缺少参考比较,因此提出如下公式表示状态:
用这个数据便可以相对客观的表示眼睛的状态,于是通过大量测试发现一个统计结果,当EAR小于0.25很多的时候,便是疲劳状态。
七、提取两眼坐标
每只眼睛获取六个坐标点,并进行标注
代码如下:
import numpy as np
import cv2
import face_recognition
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
frame = cv2.resize(frame, (0,0), fx=1, fy=1)
# Find all facial features in all the faces in the video
face_landmarks_list = face_recognition.face_landmarks(frame)
for face_landmarks in face_landmarks_list:
# Loop over each facial feature (eye, nose, mouth, lips, etc)
for name, list_of_points in face_landmarks.items():
if name=='left_eye':
print(list_of_points)
hull = np.array(face_landmarks[name])
hull_landmark = cv2.convexHull(hull)
cv2.drawContours(frame, hull_landmark, -1, (0, 255, 0), 3)
font = cv2.FONT_HERSHEY_SIMPLEX # 获取内置字体
k=1
for points in list_of_points:
cv2.putText(frame,str(k), points, font, 0.5, (255,0,255), 4) # 调用函数,对人脸坐标位置,
k=k+1
if name=='right_eye':
print(list_of_points)
hull = np.array(face_landmarks[name])
hull_landmark = cv2.convexHull(hull)
cv2.drawContours(frame, hull_landmark, -1, (0, 255, 0), 3)
font = cv2.FONT_HERSHEY_SIMPLEX # 获取内置字体
k=1
for points in list_of_points:
cv2.putText(frame,str(k), points, font, 0.5, (255,0,255), 4) # 调用函数,对人脸坐标位置,
k=k+1
cv2.imshow("Frame", frame)
ch = cv2.waitKey(1)
if ch & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
八、眼睛纵横比(EAR)函数
# 这个方程的分子是计算垂直眼睛标志之间的距离,而分母是计算水平眼睛标志之间的距离,由于水平点只有一组,而两组垂直点,所以分母乘上了2,以保证两组特征点的权重相同。使用这个简单的方程,我们可以避免使用图像处理技术,简单地依靠眼睛地标距离的比例来确定一个人是否眨眼。
def eye_aspect_ratio(eye):
# 计算距离,竖直的
A = dist.euclidean(eye[1], eye[2])
B = dist.euclidean(eye[4], eye[5])
# 计算距离,水平的
C = dist.euclidean(eye[0], eye[3])
# ear值
ear = (A + B) / (2.0 * C)
return ear
九、判断瞌睡代码
import numpy as np
import cv2
import face_recognition
from scipy.spatial import distance as dist
cap = cv2.VideoCapture(0)
# 眼睛纵横比(EAR)
# 这个方程的分子是计算垂直眼睛标志之间的距离,而分母是计算水平眼睛标志之间的距离,由于水平点只有一组,而两组垂直点,所以分母乘上了2,以保证两组特征点的权重相同。使用这个简单的方程,我们可以避免使用图像处理技术,简单地依靠眼睛地标距离的比例来确定一个人是否眨眼。
def eye_aspect_ratio(eye):
#计算距离,竖直的
A=dist.euclidean(eye[1],eye[5])
B=dist.euclidean(eye[2],eye[4])
#计算距离,水平的
C=dist.euclidean(eye[0],eye[3])
#ear值
ear=(A+B)/(2.0*C)
return ear
close_eye=0 # 闭眼计数
ClOSE_EYE=25 # 闭眼次数阈值
EYE_EAR = 0.25 # EAR阈值
while True:
ret, frame = cap.read()
frame = cv2.resize(frame, (0,0), fx=0.5, fy=0.5)
# Find all facial features in all the faces in the video
face_landmarks_list = face_recognition.face_landmarks(frame)
left_eye_points=[]
right_eye_points=[]
for face_landmarks in face_landmarks_list:
# Loop over each facial feature (eye, nose, mouth, lips, etc)
for name, list_of_points in face_landmarks.items():
if name=='left_eye':
left_eye_points=list_of_points
hull = np.array(face_landmarks[name])
hull_landmark = cv2.convexHull(hull)
cv2.drawContours(frame, hull_landmark, -1, (0, 255, 0), 3)
if name=='right_eye':
right_eye_points=list_of_points
hull = np.array(face_landmarks[name])
hull_landmark = cv2.convexHull(hull)
cv2.drawContours(frame, hull_landmark, -1, (0, 255, 0), 3)
if len(right_eye_points)!=0 and len(left_eye_points)!=0:
# 分别计算两眼ear值
leftEAR = eye_aspect_ratio(left_eye_points)
rightEAR = eye_aspect_ratio(right_eye_points)
# 算一个平均的ear值
ear = (leftEAR + rightEAR) / 2.0
# 当“ear”小于阈值时,为闭眼一次
if ear<EYE_EAR:
close_eye=close_eye+1
else:
close_eye=0
if close_eye>ClOSE_EYE:
cv2.putText(frame,"Sleep!",(20,200),cv2.FONT_HERSHEY_SIMPLEX,2,(0,0,255),3)
cv2.putText(frame,"close_eye:"+str(close_eye),(30,30),cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),1)
print(ear)
cv2.imshow("Frame", frame)
ch = cv2.waitKey(1)
if ch & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
十、完整代码
import numpy as np #导入科学计算库,CV2库需要
import cv2 #opencv-python库,用来控制摄像头,获取人脸图像
import face_recognition
from scipy.spatial import distance as dist
import time,random
from pinpong.board import Board,Pin,Servo
from pinpong.extension.handpy import *
Board("handpy").begin() #初始化,选择板型和端口号,不输入端口号则进行自动识别
s1 = Servo(Pin(Pin.P1)) #将Pin传入Servo中初始化舵机引脚
k=0
cap = cv2.VideoCapture(0)
# 眼睛纵横比(EAR)
# 这个方程的分子是计算垂直眼睛标志之间的距离,而分母是计算水平眼睛标志之间的距离,由于水平点只有一组,而两组垂直点,所以分母乘上了2,以保证两组特征点的权重相同。使用这个简单的方程,我们可以避免使用图像处理技术,简单地依靠眼睛地标距离的比例来确定一个人是否眨眼。
def eye_aspect_ratio(eye):
#计算距离,竖直的
A=dist.euclidean(eye[1],eye[5])
B=dist.euclidean(eye[2],eye[4])
#计算距离,水平的
C=dist.euclidean(eye[0],eye[3])
#ear值
ear=(A+B)/(2.0*C)
return ear
close_eye=0 # 闭眼计数
ClOSE_EYE=25 # 闭眼次数阈值
EYE_EAR = 0.28 # EAR阈值
while True:
ret, frame = cap.read()
frame = cv2.resize(frame, (0,0), fx=0.5, fy=0.5)
# Find all facial features in all the faces in the video
face_landmarks_list = face_recognition.face_landmarks(frame)
left_eye_points=[]
right_eye_points=[]
for face_landmarks in face_landmarks_list:
# Loop over each facial feature (eye, nose, mouth, lips, etc)
for name, list_of_points in face_landmarks.items():
if name=='left_eye':
left_eye_points=list_of_points
hull = np.array(face_landmarks[name])
hull_landmark = cv2.convexHull(hull)
cv2.drawContours(frame, hull_landmark, -1, (0, 255, 0), 3)
if name=='right_eye':
right_eye_points=list_of_points
hull = np.array(face_landmarks[name])
hull_landmark = cv2.convexHull(hull)
cv2.drawContours(frame, hull_landmark, -1, (0, 255, 0), 3)
if len(right_eye_points)!=0 and len(left_eye_points)!=0:
# 分别计算两眼ear值
leftEAR = eye_aspect_ratio(left_eye_points)
rightEAR = eye_aspect_ratio(right_eye_points)
# 算一个平均的ear值
ear = (leftEAR + rightEAR) / 2.0
# 当“ear”小于阈值时,为闭眼一次
if ear<EYE_EAR:
close_eye=close_eye+1
else:
close_eye=0
if close_eye>ClOSE_EYE:
cv2.putText(frame,"Sleep!",(20,200),cv2.FONT_HERSHEY_SIMPLEX,2,(0,0,255),3)
oled.DispChar("请去休息", 42, 22)
oled.show()
rgb[0] = (int(random.random()*255)+1, int(random.random()*255)+1, int(random.random()*255)+1)
rgb[1] = (int(random.random()*255)+1, int(random.random()*255)+1, int(random.random()*255)+1)
rgb[2] = (int(random.random()*255)+1, int(random.random()*255)+1, int(random.random()*255)+1)
rgb.write()
tune = ["C4:4", "D4:4", "E4:4"]
music.play(tune)
time.sleep(3)
music.stop()
s1.angle(30) #控制舵机转到0度位置
time.sleep(0.5)
s1.angle(160) #控制舵机转到90度位置
rgb.disable(-1) #关闭LED灯,-1代表3个灯(可以填灯号0,1,2)
oled.fill(0)
cv2.putText(frame,"close_eye:"+str(close_eye),(30,30),cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),1)
print(ear)
cv2.imshow("Frame", frame)
ch = cv2.waitKey(1)
if ch & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
【演示视频】