实现从项目调研、数据收集、数据预处理、深度卷积神经网络训练再到服务器部署的人脸表情识别小项目
主要分为两个部分展开:图片检测和视频检测
检测部分分为两个方面:人脸检测和表情识别
先呈上一张效果图(果然,包包越贵越自信hhh): 效果图一、数据预处理
在数据预处理上主要有:
图像预处理一般有数字化,归一化,几何变化,平滑,复原和增强等步骤。
在检测边缘前,通常会进行灰度化处理,噪声处理,二值化,开闭运算等。
二、图片检测
首先,运行所需要的库:
import sys
import numpy as np
import dlib
import cv2
import os
以及所需要的检测68个关键点参数模型:shape_predictor_68_face_landmarks.dat
链接:https://pan.baidu.com/s/1Xkh17Wdmn9dfx33yA8ghRw
提取码:rxwh
人物表情检测参数模型(这个是我自己训练的,准确率有待参考~):my_merge_Xception_0807.61-0.669.hdf5
链接:https://pan.baidu.com/s/1zviLruPE3wG4BFQhNSJsyA
提取码:rg3d
当然你也可以运用自己训练好的模型参数。
2.1、检测人脸区域,并标上68个点,按区域连线
导入已经训练好的模型,以及相关变量,人脸表情方面检测主要有7种基本表情:"angry", "disgust", "scared", "happy", "sad", "surprised", "neutral";
image_file = "image/5.jpg"
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("face_landmarks/shape_predictor_68_face_landmarks.dat")
emotion_model_path = 'models/my_merge_Xception_0807.61-0.669.hdf5'
emotion_classifier = load_model(emotion_model_path, compile=False)
EMOTIONS = ["angry", "disgust", "scared", "happy", "sad", "surprised", "neutral"]
读取图片并显示:
image = cv2.imread(image_file)
cv2.imshow("origin", image)
处理部分,将图片进行resize处理:
#这个函数里的image就是我们要检测的图片。
#在人脸检测程序的最后,我们会显示检测的结果图片来验证,这里做resize是为了避免图片过大,超出屏幕范围。
def resize(image, width=1200):
r = width * 1.0 / image.shape[1]
dim = (width, int(image.shape[0] * r))
resized = cv2.resize(image, dim, interpolation=cv2.INTER_AREA)
return resized
调用人脸检测模型,在检测之前,作了一个图片灰度处理;
灰度处理原因:
1、最直白的原因是,彩色图像单个像素是(R, G, B),转换成灰度图就是(L),简化矩阵, 提高运算速度。2、首先,梯度信息对于识别物体来说很重要。所以我们可以把灰度图像看作图像的强度(Intensity),来求一些梯度特征。比较常用的有 HOG,LBP,SIFT等等。如下图为行人检测中的hog模型。通过hog来检测部件,最后找到图像中的行人。(Reference: rbgirshick/voc-dpm)注:可视化为rgb图像,但输入是灰度图像,不要被骗了。
现行很多算法是需要将彩色图像灰度化再进行处理的。其实你要想明白,灰度化之后失去了什么,又得到了什么。。
灰度化之后颜色信息丢失,很多color-based算法就不可能这么做,但是很多简单的识别算法对于颜色的依赖性不强,hand-craft特征更多关注边缘梯度信息。工程中很多应用加上color信息之后鲁棒性会下降。灰度化之后矩阵维数下降,运算速度大幅度提高,并且梯度信息仍然保留。就是在performance和efficiency之间做一个权衡罢了。
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
rects = detector(gray, 1)
接着检测问题说,当我检测出一张图片上所有人脸的相关信息(都存储在rects);这时我们可以根据关键点的得到相关人脸区域图片,便于后面表情识别。
首先,先来看看,如何将每张人脸的关键点标出来(本次是将68个点按区域连接起来的),在检测到的人脸区域中,进一步检测器官(眼睛、鼻子、嘴巴、下巴、眉毛)。
将相关区域进行划分;
face_outline = []
face_eye_left = []
face_eye_right = []
face_eyebrow_left = []
face_eyebrow_right = []
face_nosepiece = []
face_nose = []
face_mouth = []
face_lip = []
i = 0
for (x, y) in shape:
i = i + 1
# print(i, x, y)
if i >= 1 and i <= 17:
# print("face_outline")
face_outline.append((x, y))
if i >= 18 and i <= 22:
face_eyebrow_left.append((x, y))
if i >= 23 and i <= 27:
face_eyebrow_right.append((x, y))
if i >= 37 and i <= 42:
face_eye_left.append((x, y))
if i >= 43 and i <= 48:
face_eye_right.append((x, y))
if i >= 28 and i <= 31:
face_nosepiece.append((x, y))
if i >= 32 and i <= 36:
face_nose.append((x, y))
if i >= 49 and i <= 60:
face_mouth.append((x, y))
if i >= 61 and i <= 68:
face_lip.append((x, y))
进行关键点之间连线;
color = (225, 225, 225)
thickness = 1
def draw_line(image, pixls):
i = 0
# print(pixls)
for x, y in pixls:
if i < len(pixls)-1:
next_x, next_y = pixls[i+1]
cv2.line(image, (x, y), (next_x, next_y), color, thickness)
# else:
# next_x, next_y = pixls[0]
# cv2.line(image, (x, y), (next_x, next_y), (225, 225, 225), 2)
i = i + 1
draw_line(image, face_outline)
draw_line(image, face_eyebrow_left)
draw_line(image, face_eyebrow_right)
draw_line(image, face_eye_left)
cv2.line(image, face_eye_left[0], face_eye_left[-1], color, thickness)
draw_line(image, face_eye_right)
cv2.line(image, face_eye_right[0], face_eye_right[-1], color, thickness)
draw_line(image, face_nose)
draw_line(image, face_nosepiece)
cv2.line(image, face_nose[0], face_nosepiece[-1], color, thickness)
cv2.line(image, face_nose[-1], face_nosepiece[-1], color, thickness)
draw_line(image, face_mouth)
cv2.line(image, face_mouth[0], face_mouth[-1], color, thickness)
draw_line(image, face_lip)
cv2.line(image, face_lip[0], face_lip[-1], color, thickness)
示意图:
原图
效果图
2.2、表情识别
导入资源包:
from tensorflow.keras.models import load_model
from keras.preprocessing.image import img_to_array
dlib脸部特征检测的输出,一个shape里包含了前面说到的脸部特征的68个点
shape = predictor(gray, rect)
shape = shape_to_np(shape)
# 这个函数将shape转换成Numpy array,为方便后续处理。
def shape_to_np(shape, dtype="int"):
coords = np.zeros((68, 2), dtype=dtype)
for i in range(0, 68):
coords[i] = (shape.part(i).x, shape.part(i).y)
return coords
取出68个关键点中max_left,max_right, max_top, max_bottom,进行裁剪。
left, right, top, bottom = cope(shape)
cropped = gray[top:bottom, left:right] # 裁剪坐标为[y0:y1, x0:x1]
def cope(shape):
x, y = shape[0]
min_left = x
max_right = x
min_top = y
max_bottom = y
for (x, y) in shape:
if min_left > x:
min_left = x
if max_right < x:
max_right = x
if min_top > y:
min_top = y
if max_bottom < y:
max_bottom = y
print(min_left,max_right,min_top,max_bottom)
return min_left, max_right, min_top, max_bottom
将裁剪得到的单通道图像进行预处理,resize,Normalization等。
def preprocess_input(x, v2=True):
# 归一化
x = x.astype('float32')
x = x / 255.0
if v2:
x = x - 0.5
x = x * 2.0
return x
roi = cv2.resize(cropped, emotion_classifier.input_shape[1:3])
roi = preprocess_input(roi)
roi = img_to_array(roi)
roi = np.expand_dims(roi, axis=0)
各个表情分类及表情概率。
# 用模型预测各分类的概率
preds = emotion_classifier.predict(roi)[0]
emotion_probability = np.max(preds) # 最大的概率
label = EMOTIONS[preds.argmax()] # 选取最大概率的表情类
圈出人脸区域并显示识别结果。
cv2.putText(image,
"Face{}".format(i + 1) + ", " + label + ": " + str(round(emotion_probability * 100, 2)) + "%",
(left - 10, top - 10),
cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 225, 225), 1) # putText各参数依次是:图片,添加的
效果图
另外,如果是多个人脸检测,只需要在外层加一个循环就行啦。
多人效果图
三、视频检测
视频检测其实就是将视频里面每一帧图片进行检测,其中人脸检测与表情识别都与图片检测方法一样(可以参考上面图片检测)。
import numpy as np
import dlib
import cv2
from tensorflow.keras.models import load_model
from keras.preprocessing.image import img_to_array
cv2.VideoCapture调用笔记本内置摄像头,所以参数为0,如果有其他的摄像头可以调整参数为1,2,也可以为视频路径;
cap=cv2.VideoCapture("image/7.mp4")
fps = cap.get(cv2.CAP_PROP_FPS) # 调用cv2方法获取cap的视频帧(帧:每秒多少张图片)
# 获取cap视频流的每帧大小
size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),
int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
# 定义编码格式mpge-4
# 一种视频格式,参数搭配固定,不同的编码格式对应不同的参数
fourcc = cv2.VideoWriter_fourcc(*"XVID")
# 定义视频文件输入对象
outVideo = cv2.VideoWriter("output.avi", fourcc, fps, size) #第一个参数是保存视频文件的绝对路径
读取每一帧图片:
sucess, image = cap.read()
效果演示:
片段来自于电影《闻香识女人》
网友评论