制作人脸表情数据集

转自:https://blog.csdn.net/z704630835 https://blog.csdn.net/z704630835/article/details/82992036



一、背景

人脸表情识别网上已有很多教程,大多基于fer2013数据集展开的。现在的问题就在于fer2013数据集的数量太少,表情的区分度不够明显,大部分基于此数据集的模型,其识别精度仅有70%左右。

因此我想自己从零开始制作人脸表情,而且是非常夸张,有趣的人脸表情,用于后续的表情识别实验。这一篇仅仅介绍如何在没有任何数据的情况下,从零开始制作人脸表情数据集。

二、数据获取

首先没有任何数据的情况下,该如何开始数据的获取呢?其实就两种思路:一种是自己采集,例如用摄像头抓帧,另一种是爬虫爬取数据。实验采用爬虫爬取百度图片中的数据。

然后自己设计自己需要的表情,我自己设置了10类。这里以“吃惊”表情为例,在百度图片搜索中可以看到:

有一部分人脸数据,当然还有很多非人脸数据,这里我们先不管那么多,直接全部爬取下来,爬取的程序采用:


   
   
   
   
  1. # 导入需要的库
  2. import requests
  3. import os
  4. import json
  5. # 爬取百度图片,解析页面的函数
  6. def getManyPages(keyword, pages):
  7. '''
  8. 参数keyword:要下载的影像关键词
  9. 参数pages:需要下载的页面数
  10. '''
  11. params = []
  12. for i in range( 30, 30 * pages + 30, 30):
  13. params.append({
  14. 'tn': 'resultjson_com',
  15. 'ipn': 'rj',
  16. 'ct': 201326592,
  17. 'is': '',
  18. 'fp': 'result',
  19. 'queryWord': keyword,
  20. 'cl': 2,
  21. 'lm': -1,
  22. 'ie': 'utf-8',
  23. 'oe': 'utf-8',
  24. 'adpicid': '',
  25. 'st': -1,
  26. 'z': '',
  27. 'ic': 0,
  28. 'word': keyword,
  29. 's': '',
  30. 'se': '',
  31. 'tab': '',
  32. 'width': '',
  33. 'height': '',
  34. 'face': 0,
  35. 'istype': 2,
  36. 'qc': '',
  37. 'nc': 1,
  38. 'fr': '',
  39. 'pn': i,
  40. 'rn': 30,
  41. 'gsm': '1e',
  42. '1488942260214': ''
  43. })
  44. url = 'https://image.baidu.com/search/acjson'
  45. urls = []
  46. for i in params:
  47. try:
  48. urls.append(requests.get(url, params=i).json().get( 'data'))
  49. except json.decoder.JSONDecodeError:
  50. print( "解析出错")
  51. return urls
  52. # 下载图片并保存
  53. def getImg(dataList, localPath):
  54. '''
  55. 参数datallist:下载图片的地址集
  56. 参数localPath:保存下载图片的路径
  57. '''
  58. if not os.path.exists(localPath): # 判断是否存在保存路径,如果不存在就创建
  59. os.mkdir(localPath)
  60. x = 0
  61. for list in dataList:
  62. for i in list:
  63. if i.get( 'thumbURL') != None:
  64. print( '正在下载:%s' % i.get( 'thumbURL'))
  65. ir = requests.get(i.get( 'thumbURL'))
  66. open(localPath + '%d.jpg' % x, 'wb').write(ir.content)
  67. x += 1
  68. else:
  69. print( '图片链接不存在')
  70. # 根据关键词来下载图片
  71. if __name__ == '__main__':
  72. dataList = getManyPages( '吃惊', 20) # 参数1:关键字,参数2:要下载的页数
  73. getImg(dataList, './data/chijing/') # 参数2:指定保存的路径

之前我用这段程序爬取过皮卡丘图像,因此不再多做介绍,详细可以参见:对抗神经网络学习(四)——WGAN+爬虫生成皮卡丘图像(tensorflow实现)。这里爬取好的图像直接保存在根目录下的'./data/chijing/'文件夹中。

同样的思路,可以爬取其余9种表情。

三、人脸裁剪及预处理

