转自:https://blog.csdn.net/z704630835 https://blog.csdn.net/z704630835/article/details/82992036
人脸表情识别网上已有很多教程,大多基于fer2013数据集展开的。现在的问题就在于fer2013数据集的数量太少,表情的区分度不够明显,大部分基于此数据集的模型,其识别精度仅有70%左右。
因此我想自己从零开始制作人脸表情,而且是非常夸张,有趣的人脸表情,用于后续的表情识别实验。这一篇仅仅介绍如何在没有任何数据的情况下,从零开始制作人脸表情数据集。
首先没有任何数据的情况下,该如何开始数据的获取呢?其实就两种思路:一种是自己采集,例如用摄像头抓帧,另一种是爬虫爬取数据。实验采用爬虫爬取百度图片中的数据。
然后自己设计自己需要的表情,我自己设置了10类。这里以“吃惊”表情为例,在百度图片搜索中可以看到:
有一部分人脸数据,当然还有很多非人脸数据,这里我们先不管那么多,直接全部爬取下来,爬取的程序采用:
-
# 导入需要的库
-
import requests
-
import os
-
import json
-
-
# 爬取百度图片,解析页面的函数
-
def getManyPages(keyword, pages):
-
'''
-
参数keyword:要下载的影像关键词
-
参数pages:需要下载的页面数
-
'''
-
params = []
-
-
for i
in range(
30,
30 * pages +
30,
30):
-
params.append({
-
'tn':
'resultjson_com',
-
'ipn':
'rj',
-
'ct':
201326592,
-
'is':
'',
-
'fp':
'result',
-
'queryWord': keyword,
-
'cl':
2,
-
'lm':
-1,
-
'ie':
'utf-8',
-
'oe':
'utf-8',
-
'adpicid':
'',
-
'st':
-1,
-
'z':
'',
-
'ic':
0,
-
'word': keyword,
-
's':
'',
-
'se':
'',
-
'tab':
'',
-
'width':
'',
-
'height':
'',
-
'face':
0,
-
'istype':
2,
-
'qc':
'',
-
'nc':
1,
-
'fr':
'',
-
'pn': i,
-
'rn':
30,
-
'gsm':
'1e',
-
'1488942260214':
''
-
})
-
url =
'https://image.baidu.com/search/acjson'
-
urls = []
-
for i
in params:
-
try:
-
urls.append(requests.get(url, params=i).json().get(
'data'))
-
except json.decoder.JSONDecodeError:
-
print(
"解析出错")
-
return urls
-
-
# 下载图片并保存
-
def getImg(dataList, localPath):
-
'''
-
参数datallist:下载图片的地址集
-
参数localPath:保存下载图片的路径
-
'''
-
if
not os.path.exists(localPath):
# 判断是否存在保存路径,如果不存在就创建
-
os.mkdir(localPath)
-
x =
0
-
for list
in dataList:
-
for i
in list:
-
if i.get(
'thumbURL') !=
None:
-
print(
'正在下载:%s' % i.get(
'thumbURL'))
-
ir = requests.get(i.get(
'thumbURL'))
-
open(localPath +
'%d.jpg' % x,
'wb').write(ir.content)
-
x +=
1
-
else:
-
print(
'图片链接不存在')
-
-
# 根据关键词来下载图片
-
if __name__ ==
'__main__':
-
dataList = getManyPages(
'吃惊',
20)
# 参数1:关键字,参数2:要下载的页数
-
getImg(dataList,
'./data/chijing/')
# 参数2:指定保存的路径
之前我用这段程序爬取过皮卡丘图像,因此不再多做介绍,详细可以参见:对抗神经网络学习(四)——WGAN+爬虫生成皮卡丘图像(tensorflow实现)。这里爬取好的图像直接保存在根目录下的'./data/chijing/'文件夹中。
同样的思路,可以爬取其余9种表情。
爬取完人脸表情之后,我们需要裁剪处图像中的人脸,这里设置裁剪大小为128*128。裁剪过程需要用到opencv的人脸识别工具,即haarcascade_frontalface_alt.xml,关于该文件,可从opencv库的根目录中查找,具体查找方法可以参见我之前的文章:深度学习(一)——deepNN模型实现摄像头实时识别人脸表情(C++和python3.6混合编程)。将该文件复制到project的根目录下,然后裁剪人脸,具体的裁剪程序为:
-
import os
-
import cv2
-
-
# 读取图像,然后将人脸识别并裁剪出来, 参考https://blog.csdn.net/wangkun1340378/article/details/72457975
-
def clip_image(input_dir, floder, output_dir):
-
images = os.listdir(input_dir + floder)
-
-
for imagename
in images:
-
imagepath = os.path.join(input_dir + floder, imagename)
-
img = cv2.imread(imagepath)
-
-
path =
"haarcascade_frontalface_alt.xml"
-
-
hc = cv2.CascadeClassifier(path)
-
-
faces = hc.detectMultiScale(img)
-
i =
1
-
image_save_name = output_dir + floder + imagename
-
for face
in faces:
-
imgROI = img[face[
1]:face[
1] + face[
3], face[
0]:face[
0] + face[
2]]
-
imgROI = cv2.resize(imgROI, (
128,
128), interpolation=cv2.INTER_AREA)
-
cv2.imwrite(image_save_name, imgROI)
-
i = i +
1
-
print(
"the {}th image has been processed".format(i))
-
-
-
def main():
-
input_dir =
"data/"
-
floder =
"chijing/"
-
output_dir =
"output/"
-
-
if
not os.path.exists(output_dir + floder):
-
os.makedirs(output_dir + floder)
-
-
clip_image(input_dir, floder, output_dir)
-
-
-
if __name__ ==
'__main__':
-
main()
如果要将该方法用于其余9种表情,只需自己修改main函数中的相关路径即可。这样裁剪之后的影像中,难免混有一些非人脸图像,只需手动删除即可。这样处理之后的效果为:
经过以上三步,基本可以得到人脸数据集了,但是有的表情的数据量很少,进行模型训练的时候必然会因为数据不足带来影响,因此还需要进行数据增广处理。
一般地,数据增广处理的方法包括:旋转,镜像,随机裁剪,噪声,变形,颜色变化,对比度拉伸等方法。对于人脸来说,这里所选择的处理方法仅有:镜像,即左右反转;随机裁剪,将128*128影响随机裁剪为120*120,再将其resize成128*128;噪声,添加少量的随机噪声。考虑到人脸数据集的特殊性,其他方法暂时没有选择。
这里直接给出数据增广的代码:
-
# 参考https://blog.csdn.net/qq_36219202/article/details/78339459
-
import os
-
from PIL
import Image,ImageEnhance
-
import skimage
-
import random
-
import numpy
as np
-
import cv2
-
-
-
# 随机镜像
-
def random_mirror(root_path, img_name):
-
img = Image.open(os.path.join(root_path, img_name))
-
filp_img = img.transpose(Image.FLIP_LEFT_RIGHT)
-
filp_img = np.asarray(filp_img, dtype=
"float32")
-
return filp_img
-
-
-
# 随机平移
-
def random_move(root_path, img_name, off):
-
img = Image.open(os.path.join(root_path, img_name))
-
offset = img.offset(off,
0)
-
offset = np.asarray(offset, dtype=
"float32")
-
return offset
-
-
-
# # 随机转换
-
# def random_transform( image, rotation_range, zoom_range, shift_range, random_flip ):
-
# h,w = image.shape[0:2]
-
# rotation = numpy.random.uniform( -rotation_range, rotation_range )
-
# scale = numpy.random.uniform( 1 - zoom_range, 1 + zoom_range )
-
# tx = numpy.random.uniform( -shift_range, shift_range ) * w
-
# ty = numpy.random.uniform( -shift_range, shift_range ) * h
-
# mat = cv2.getRotationMatrix2D( (w//2,h//2), rotation, scale )
-
# mat[:,2] += (tx,ty)
-
# result = cv2.warpAffine( image, mat, (w,h), borderMode=cv2.BORDER_REPLICATE )
-
# if numpy.random.random() < random_flip:
-
# result = result[:,::-1]
-
# return result
-
-
-
# # 随机变形
-
# def random_warp( image ):
-
# assert image.shape == (256,256,3)
-
# range_ = numpy.linspace( 128-80, 128+80, 5 )
-
# mapx = numpy.broadcast_to( range_, (5,5) )
-
# mapy = mapx.T
-
#
-
# mapx = mapx + numpy.random.normal( size=(5,5), scale=5 )
-
# mapy = mapy + numpy.random.normal( size=(5,5), scale=5 )
-
#
-
# interp_mapx = cv2.resize( mapx, (80,80) )[8:72,8:72].astype('float32')
-
# interp_mapy = cv2.resize( mapy, (80,80) )[8:72,8:72].astype('float32')
-
#
-
# warped_image = cv2.remap( image, interp_mapx, interp_mapy, cv2.INTER_LINEAR )
-
#
-
# src_points = numpy.stack( [ mapx.ravel(), mapy.ravel() ], axis=-1 )
-
# dst_points = numpy.mgrid[0:65:16,0:65:16].T.reshape(-1,2)
-
# mat = umeyama( src_points, dst_points, True )[0:2]
-
#
-
# target_image = cv2.warpAffine( image, mat, (64,64) )
-
#
-
# return warped_image, target_image
-
-
-
# 随机旋转
-
def random_rotate(root_path, img_name):
-
img = Image.open(os.path.join(root_path, img_name))
-
rotation_img = img.rotate(
180)
-
rotation_img = np.asarray(rotation_img, dtype=
"float32")
-
return rotation_img
-
-
-
# 随机裁剪
-
def random_clip(root_path, floder, imagename):
-
# 可以使用crop_img = tf.random_crop(img,[280,280,3])
-
img = cv2.imread(root_path + floder + imagename)
-
count =
1
# 随机裁剪的数量
-
while
1:
-
y = random.randint(
1,
8)
-
x = random.randint(
1,
8)
-
cropImg = img[(y):(y +
120), (x):(x +
120)]
-
image_save_name = root_path + floder +
'clip' + str(count) + imagename
-
# BGR2RGB
-
# cropImg = cv2.cvtColor(cropImg, cv2.COLOR_BGR2RGB)
-
cropImg = cv2.resize(cropImg, (
128,
128))
-
cv2.imwrite(image_save_name, cropImg)
-
count +=
1
-
print(count)
-
if count >
3:
-
break
-
-
-
# 随机噪声
-
def random_noise(root_path, img_name):
-
image = Image.open(os.path.join(root_path, img_name))
-
im = np.array(image)
-
-
means =
0
-
sigma =
10
-
-
r = im[:, :,
0].flatten()
-
g = im[:, :,
1].flatten()
-
b = im[:, :,
2].flatten()
-
-
# 计算新的像素值
-
for i
in range(im.shape[
0] * im.shape[
1]):
-
pr = int(r[i]) + random.gauss(means, sigma)
-
pg = int(g[i]) + random.gauss(means, sigma)
-
pb = int(b[i]) + random.gauss(means, sigma)
-
-
if (pr <
0):
-
pr =
0
-
if (pr >
255):
-
pr =
255
-
if (pg <
0):
-
pg =
0
-
if (pg >
255):
-
pg =
255
-
if (pb <
0):
-
pb =
0
-
if (pb >
255):
-
pb =
255
-
r[i] = pr
-
g[i] = pg
-
b[i] = pb
-
im[:, :,
0] = r.reshape([im.shape[
0], im.shape[
1]])
-
im[:, :,
1] = g.reshape([im.shape[
0], im.shape[
1]])
-
im[:, :,
2] = b.reshape([im.shape[
0], im.shape[
1]])
-
gaussian_image = Image.fromarray(np.uint8(im))
-
return gaussian_image
-
-
-
# 随机调整对比度
-
def random_adj(root_path, img_name):
-
image = skimage.io.imread(os.path.join(root_path, img_name))
-
gam = skimage.exposure.adjust_gamma(image,
0.5)
-
log = skimage.exposure.adjust_log(image)
-
gam = np.asarray(gam, dtype=
"float32")
-
log = np.asarray(log, dtype=
"float32")
-
return gam, log
-
-
-
# 运行
-
def main():
-
root_dir =
"output/"
-
floder =
"chijing/"
-
-
images = os.listdir(root_dir + floder)
-
-
for imagename
in images:
-
-
mirror_img = random_mirror(root_dir + floder, imagename)
-
image_save_name = root_dir + floder +
"mirror" + imagename
-
mirror_img = cv2.cvtColor(mirror_img, cv2.COLOR_BGR2RGB)
-
cv2.imwrite(image_save_name, mirror_img)
-
-
random_clip(root_dir, floder, imagename)
-
-
noise_img = random_noise(root_dir + floder, imagename)
-
noise_img = np.asarray(noise_img, dtype=
"float32")
-
image_save_name = root_dir + floder +
"noise" + imagename
-
noise_img = cv2.cvtColor(noise_img, cv2.COLOR_BGR2RGB)
-
cv2.imwrite(image_save_name, noise_img)
-
-
print(
"image preprocessing")
-
-
-
if __name__ ==
'__main__':
-
main()
很多处理方法都是参考网上的代码,只不过我将这些方法整合到了一起。对于其余9种表情,需要设置的仅仅只是main函数种的路径参数。
随机裁剪3张、镜像1张、随机噪声1张的处理结果如下:
最终的“吃惊”表情处理结果:
整个处理之后的数据量还是非常可观的,当然了,如果自己觉得数据量还不够,还可以再进行进一步处理,例如对镜像图像进行随机裁剪,不同程度的噪声影像,以及对比度拉伸等方法。
以上仅仅是一种表情的数据集制作,同理制作其余的表情数据集即可。准备好了数据集之后,即可开始进行模型的训练了~