利用Python和OpenCV进行面部表情识别
简要介绍:
使用摄像头拍摄输入图像,并使用python中的opencv检测脸部,并尝试使用CNN深度学习概念和分类任务从获得的脸部图像中获取特征,提取的特征为给予像Logistic回归这样的分类器,SVM等和分类器将识别的表达式预测为输出。
入门..
为了解决这个问题,我们需要一个描述不同情绪的不同面孔的大型数据库。为此,您可以下载CK +数据库(http://www.consortium.ri.cmu.edu/ckagree/),或者您可以自己制作数据集,但要确保数据集大或多样化。
在这里,我使用我自己的数据库和数千个图像,并将它们组织在一个名为dataset的文件夹中,其中有7个不同的子文件夹(将成为您的7个课程),名为愤怒(anger),厌恶(disgust),恐惧(fear),快乐(happy),中立(neutral),悲伤(sad),惊喜(surprise)其中包含特定表达的图像。
为了完成这项任务,我们需要在每幅图像上找到人脸,将其转换为灰度图,剪裁并将图像保存到数据集中。我们可以使用OpenCV的HAAR过滤器自动进行人脸识别
cv2.namedWindow("preview")
vc = cv2.VideoCapture(0)
face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
facedict = {}
emotions =["anger","disgust","fear","happy","neutral","sad","surprise"]
#To crop face in an image
def crop_face(clahe_image, face):
for (x, y, w, h) in face:
faceslice = clahe_image[y:y+h, x:x+w]
faceslice = cv2.resize(faceslice, (350, 350))
facedict["face%s" %(len(facedict)+1)] = faceslice
return faceslice
def build_set(emotions):
check_folders(emotions)
for i in range(0, len(emotions)):
save_face(emotions[i])
print("Great,You are Done!" )
cv2.destroyWindow("preview")
cv2.destroyWindow("webcam")
#To check if folder exists, create if doesnt exists
def check_folders(emotions):
for x in emotions:
if os.path.exists("dataset\%s" %x):
pass
else:
os.makedirs("dataset\%s" %x)
#To save a face in a particular folder
def save_face(emotion):
print("please look " + emotion)
#To create timer to give time to read what emotion to express
for i in range(0,5):
print(5-i)
time.sleep(1)
#To grab 50 images for each emotion of each person
while len(facedict.keys()) < 51:
open_webcamframe()
#To save contents of dictionary to files
for x in facedict.keys():
cv2.imwrite("dataset_set\%s\%s.jpg" %(emotion, len(glob.glob("dataset\%s\*" %emotion))), facedict[x])
facedict.clear() #clear dictionary so that the next emotion can be stored
def open_webcamframe():
while True:
if vc.isOpened(): # try to get the first frame
rval, frame = vc.read()
else:
rval = False
cv2.imshow("preview", frame)
key = cv2.waitKey(40)
if key == 27: # exit on ESC
break
if key == 32:
#To convert image into grayscale
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
clahe_image = clahe.apply(gray)
#To run classifier on frame
face = face_cascade.detectMultiScale(clahe_image, scaleFactor=1.1, minNeighbors=15, minSize=(10, 10), flags=cv2.CASCADE_SCALE_IMAGE)
#To draw rectangle around detected faces
for (x, y, w, h) in face:
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 0, 255), 2) #draw it on "frame", (coordinates), (size), (RGB color), thickness 2
#Use simple check if one face is detected, or multiple (measurement error unless multiple persons on image)
if len(face) == 1:
faceslice = crop_face(clahe_image, face)
cv2.imshow("webcam", frame)
return faceslice#slice face from image
else:
print("no/multiple faces detected, passing over frame")
cv2.destroyWindow("preview")
cv2.destroyWindow("webcam")
build_set(emotions)
下一步是将数据集转换成矢量表示。使用VGG-16( 16层卷积神经网络)将图像转换为矢量。
对于一台普通电脑来说,培训一个拥有数百万图像或数据的大型中性网络需要大量的计算能力,而且非常昂贵,所以更好的想法是使用像VGG-16这样的预先训练好的模型,而不必掌握调整和训练那些已经用很多图像训练过的模型,因为我们可以直接使用获得的权重和体系结构,并将学习应用到我们的问题陈述上。这种方法也被称为转移学习。我们将预先训练的模型的“学习”转移到我们的具体问题陈述中。因此,加载预先训练的模型并使用该模型进行预测相对比较简单,并在此处进行描述。
img_width, img_height = 350, 350
top_model_weights_path = 'bottleneck_fc_model.h5'
train_data_dir = 'dataset'
nb_train_samples = 1011
epochs = 50
batch_size = 1
def save_bottlebeck_features():
#Function to compute VGG-16 CNN for image feature extraction.
train_target = []
datagen = ImageDataGenerator(rescale=1. / 255)
# build the VGG16 network
model = applications.VGG16(include_top=False,weights='imagenet')
generator_train = datagen.flow_from_directory(
train_data_dir,
target_size=(img_width, img_height),
batch_size=batch_size,
class_mode=None,
shuffle=False)
for i in generator_train.filenames:
train_target.append(i[:])
bottleneck_features_train = model.predict_generator(generator_train, nb_train_samples // batch_size)
bottleneck_features_train = bottleneck_features_train.reshape(1011,51200)
np.save(open('data_features.npy', 'wb'), bottleneck_features_train)
np.save(open('data_labels.npy', 'wb'), np.array(train_target))
save_bottlebeck_features()
培训和测试数据
从图像中获取特征后,为了构建分类模型,下一个重要任务是将整个数据集分割为训练,交叉验证和测试集。
分割数据集的原因 - 评估分类器在与训练集相同的集合上的性能是不公平的或差的练习,因为我们对分类器记忆训练集的程度不感兴趣。相反,我们感兴趣的是分类器如何将其识别能力推广到看不见的数据。
我们使用训练集来训练分类器以识别待预测类,并使用测试集来估计分类器性能。但是,那么交叉验证的用途是什么? 它主要用于目标是预测的问题,并且人们想要计算预测模型在实际操作中的准确度。
交叉验证的目的是定义一个数据集以在训练阶段(即验证集合)“测试”模型,以限制过度拟合和不足拟合等问题,并深入了解模型如何推广到未知的数据集。
因此,我们使用python中的sklearn的train_test_split方法将64%的数据集随机分为训练数据,16%的交叉验证集和20%的测试集。你可以看到下面三组的分布:
在这里,你可以看到我编码不同的表达式(或类标签)作为从0到6的数值,因为每个数字代表不同的类别(“anger”: 0, “disgust”: 1, “fear”: 2, “happy”: 3, “neutral”: 4, “sad”: 5, “surprise”:6 ).
训练机器学习算法
在开始训练任何机器学习分类算法之前,首先让我们看看一些约束条件和性能指标:
约束:
低延迟要求。
可解释性并不那么重要。
错误不能很昂贵。
属于每个类的数据点的可能性是必需的。
性能指标):
Multi class log-loss
混淆和精确矩阵。
基本上,对数损失的范围是[0,inf],我们的机器学习模型的目标是使这个值最小化。一个完美的模型的对数损失为0.对数损失的值随着预测的概率偏离实际标签而增加,但对于除0以外的某个对数损失值,我们可以量化我们模型的表现吗?
我们有一种方法,我们通过从7中随机地预测测试集中每个数据点的类别标签来计算对数损失。通过这样做,我们可以检查我们的模型与随机模型相比预测的好坏程度,因为对于您要构建的每个分类模型,您将得到的对数损失应小于使用随机预测的结果。
使用随机模型:
Log loss on Cross Validation Data using Random Model 2.20641012297
Log loss on Test Data using Random Model 2.38831681057
在混乱矩阵 -
如果x =cell (i,j)表示类别i的x个点数被预测为类别j。
在精密矩阵中 -
如果x = cell(i,j)表示类别i的x%点数被预测为类别j。
正如你在这里可以看到的那样,使用随机模型(即随机分配类标签随机预测)log-loss〜2.3。因此,通过这个,我们可以比较我们的模型在使用其他ML模型(如Logistic回归,SVM或其他)时的好坏。
I.使用Logistic回归:
The train log loss is: 0.768237309949
The cross validation log loss is: 1.05564794889
The test log loss is: 0.971378253018
II 使用KNN:
The train log loss is: 1.13836352362
The cross validation log loss is: 1.23858168969
The test log loss is: 1.19100240199
III。使用支持向量机(SVM):
The train log loss is: 0.7118609702
The cross validation log loss is: 1.05977434469
The test log loss is: 0.921972157437
将模型应用于实时!
正如您可以看到所有模型中的结果一样,逻辑回归比所有其他算法都更适合我们的问题约束,所以我们更愿意在我们的实时中使用LR并获得如下所示的结果: