计算机视觉opencv之人脸识别3(表情识别&疲劳监测)
一、表情识别
需要用到的模块
import numpy as np
import dlib
import cv2
from sklearn.metrics.pairwise import euclidean_distances
from PIL import Image, ImageDraw, ImageFont
1.三个函数
1)第一个函数MAR用来计算嘴的宽高比,也是利用人脸68个关键点进行操作的
A是上唇上中点到下唇下中点;B是上唇中左点到下唇中点;C是上唇中右点到下唇下左点;D是左嘴角到右嘴角(嘴宽度)
最后返回的((A+B+C)/3)/D就是嘴张开的比例
2)MJR函数用来计算嘴颊比,M是嘴宽,也就是上面的D。J是脸颊宽。返回值是M/J
3)第三个函数,之前我们也有用过,就是使得最后输出支持中文,不做叙述。
def MAR(shape): # 计算嘴的宽高比
# x=shape[50]
# y=shape[50].reshape(1, 2)
A = euclidean_distances(shape[50].reshape(1, 2),shape[58].reshape(1, 2))
B = euclidean_distances(shape[51].reshape(1, 2),shape[57].reshape(1, 2))
C = euclidean_distances(shape[52].reshape(1, 2),shape[56].reshape(1, 2))
D = euclidean_distances(shape[48].reshape(1, 2),shape[54].reshape(1, 2))
return ((A+B+C)/3)/D
def MJR(shape): #计算嘴宽度、脸颊宽度的比值
M = euclidean_distances(shape[48].reshape(1, 2), shape[54].reshape(1, 2)) #嘴宽度
J = euclidean_distances(shape[3].reshape(1, 2), shape[13].reshape(1, 2)) #下颌的宽度
return M/J
def cv2AddChineseText(img, text, position, textColor=(0, 255, 0), textSize=30):
""" 向图片中添加中文 """
if (isinstance(img, np.ndarray)): # 判断是否OpenCV图片类型
img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))#实现array到image的转换
draw = ImageDraw.Draw(img)# 在img图片上创建一个绘图的对象
# 字体的格式
fontStyle = ImageFont.truetype("simsun.ttc", textSize, encoding="utf-8")
draw.text(position, text, textColor, font=fontStyle) # 绘制文本
return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)# 转换回OpenCV格式
2.主程序
表情识别也是基于人脸关键68个点进行的,首先我们要构造脸部检测器然后读取人脸关键点定位的模型。
faces=detector(frame,0)就是用来捕捉摄像头中所有人脸的。faces是一个列表,包含多个人脸框,每个face是一个人脸的矩形坐标
对摄像头/视频进行表情检测获取到关键点之后,要把关键点用numpy库转化为坐标形式。获取到人脸关键点的第一个shape数据类型是dlib的full_object_detection对象。这个数据类型的结构中有三个:shape.parts返回68个点的列表,每个点是dlib.poin;shape.part(i)获取第i个点(从0到67);shape.rect,人脸检测框。我们要把原来这种获取不是坐标的点转化为坐标。
然后使用上面我们定义的函数。自行设置大笑和微笑的阈值
detector = dlib.get_frontal_face_detector() # 构造脸部位置检测器
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")#读取人脸关键点定位模型
cap=cv2.VideoCapture(0)
while True:
ret,frame = cap.read()
faces = detector(frame, 0) # 获取图片中全部人脸位置
for face in faces:
shape = predictor(frame, face) #获取关键点
# 将关键点转换为坐标(x,y)的形式
shape = np.array([[p.x, p.y] for p in shape.parts()])
mar = MAR(shape) #计算嘴部的高宽比
mjr = MJR(shape) #计算“嘴宽/脸颊宽”
result="正常" #默认是正常表情
print("mar",mar," mjr",mjr) #测试一下实际值,可以根据该值确定
if mar >0.5: #可更具项目要求调整阈值。
result="大笑"
elif mjr<0.45 : # 超过阈值为微笑
result="微笑"
# frame = cv2AddChineseText(frame, result, (50, 100))#输出中文
# cv2.putText()#输出英文
mouthHull = cv2.convexHull(shape[48:61]) # 嘴型构造凸包
frame = cv2AddChineseText(frame, result, mouthHull[0,0]) #在嘴上表上result检测结果
cv2.drawContours(frame, [mouthHull], -1, (0, 255, 0), 1)#画嘴上凸包
cv2.imshow("Frame", frame)
if cv2.waitKey(1) == 27:
break
cv2.destroyAllWindows()
cap.release()
二、疲劳监测
疲劳检测,可用于驾驶员监控、学员上课状态检测等。
疲劳监测也是基于人脸68个关键点进行的,这里是对眼睛进行操作。
import numpy as np
import dlib
import cv2
from sklearn.metrics.pairwise import euclidean_distances #计算欧氏距离
from PIL import Image, ImageDraw, ImageFont
1.三个函数
1)第一个函数,眼睛纵横比计算。A是p1到p5的垂直距离,上眼皮中点到下眼皮中点;B是p2到p4的垂直距离,上眼皮中点到下眼皮中点;C是p0到p3的水平距离,眼宽。最后返回ear是眼高均值/眼宽,ear值越小,说明眼睛闭的越紧
2)第二个依旧是支持中文显示
3)第三个函数就是用来绘制眼睛凸包
def eye_aspect_ratio(eye):
"""---------计算眼睛纵横比--------------"""
# 1 2
# 0 3 <----这是眼睛的6个关键点
# 5 4
#---------------------------------------------
A = euclidean_distances(eye[1].reshape(1,2), eye[5].reshape(1,2))
B = euclidean_distances(eye[2].reshape(1,2), eye[4].reshape(1,2))
C = euclidean_distances(eye[0].reshape(1,2), eye[3].reshape(1,2))
ear = ((A + B) /2.0) / C#纵横比
return ear
def cv2AddChineseText(img, text, position, textColor=(255, 0, 0), textSize=50):
""" 向图片中添加中文 """
if (isinstance(img, np.ndarray)): # 判断是否OpenCV图片类型
img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))#实现array到image的转换
draw = ImageDraw.Draw(img)# 在img图片上创建一个绘图的对象
# 字体的格式
fontStyle = ImageFont.truetype("simsun.ttc", textSize, encoding="utf-8")
draw.text(position, text, textColor, font=fontStyle) # 绘制文本
return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)# 转换回OpenCV格式
def drawEye(eye):#绘制眼框凸包
eyeHull = cv2.convexHull(eye)
cv2.drawContours(frame, [eyeHull], -1, (0, 255, 0), 1)
2.主程序
脸部检测器和关键点检测模型。connter用来统计闭眼次数,和上面一样,需要把关键点转化为坐标。本来每个关键点都有个编号,转化为坐标之后,每个关键点就对应了一个坐标,所以这里我们用shape[36:42]这种形式表示。我们使用了第一个函数,shape就相当于函数要传入的eye,所以函数中的eye[1]就表示列表中第二个坐标。坐标是一维数据[x1,y1],reshape就是把一维转化为二维数据[[x1,y1]],这是因为euclidean_distances()要求二位数组输入。
利用第一个函数分别计算出左眼和右眼的纵横比,最后计算左眼和右眼的平均纵横比值。
自定设置阈值,小于0.3认为闭眼。如果持续五十帧都检测到闭眼就警告。一次警告之后闭眼次数就清零,重新计算。
COUNTER = 0 # 闭眼持续次数统计
detector = dlib.get_frontal_face_detector()# 构造脸部位置检测器
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")#读取人脸关键点定位模型
cap = cv2.VideoCapture(0)
while True:
ret,frame = cap.read()
faces = detector(frame, 0) # 获取人脸
for face in faces: # 循环遍历每一个人脸
shape = predictor(frame, face) #获取关键点
# 将关键点转换为坐标(x,y)的形式
shape = np.array([[p.x, p.y] for p in shape.parts()])
rightEye = shape[36:42] # 右眼,关键点索引从36到41(不包含42)
leftEye = shape[42:48] # 左眼,关键点索引从42到47(不包含48)
rightEAR = eye_aspect_ratio(rightEye)# 计算右眼纵横比
leftEAR = eye_aspect_ratio(leftEye)# 计算左眼纵横比
ear = (leftEAR + rightEAR) / 2.0# 均值处理
if ear <0.3: #小于0.3认为闭眼,也可能是眨眼
COUNTER += 1 # 每检测到一次,将+1
if COUNTER >= 50: #持续50帧都闭眼,则警报
frame = cv2AddChineseText(frame, "!!!!危险!!!!", (250,250))
# 宽高比<0.3,则计数器清零、解除疲劳标志
else:
COUNTER = 0 #闭眼次数清零
drawEye(leftEye) #绘制左眼凸包
drawEye(rightEye) #绘制右眼凸包
info = "EAR: {:.2f}".format(ear[0][0])
frame = cv2AddChineseText(frame, info, (0,30))#显示眼睛闭合程度值
cv2.imshow("Frame", frame)
if cv2.waitKey(1) == 27:
break
cv2.destroyAllWindows()
cap.release()