爬取完人脸表情之后,我们需要裁剪处图像中的人脸,这里设置裁剪大小为128*128。裁剪过程需要用到opencv的人脸识别工具,即haarcascade_frontalface_alt.xml,关于该文件,可从opencv库的根目录中查找,具体查找方法可以参见我之前的文章:深度学习(一)——deepNN模型实现摄像头实时识别人脸表情(C++和python3.6混合编程)。将该文件复制到project的根目录下,然后裁剪人脸,具体的裁剪程序为:


   
   
   
   
  1. import os
  2. import cv2
  3. # 读取图像,然后将人脸识别并裁剪出来, 参考https://blog.csdn.net/wangkun1340378/article/details/72457975
  4. def clip_image(input_dir, floder, output_dir):
  5. images = os.listdir(input_dir + floder)
  6. for imagename in images:
  7. imagepath = os.path.join(input_dir + floder, imagename)
  8. img = cv2.imread(imagepath)
  9. path = "haarcascade_frontalface_alt.xml"
  10. hc = cv2.CascadeClassifier(path)
  11. faces = hc.detectMultiScale(img)
  12. i = 1
  13. image_save_name = output_dir + floder + imagename
  14. for face in faces:
  15. imgROI = img[face[ 1]:face[ 1] + face[ 3], face[ 0]:face[ 0] + face[ 2]]
  16. imgROI = cv2.resize(imgROI, ( 128, 128), interpolation=cv2.INTER_AREA)
  17. cv2.imwrite(image_save_name, imgROI)
  18. i = i + 1
  19. print( "the {}th image has been processed".format(i))
  20. def main():
  21. input_dir = "data/"
  22. floder = "chijing/"
  23. output_dir = "output/"
  24. if not os.path.exists(output_dir + floder):
  25. os.makedirs(output_dir + floder)
  26. clip_image(input_dir, floder, output_dir)
  27. if __name__ == '__main__':
  28. main()

如果要将该方法用于其余9种表情,只需自己修改main函数中的相关路径即可。这样裁剪之后的影像中,难免混有一些非人脸图像,只需手动删除即可。这样处理之后的效果为:

制作人脸表情数据集_第1张图片

四、数据增广

经过以上三步,基本可以得到人脸数据集了,但是有的表情的数据量很少,进行模型训练的时候必然会因为数据不足带来影响,因此还需要进行数据增广处理。

一般地,数据增广处理的方法包括:旋转,镜像,随机裁剪,噪声,变形,颜色变化,对比度拉伸等方法。对于人脸来说,这里所选择的处理方法仅有:镜像,即左右反转;随机裁剪,将128*128影响随机裁剪为120*120,再将其resize成128*128;噪声,添加少量的随机噪声。考虑到人脸数据集的特殊性,其他方法暂时没有选择。

