瞌睡AI掌控提醒器

云天(宋秀双)
帖子创建于2021年08月29日 第二届掌控板教学应用设计大赛 676 次浏览 2 个赞 1 人关注

【项目背景】

上班族因工作量大、上网课的学生因学业繁重,长期久坐在电脑前,容易疲劳瞌睡。很多人在电脑前打瞌睡,既没有休息好,也没有工作、学习好。所以本人制作了一个“瞌睡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()

【演示视频】

疲劳AI提醒器演示视频

5 条评论

LP_OVER

2021年09月02日

太nice了

Mr.Kong

2021年09月03日

看了视频,这陀机力度还挺大的。。没看到摄像头,是配套电脑及摄像头使用吧?

LP_OVER

2021年09月03日

摄像头不见了?

有 1 条回复

长主月

2021年09月08日

创意不错,程序复杂,学习一下

小齿轮 汝州一中朱现伟

2021年09月14日

云天老师技术高手