本项目主要由包含我在内的四名成员共同完成:孙明喆、吴震、张晨、张明
项目介绍,及可执行文件、模型文件、详细报告均在GitHub中可以查看:
GitHub
关于项目的详细介绍,可能过些日子在复习时候会有所补充
2020年2月28日更新
文章目录
一、引言
人脸表情是人们之间非语言交流时的最丰富的资源和最容易表达人们感情的一种有效方式,在人们的交流中起着非常重要的作用。表情含有丰富的人体行为信息,是情感的主载体,通过脸部表情能够表达人的微妙的情绪反应以及人类对应的心理状态,由此可见表情信息在人与人之间交流中的重要性。人脸表情识别技术随着人们对表情信息的日益重视而受到关注,成为目前-个研究的热点。所谓人脸表情识别,就是利用计算机进行人脸表情图像获取、表情图像预处理、表情特征提取和表情分类的过程,它通过计算机分析人的表情信息,从而推断人的心理状态,最后达到实现人机之间的智能交互。表情识别技术是情感计算机研究的内容之一,是心理学、生理学、计算机视觉、生物特征识别、情感计算、人工心理理论等多学科交叉的一个极富挑战性的课题,它的研究对于自然和谐的人机交互、远程教育、安全驾:驶等都有重要的作用和意义。
二、关键技术
2.1 基于几何特征提取的方法
2.1.1 原理介绍
基于几何特征的表情识别是指对嘴、眉毛、鼻子、眼睛等这些人脸表情的显著特征的形状和位置变化进行定位和测量,确定它的形状、大小、距离及相互比例,进行表情识别的方法。Bourel 等人定义了面部特征点之间的九个距离并通过它们构建了表情特征向量进行表情分析。Chibelushi 等人也采用了面部几何特征点并采用Kanade-Tucas-Tomasi 特征点跟踪算法实现特征点跟踪,然后通过计算得到九个特征系数,而这九个系数构成了特征流,描述了由于表情的发生而引起的面部特征点的几何关系的变化。Pantic 等人进行面部特征检测并确定面部几何关系,然后他们通过规则推理系统将这种面部几何关系转化为面部动作单元的活动,最终通过专家系统实现表情识别。Ying-li Tian等 人采用几何特征提取与神经网络相结合的方法对正面或接近正面的面部图像进行表情识别,其中提取几何特征主要包括对于关键部位的定位特征和表情区的形状特征。
2.1.2 实现方法
实例化一个 shape_predictor 对象,使用dlib训练好人脸特征检测器,进行人脸的特征点标定。标定的时候使用opencv的circle方法,在特征点的坐标上面添加水印,内容就是特征点的序号和位置。
2.2 机器学习的过程
机器学习方法是计算机利用已有的数据(经验),得出了某种模型(迟到的规律),并利用此模型预测未来(是否迟到)的一种方法。从广义上来说,机器学习是一种能够赋予机器学习的能力以此让它完成直接编程无法完成的功能的方法。但从实践的意义上来说,机器学习是一种通过利用数据,训练出模型,然后使用模型预测的一种方法。
2.3 分类器的选择及设计
2.2.1 KNN分类原理介绍
分类器采用KNN分类,K近邻算法(K-NN)算法是一种简单但也很常用的分类算法,它也可以应用于回归计算。K-NN是无参数学习,这意味着它不会对底层数据的分布做出任何假设。它是基于实例,即该算法没有显式地学习模型。相反,它选择的是记忆训练实例,并在一个有监督的学习环境中使用。KNN算法的实现过程主要包括距离计算方式的选择、k值得选取以及分类的决策规则三部分。
2.2.2 KNN分类的决策规则
常用的分类决策规则是取k个近邻训练数据中类别出现次数最多者作为输入新实例的类别。即首先确定前k个点所在类别的出现频率,对于离散分类,返回前k个点出现频率最多的类别作预测分类;对于回归则返回前k个点的加权值作为预测值。
2.2.3 Sklearn库实现KNN分类器的方法
划分相应的训练集与测试集,调用sklearn库中的sklearn.neighbors,#创建knn类 :knn = KNeighborsClassifier()
,#训练knn :Knn.fit(X_train,y_train)
三、设计方案及步骤
3.1总体方案设计
利用Dlib库进行人脸识别与特征标定,结合嘴、眉毛、鼻子、眼睛这些人脸表情的显著特征的形状和位置变化进行定位和测量,确定它的形状、大小、距离及相互比例,进行表情识别。利用sklearn库中机器学习的算法对特征值进行学习分类。利用cv2库进行摄像头视频的采集,以及标定点位置。利用pygame库实现音频的播放等。利用numpy库对数组进行操作。
3.2详细方案设计
3.2.1利用dlib进行人脸识别
# 使用特征提取器get_frontal_face_detector
detector = dlib.get_frontal_face_detector()
# dlib的68点模型,使用作者训练好的特征预测器
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
# 利用预测器预测
shape = predictor(img, d)
# 标出68个点的位置
for i in range(68):
cv2.circle(img, (shape.part(i).x, shape.part(i).y), 4, (0, 255, 0), -1, 8)
cv2.putText(img, str(i), (shape.part(i).x, shape.part(i).y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255))
3.2.2 数据集的采集与建立(特征提取)
本文选择的The Extended Cohn-Kanade Dataset(CK+)表情数据库。这个数据库包括123个subjects, 593 个 image sequence,每个image sequence的最后一张 Frame 都有action units 的label,而在这593个image sequence中,有327个sequence 有 emotion的 label。是数据库是人脸表情识别中比较流行的一个数据库。该人脸数据库包括七个表情类分别是0-中性、1-愤怒、2-蔑视、3-厌恶、4-恐惧、5-高兴、6-悲伤、7-惊讶,遍历每个表情类提取每个表情类每张图片的人脸特征值,。本文采用五维特征值分别是a-嘴巴宽度与识别框宽度之比、b-嘴巴高度与识别框高度之比、c-眉毛高度与识别框高度之比、d-眉毛距离与识别框宽度之比、e-眼睛睁开距离与识别框高度之比。将提取的五维特征值按表情类保存为xls表格,并为表情数据集打标签,以便后续训练分类识别模型使用。
#使用workbook方法,创建一个新的工作簿
book = xlwt.Workbook(encoding='utf-8', style_compression=0)
#添加一个sheet,名字为mysheet
sheet = book.add_sheet('mysheet', cell_overwrite_ok=True)
#遍历每张图片提取图片中人脸的特征值
for m in range ():
path =str(m)+'.jpg'
print(path)
im_rd = cv2.imread(path)
im_rd = cv2.resize(im_rd ,None,fx=2,fy=2,interpolation=cv2.INTER_CUBIC)
n = 0
k = cv2.waitKey(1)
img_gray = cv2.cvtColor(im_rd, cv2.COLOR_RGB2GRAY)
# 使用人脸检测器检测每一帧图像中的人脸。并返回人脸数rects
faces = self.detector(img_gray, 0)
# 待会要显示在屏幕上的字体
font = cv2.FONT_HERSHEY_SIMPLEX
# 如果检测到人脸
if(len(faces)!=0):
# 对每个人脸都标出68个特征点
for i in range(len(faces)):
# enumerate方法同时返回数据对象的索引和数据,k为索引,d为faces中的对象
for k, d in enumerate(faces):
# 用红色矩形框出人脸
cv2.rectangle(im_rd, (d.left(), d.top()), (d.right(), d.bottom()), (0, 0, 255))
# 计算人脸热别框边长
self.face_width = d.right() - d.left()
#self.face_hight = d.bottom() - d.top()
# 使用预测器得到68点数据的坐标
shape = self.predictor(im_rd, d)
# 圆圈显示每个特征点
for i in range(68):
cv2.circle(im_rd, (shape.part(i).x, shape.part(i).y), 2, (0, 255, 0), -1, 8)
self.face_high = (shape.part(8).y - shape.part(19).y + shape.part(8).y - shape.part(
18).y + shape.part(8).y - shape.part(24).y) / 3
# 分析任意n点的位置关系来作为表情识别的依据
mouth_width = (shape.part(54).x - shape.part(48).x) / self.face_width #嘴巴咧开程度
mouth_higth = (shape.part(66).y - shape.part(62).y) / self.face_high #嘴巴张开程度
sheet.write(m, n,str(mouth_width) )
n = n + 1
sheet.write(m, n, mouth_higth)
n = n + 1
brow_sum = 0 # 高度之和
frown_sum = 0 # 两边眉毛距离之和
for j in range(17, 21):
brow_sum += (shape.part(j).y - d.top()) + (shape.part(j + 5).y - d.top())
frown_sum += shape.part(j + 5).x - shape.part(j).x
line_brow_x.append(shape.part(j).x)
line_brow_y.append(shape.part(j).y)
tempx = np.array(line_brow_x)
tempy = np.array(line_brow_y)
z1 = np.polyfit(tempx, tempy, 1) # 拟合成一次直线
self.brow_k = -round(z1[0], 3)
brow_hight = (brow_sum / 10) / self.face_width # 眉毛高度占比
brow_width = (frown_sum / 5) / self.face_high # 眉毛距离占比
#人脸特征值之眉高值的读入
sheet.write(m, n, round(brow_hight,3))
n = n + 1
#人脸特征值之眉宽值的读入
sheet.write(m, n, round(brow_width,3))
n = n + 1
# 眼睛睁开程度
eye_sum = (shape.part(41).y - shape.part(37).y + shape.part(40).y - shape.part(38).y )
shape.part(47).y - shape.part(43).y + shape.part(46).y - shape.part(44).y)
eye_hight = (eye_sum / 4) / self.face_high
#人脸特征之眼高值的读入
sheet.write(m, n, round(eye_hight,3))
n = n + 1
#人脸特征值之眉毛倾斜程度的读入
#sheet.write(m,n,self.brow_k)
#保存xls工作簿
book.save()
3.2.3 模型的设计(knn与svm)
Scikit-learn也简称 sklearn, 是机器学习领域当中最知名的 python 模块之一,期中包含了许多集成的机器学习模型,在此次的课程设计中,我们采取了knn和svm两种分类器,通过给分类器喂相应的训练数据与标签,得到.m模型文件。
首先根据采集到的特征值,打上相应的标签:1,2,5,6,7,分别代表:生气、伤心、开心、正常、惊讶五种表情。再根据sklearn库的官方文档,训练knn和svm分类器。
这里只贴出KNN分类器主要部分代码,SVM分类器的可以学习着写,不难。
#读取数据集xlsx文件,利用pandas库根据列来分特征值和标签列
file_loc = ""
X = pd.read_excel(file_loc, index_col=None, na_values=['NA'], parse_cols = "A,B,C,D,E")
y = pd.read_excel(file_loc, index_col=None, na_values=['NA'], parse_cols = "G")
#给模型喂数据,调用sklearn中的集成好的分类器函数
model = sk_neighbors.KNeighborsClassifier(n_neighbors=5,n_jobs=1)
model.fit(X,y)
#评估模型,根据给定数据与标签返回正确率的均值
acc=model.score(X,y)
print('KNN模型(分类)评价:',acc)
#保存模型
joblib.dump(model, "")
根据最后的结果,可以发现,knn分类器的评估分值比svm的高,因此我们采用了knn分类器,在后来调用模型时候,我们分别采用了knn和svm训练后的模型,在识别过程中,识别效果跟模型的评估分值成正比关系。因此最后确定的是knn模型。
3.2.4 模型的调用与识别
先判断眼睛睁开距离与识别框高度之比来确定是否状态为tired。若是,则播放提示音提醒驾驶员;若不是,则再通过调用已经完成的KNN分类模型,进一步对摄像头捕捉到的人脸表情进行判定分类,若判定表情为愤怒(路怒状态),则播放提示音提示驾驶员注意心态。
具体实现代码:
import joblib
import cv2
import music
def face_pd(im_rd,a,b,c,d,dd,e):
#如果眼睛睁开距离与识别框高度之比小于0.2,则判定为闭眼,疲劳状态
if (e < 0.02):
cv2.putText(im_rd, "tired", (d.left(), d.bottom() + 20), cv2.FONT_HERSHEY_SIMPLEX, 0.8,
(0, 0, 255), 2, 4)
#播放疲劳提示音
music.runtired()
else:
cld = joblib.load(".m")
x = [[a, b, c, dd, e]]
# 存储特征值
# a-嘴巴宽度与识别框宽度之比
# b-嘴巴高度与识别框高度之比
# c-眉毛高度占比
# dd-眉毛距离占比
# e-眼睛睁开程度
kll = int(cld.predict(x))#用训练好的KNN模型对获取的特征值进行分析分类,返回分类后的标签
#根据标签判断当前表情,并在图像上输出
if (kll == 1):
cv2.putText(im_rd, "angry", (d.left(), d.bottom() + 20), cv2.FONT_HERSHEY_SIMPLEX, 0.8,
(0, 0, 255), 2, 4)
#检测愤怒表情
music.runangry()
elif (kll == 2):
cv2.putText(im_rd, "sadness", (d.left(), d.bottom() + 20), cv2.FONT_HERSHEY_SIMPLEX, 0.8,
(0, 0, 255), 2, 4)
elif (kll == 5):
cv2.putText(im_rd, "happy", (d.left(), d.bottom() + 20), cv2.FONT_HERSHEY_SIMPLEX, 0.8,
(0, 0, 255), 2, 4)
elif (kll == 6):
cv2.putText(im_rd, "normal", (d.left(), d.bottom() + 20), cv2.FONT_HERSHEY_SIMPLEX, 0.8,
(0, 0, 255), 2, 4)
elif (kll == 7):
cv2.putText(im_rd, "surprise", (d.left(), d.bottom() + 20), cv2.FONT_HERSHEY_SIMPLEX, 0.8,
(0, 0, 255), 2, 4)
四.结果与分析
结果不给予太多的内容,代码可以去Git中拉去
4.1模型训练结果比较
4.1.1 KNN模型
- 五维特征
- 六维特征
4.1.2 SVM模型
- 五维特征
- 六维特征