这里直接给出数据增广的代码:


   
   
   
   
  1. # 参考https://blog.csdn.net/qq_36219202/article/details/78339459
  2. import os
  3. from PIL import Image,ImageEnhance
  4. import skimage
  5. import random
  6. import numpy as np
  7. import cv2
  8. # 随机镜像
  9. def random_mirror(root_path, img_name):
  10. img = Image.open(os.path.join(root_path, img_name))
  11. filp_img = img.transpose(Image.FLIP_LEFT_RIGHT)
  12. filp_img = np.asarray(filp_img, dtype= "float32")
  13. return filp_img
  14. # 随机平移
  15. def random_move(root_path, img_name, off):
  16. img = Image.open(os.path.join(root_path, img_name))
  17. offset = img.offset(off, 0)
  18. offset = np.asarray(offset, dtype= "float32")
  19. return offset
  20. # # 随机转换
  21. # def random_transform( image, rotation_range, zoom_range, shift_range, random_flip ):
  22. # h,w = image.shape[0:2]
  23. # rotation = numpy.random.uniform( -rotation_range, rotation_range )
  24. # scale = numpy.random.uniform( 1 - zoom_range, 1 + zoom_range )
  25. # tx = numpy.random.uniform( -shift_range, shift_range ) * w
  26. # ty = numpy.random.uniform( -shift_range, shift_range ) * h
  27. # mat = cv2.getRotationMatrix2D( (w//2,h//2), rotation, scale )
  28. # mat[:,2] += (tx,ty)
  29. # result = cv2.warpAffine( image, mat, (w,h), borderMode=cv2.BORDER_REPLICATE )
  30. # if numpy.random.random() < random_flip:
  31. # result = result[:,::-1]
  32. # return result
  33. # # 随机变形
  34. # def random_warp( image ):
  35. # assert image.shape == (256,256,3)
  36. # range_ = numpy.linspace( 128-80, 128+80, 5 )
  37. # mapx = numpy.broadcast_to( range_, (5,5) )
  38. # mapy = mapx.T
  39. #
  40. # mapx = mapx + numpy.random.normal( size=(5,5), scale=5 )
  41. # mapy = mapy + numpy.random.normal( size=(5,5), scale=5 )
  42. #
  43. # interp_mapx = cv2.resize( mapx, (80,80) )[8:72,8:72].astype('float32')
  44. # interp_mapy = cv2.resize( mapy, (80,80) )[8:72,8:72].astype('float32')
  45. #
  46. # warped_image = cv2.remap( image, interp_mapx, interp_mapy, cv2.INTER_LINEAR )
  47. #
  48. # src_points = numpy.stack( [ mapx.ravel(), mapy.ravel() ], axis=-1 )
  49. # dst_points = numpy.mgrid[0:65:16,0:65:16].T.reshape(-1,2)
  50. # mat = umeyama( src_points, dst_points, True )[0:2]
  51. #
  52. # target_image = cv2.warpAffine( image, mat, (64,64) )
  53. #
  54. # return warped_image, target_image
  55. # 随机旋转
  56. def random_rotate(root_path, img_name):
  57. img = Image.open(os.path.join(root_path, img_name))
  58. rotation_img = img.rotate( 180)
  59. rotation_img = np.asarray(rotation_img, dtype= "float32")
  60. return rotation_img
  61. # 随机裁剪
  62. def random_clip(root_path, floder, imagename):
  63. # 可以使用crop_img = tf.random_crop(img,[280,280,3])
  64. img = cv2.imread(root_path + floder + imagename)
  65. count = 1 # 随机裁剪的数量
  66. while 1:
  67. y = random.randint( 1, 8)
  68. x = random.randint( 1, 8)
  69. cropImg = img[(y):(y + 120), (x):(x + 120)]
  70. image_save_name = root_path + floder + 'clip' + str(count) + imagename
  71. # BGR2RGB
  72. # cropImg = cv2.cvtColor(cropImg, cv2.COLOR_BGR2RGB)
  73. cropImg = cv2.resize(cropImg, ( 128, 128))
  74. cv2.imwrite(image_save_name, cropImg)
  75. count += 1
  76. print(count)
  77. if count > 3:
  78. break
  79. # 随机噪声
  80. def random_noise(root_path, img_name):
  81. image = Image.open(os.path.join(root_path, img_name))
  82. im = np.array(image)
  83. means = 0
  84. sigma = 10
  85. r = im[:, :, 0].flatten()
  86. g = im[:, :, 1].flatten()
  87. b = im[:, :, 2].flatten()
  88. # 计算新的像素值
  89. for i in range(im.shape[ 0] * im.shape[ 1]):
  90. pr = int(r[i]) + random.gauss(means, sigma)
  91. pg = int(g[i]) + random.gauss(means, sigma)
  92. pb = int(b[i]) + random.gauss(means, sigma)
  93. if (pr < 0):
  94. pr = 0
  95. if (pr > 255):
  96. pr = 255
  97. if (pg < 0):
  98. pg = 0
  99. if (pg > 255):
  100. pg = 255
  101. if (pb < 0):
  102. pb = 0
  103. if (pb > 255):
  104. pb = 255
  105. r[i] = pr
  106. g[i] = pg
  107. b[i] = pb
  108. im[:, :, 0] = r.reshape([im.shape[ 0], im.shape[ 1]])
  109. im[:, :, 1] = g.reshape([im.shape[ 0], im.shape[ 1]])
  110. im[:, :, 2] = b.reshape([im.shape[ 0], im.shape[ 1]])
  111. gaussian_image = Image.fromarray(np.uint8(im))
  112. return gaussian_image
  113. # 随机调整对比度
  114. def random_adj(root_path, img_name):
  115. image = skimage.io.imread(os.path.join(root_path, img_name))
  116. gam = skimage.exposure.adjust_gamma(image, 0.5)
  117. log = skimage.exposure.adjust_log(image)
  118. gam = np.asarray(gam, dtype= "float32")
  119. log = np.asarray(log, dtype= "float32")
  120. return gam, log
  121. # 运行
  122. def main():
  123. root_dir = "output/"
  124. floder = "chijing/"
  125. images = os.listdir(root_dir + floder)
  126. for imagename in images:
  127. mirror_img = random_mirror(root_dir + floder, imagename)
  128. image_save_name = root_dir + floder + "mirror" + imagename
  129. mirror_img = cv2.cvtColor(mirror_img, cv2.COLOR_BGR2RGB)
  130. cv2.imwrite(image_save_name, mirror_img)
  131. random_clip(root_dir, floder, imagename)
  132. noise_img = random_noise(root_dir + floder, imagename)
  133. noise_img = np.asarray(noise_img, dtype= "float32")
  134. image_save_name = root_dir + floder + "noise" + imagename
  135. noise_img = cv2.cvtColor(noise_img, cv2.COLOR_BGR2RGB)
  136. cv2.imwrite(image_save_name, noise_img)
  137. print( "image preprocessing")
  138. if __name__ == '__main__':
  139. main()

很多处理方法都是参考网上的代码,只不过我将这些方法整合到了一起。对于其余9种表情,需要设置的仅仅只是main函数种的路径参数。

随机裁剪3张、镜像1张、随机噪声1张的处理结果如下:

制作人脸表情数据集_第2张图片

最终的“吃惊”表情处理结果:

制作人脸表情数据集_第3张图片

整个处理之后的数据量还是非常可观的,当然了,如果自己觉得数据量还不够,还可以再进行进一步处理,例如对镜像图像进行随机裁剪,不同程度的噪声影像,以及对比度拉伸等方法。

以上仅仅是一种表情的数据集制作,同理制作其余的表情数据集即可。准备好了数据集之后,即可开始进行模型的训练了~

你可能感兴趣的:(制作人脸表情数据集)