上一篇博文中,我们已经获取到了所有样本的面部区域,并且对面部区域的有效性进行了验证。当使用TensorFlow进行神经网络训练时,涉及到的大量IO操作会成为训练速度的瓶颈。为了加快训练的速度,方便后期利用与复现,需要将所有用到的数据打包成为TFRecord文件,一种TensorFlow原生支持的数据格式。

提取独立的面部图像

为了方便将来的重复利用,我决定采用保险一点的策略——将所有面部区域提取出来存储为独立的图像文件。

文件名命名规则

由于图像来自于不同的数据库,为了方便后期debug文件需要重命名,我推荐以下策略:

  • 可追溯。只通过文件名就可以找到原始图片。
  • 不重复。即使所有文件放置在一个文件夹下,也不会出现重名错误。
  • 全部采用小写字母。

这样可以定下文件名模板dataset_name-subset_name-file_name

300-VW

之前已经将视频转换为了图片,图片文件名是帧编号。每一个视频都有一个独立的3位数编号。因此示例文件名为300vw-001-000001.jpg

300-W

300-W的文件名中已经包含了subset的名字,因此沿用原有文件名与dataset名字。最终结果示例300w-indoor_001.jpg

AFW

AFW下没有看到明显subset,最终文件名示例:afw-134212_1.jpg

HELEN

HELEN下有两个subset,最终文件名示例:helen-trainset-232194_1.jpg

IBUG

IBUG没有观察到subset,最终文件名示例:ibug-image_105.jpg

LFPW

LFPW下有两个subset,最终文件名示例:lfpw-trainset-image_0001.jpg

保存图像为jpg格式

保存图像并不复杂,不过由于提取到的面部区域多种多样,因此在保存前需要考虑以下两点。

缩放

从原始图片中提取到的面部区域大小是不确定的,因此需要做缩放处理。这里缩放的目标尺寸取决于神经网络的输入层结构。例如我将采用128×128作为神经网络的输入层大小,因此图像缩放的目标尺寸即为128×128像素。

压缩品质

为了节省空间,图片会采用压缩格式jpg。我推荐采用OpenCV中的imwrite()函数来实现[1],它默认的压缩品质为95,可以保证压缩后的图片质量不下降的特别明显。如果你采用其它工具来实现,请留意一下图像的压缩品质这个容易被忽略的参数。

特征点坐标转换

以上数据库中的坐标是以原始图片坐标系为基准的。当图像被裁剪后,原始坐标也就失去了它的意义,需要将原始坐标转换到新的图片坐标系中。这个转换过程并不复杂,直接用特征点当前坐标减去面部边界框左上角的坐标即可。

# Shift points first.
for point in points:
    point[0] -= left_x
    point[1] -= top_y

同时为了方便神经网络更好的学习,我建议将裁剪后的新图像特征点坐标归一化。这样所有的坐标均变成了[0, 1]之间的实数。这是从Udacity的深度学习课程中学到的,算是学有所用吧。

# Then normalize the coordinates.
for point in points:
    point[0] /= width
    point[1] /= height

转换完成后的坐标是一个Python list,形如[[x1, y1], [x2, y2], ..., [x68, y68]]。由于存入TFRecord文件的数据会被序列化,所以这种二维列表的形式需要被转换为一个一维列表。这里我将借助Python下的矩阵运算模块Numpy的flatten()函数来实现[2]。转换完成后的列表形如[x1, y1, ..., x68, y68]。这样做的好处是若将来想恢复原始坐标的结构,只需要使用Numpy的reshape()函数即可。

考虑到新的坐标已经与原始坐标相去甚远,为方便后期使用,我将转换完成后的归一化的新坐标存储为单独的json文件,这里我推荐使用json模块下的dump()来实现[3]。采用json文件的好处在于它可以方便的被其它计算机语言或者程序使用,通用性较好。

如果你感兴趣,可以参考我在GitHub上的开源实现[4]

提取结果

使用该方法提取300-VW中的人脸数据的结果如下:

All done! Total file: 218597, invalid: 1469, succeed: 217128

22万多张图片的数据转换花费了3个多小时,可能是因为我在转换过程中开启了图片预览造成的。剩余的数据集转换原理是一样的,具体结果如下:

  • HELEN:Total file: 2330, invalid: 0, succeed: 2330
  • 300W:Total file: 600, invalid: 0, succeed: 600
  • AFW:Total file: 337, invalid: 0, succeed: 337
  • IBUG:Total file: 135, invalid: 0, succeed: 135
  • LFPW:Total file: 1035, invalid: 0, succeed: 1035

最终一共获得221565个样本,数据总大小约为1.9GB。
dataset_size

将图像与特征点转换为TFRecord文件

之前的博文[5]中已经介绍了TFrecord文件,在这里我们需要将一些必要的信息存入到TFRecord文件中,具体包括:

  • 图像
  • 图像尺寸
  • 图像文件名,以备后期DEBUG
  • 图像格式
  • 特征点坐标,共计68×2个数值。

在制作文件之前,我将所有的文件分为3大部分:train,validation与test。

Train

顾名思义,用来训练神经网络的数据集。我随机选择了20万个样本作为训练集。这部分数据将是神经网络主要的信息来源。

Validation

验证集,主要用来在训练过程中验证神经网络是否陷入了局部最优陷阱。典型的表现就是train集合上的准确率上升而验证集上的准确率却在下降。随机选择了11565个样本作为验证集的大小。

Test

测试集,神经网络在该数据集上的表现将被作为训练是否成功的最终判定依据。所以在整个的训练过程中这部分数据对于神经网络来说一直是不可见的绝密内容。这里我将剩余的1万个样本作为测试集。

这三部分的选择完全是随机的,这样可以尽可能的使数据集均衡,消除人为因素对数据的干扰。最后生成的文件包括:train.tfrecord,validation.tfrecord和test.tfrecord三个文件。

最终的TFRecord文件生成并不是非常困难,可以参考我在Github上的开源实现[6]

小结与下一步计划

简单回顾一下,我们通过将视频转换为图像已经成功的从6个数据库中提取出了22万多个样本;通过对特征点坐标进行验证剔除掉其中的不可用样本,提取出可用样本221565个;采用其中的20万个用作训练,11565个作为验证,1万个作为测试;对数据进行转换并最终生成了三个TensorFlow可以直接使用的TFRecord文件。

到目前为止工作内容一直集中在数据的清洗与预处理,大部分的代码都与TensorFlow没有太大的关系。不过TFRecord文件的生成意味着数据相关工作已经基本完成。在下一篇文章中,我们将正式基于TensorFlow来构建一款属于我们自己的神经网络。


  1. https://docs.opencv.org/3.3.1/d4/da8/group__imgcodecs.html ↩︎

  2. https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.flatten.html ↩︎

  3. https://docs.python.org/3/tutorial/inputoutput.html ↩︎

  4. https://github.com/yinguobing/image_utility/blob/master/extract_face_from_ibug.py ↩︎

  5. https://yinguobing.com/tfrecord-in-tensorflow/ ↩︎

  6. https://github.com/yinguobing/tfrecord_utility/tree/ibug ↩︎