> ## Content Index
> Fetch the complete content index at: https://yinguobing.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# 基于深度学习的人脸特征点检测 - 数据集整理
- URL: https://yinguobing.com/blog/facial-landmark-localization-by-deep-learning-data-cleansing/
- Published: 2017-12-08T07:28:09.000Z
- Updated: 2022-05-29T05:36:23.000Z
- Description: 从互联网获取的数据大多数情况下不是开箱即用的，这意味着我们需要对数据进行初步的整理，例如统计数据量、去除不需要的文件、必要的格式转换等。
- Author: 尹国冰
- Tags: 深度学习, 人脸识别

在[上一篇博文](https://yinguobing.com/blog/facial-landmark-localization-by-deep-learning-data-and-algorithm/)中，我们已经下载到了包括300-W、LFPW、HELEN、AFW、IBUG和300-VW在内的6个数据集，初步估算有25万个样本可以使用。同时还发现了一篇论文描述了采用深度神经网络来提取特征点的方法。在本篇文章中，我们将尝试对下载到的数据进行分析整理，包括：

- 统计数据集中不同格式文件的数量。
- 提取视频文件中的每一帧为单独的文件。
- 从自定义格式pts文件中读取特征点坐标，并显示在图片上。

这些步骤将为后续的工作打下坚实的数据基础。

## 检查数据集的形式

为方便使用，大部分数据集都会尽可能的保留原始数据，并将附加的信息以文本或者其它形式进行存储，然后打包成一个或者若干压缩文档。所以拿到数据集后的第一件事，就是阅读数据集的说明文档。通过阅读说明，我们发现这六个数据库中的特征点坐标均以后缀名为`pts`的格式进行存储，且文件名与对应的图片文件名相同。

![与图片文件名对应的pts文件](https://yinguobing.com/blog/content/images/2017/12/img_pts.jpg)

有一个例外是300-VW。由于它是视频数据库，每一个视频文件则对应多个`pts`文件，每个`pts`文件对应了视频中的某一帧。

![300-VW数据库的标记文件](https://yinguobing.com/blog/content/images/2017/12/300-vw-anno.jpg)

打开其中任意一个`pts`文件，发现文件内容大概是这个样子：

```
version: 1
n_points:  68
{
336.820955 240.864510
334.238298 260.922709
335.266918 283.697151
# 中间省略若干行
370.512631 329.910074
363.035791 328.132512
357.493721 327.074215
}

```

不难发现，每一个`pts`文件中第一行是文件版本，第二行是坐标个数说明，从第三行开始，括号内存储的是对应图像中人脸的68个特征点的坐标x与y，一行一组。所以有多少个`pts`文件，就有多少个可用样本。所以，我们究竟有多少个可用样本？

## 使用Python统计文件数量

在Linux系统下，统计文件数量其实是非常简单的事情。直接在文件夹上点击鼠标邮件，选取`Properties`即可看到文件夹下的文件总数与大小。例如我这里总共包含227827项，大小5.2GB。但是这并不能准确的告诉我们`pts`文件的个数。并且，为了对数据集获得全面的认识，我们通常希望掌握文件夹下所有文件的数量与分布情况。这时候就该Python出场了！

![linux_file_properties](https://yinguobing.com/blog/content/images/2017/12/linux_file_properties.jpg)

为什么用Python？作为“自带电池”的计算机程序语言，Python提供了一系列模块可以极大的提升我们的工作效率，用尽可能少的代码快速的实现功能。对于统计文件数量这个需求来说，我们可以简单的使用几个函数就实现。主要的思路是：

- `os.walk()` 用来列出目录下所有文件夹与文件[\[1\]](#fn1)。
- `split()` 分割文件名，获取文件后缀名。
- `collection.Counter` 来统计后缀名的数量[\[2\]](#fn2)。

最后总体代码只有55行，包含了注释与空行。你可以自己尝试实现一下，也可以查看我在Github上[开源实现](https://github.com/yinguobing/image%5Futility/blob/master/count%5Ffiles.py?ref=yinguobing.com)。

经过统计，当前数据集所包含的文件数量与分布情况如下：

```
Total files: 227587
zip: 1; txt: 1; jpg: 2802; png: 1635; pts: 223034; avi: 114; Done!

```

从中可以看到我们可以使用的样本个数为223034个，即`pts`文件的个数。图片以两种格式存储，`jpg`格式2802个，`png`格式1635个，两者加起来才4437个。有20多万个样本都以视频帧的形式存储在114个`avi`文件中。

## 使用FFmpeg提取视频帧

若要利用这20多万个样本，需要从视频中提取每一帧图像为单独的文件。这里我推荐使用著名的音视频包FFmpeg[\[3\]](#fn3)。

![FFmpeg Logo](https://yinguobing.com/blog/content/images/2017/12/250px-FFmpeg_Logo_new.svg.png)

FFmpeg是一套完整的音视频转换与流媒体开源解决方案。在Ubuntu下你可以简单的通过`apt`命令来安装它。FFmpeg的使用也很简单，在终端中输入以下命令即可将视频中的每一帧提取成为单独的图像文件，存放在当前目录的`image`文件夹下，文件名取自帧编号：

```shell
ffmpeg -i vid.avi -qscale:v 1 -f image2 image/%06d.jpg

```

其中`-i`是指定输入文件，`-qscale:v`是指定输出的图片质量参数，`-f`为输出文件的模式。如果不指定`-qscale:v`的话FFmpeg会使用默认参数输出，图片质量会变得很差[\[4\]](#fn4)。下图从左向右依次是默认参数，质量为1和不压缩的BMP格式。

![默认参数的图片质量损失严重](https://yinguobing.com/blog/content/images/2017/12/ffmpeg-output-compare.jpg)

由于300-VW下的114个`avi`视频文件文件名是相同的，我们可以写一个简单的Shell脚本来批量将`avi`视频中的帧提取出来。

```shell
for dir in $(ls -d */);
do 
    cd $dir;
    mkdir image;
    ffmpeg -i vid.avi -qscale:v 1 -f image2 image/%06d.jpg;
    cd ..;
done

```

这段脚本在执行过程中会输出log供参考。整个300-VW的转换在我的计算机上花费的时间不到10分钟。

![ffmpeg-shell-script](https://yinguobing.com/blog/content/images/2017/12/ffmpeg-shell-script.jpg)

转换完成后，我们可以使用之前创建好的文件统计脚本重新统计文件数量。

```
Total files: 446185
jpg: 221399; txt: 1; pts: 223034; png: 1635; zip: 1; sh: 1; avi: 114; Done!

```

将`jpg`的数量221399与`png`的数量1635加起来是223034，刚好等于`pts`文件的数量。

So far so good！不过，这些图片与`pts`中的坐标能对应上吗？

## 验证特征点坐标与图片的一致性

理论上图片与坐标数据是一一对应的，不过我相信你心里应该也会有那么一点点的不确定——同名的`pts`与图像文件真的一一对应吗？我们从视频中提取出来的图像呢，也能对应上吗？

为了消除心中的这个疑虑，也是为了从最开始就排除数据不匹配这一最糟糕的情况，最好将数据中的特征点画在对应图像上，人工用肉眼来验证一下！

要达成这一目标，需要实现以下两个主要功能：

- 从`pts`文件中读取68个特征点的坐标。
- 读取对应的图像，将特征点绘制在图像上并呈现出来。

### 使用Python读取文本文件

Python读取文本文档是不能再简单的事情，使用以下3行代码即可逐行打印出文本文档中的所有内容[\[5\]](#fn5)。

```python
with open('image_003_1.pts') as file:
    for line in file:
        print(line)

```

使用该方法将一个`pts`文件中的内容打印出来如下：

```
version: 1
n_points:  68
{
336.820955 240.864510
334.238298 260.922709
335.266918 283.697151
# 中间省略若干行
370.512631 329.910074
363.035791 328.132512
357.493721 327.074215
}

```

仔细观察不难发现，68个特征点在`pts`文件中是顺序存储的，一行一组，所以只需要排除掉非坐标点行即可得到所有坐标。具体的排除方法可以自选，例如坐标行中仅包含数字、小数点和空格，非坐标行的字符与符号几乎是固定的。稍微留意一下方法的稳健性与通用性即可。如果你想也可以参考下我在Github上的实现方法[\[6\]](#fn6)。

### 使用Python读取并显示图片

在这里，我推荐使用适用于Python的OpenCV模块。你可以通过`pip`方式安装，也可以按照官方教程[\[7\]](#fn7)自行编译。

安装完成后，我们就可以使用Python调用OpenCV函数，主要是以下3个：

- `cv2.imread()` 读取图像文件。
- `cv2.circle()` 在图像指定位置处绘制圆点。
- `cv2.imshow()` 将修改后的图像显示在窗口中。

具体实现过程并不复杂，你可以自己尝试一下，同样也可以参考我在Github上的实现方法[\[6:1\]](#fn6)。

最后，我们从搜集好的6个数据集中**随机**选择一些文件来看一下效果。

#### 300-VW

![300vw-009](https://yinguobing.com/blog/content/images/2017/12/300vw-009.jpg)

#### 300-W

![300w-outdoor](https://yinguobing.com/blog/content/images/2017/12/300w-outdoor.jpg)

#### AFW

![afw](https://yinguobing.com/blog/content/images/2017/12/afw.jpg)

#### HELEN

HELEN数据集中的图片分辨率较高，因此绘制的Mark点看上去很小。  
![helen](https://yinguobing.com/blog/content/images/2017/12/helen.jpg)

#### IBUG

![ibug](https://yinguobing.com/blog/content/images/2017/12/ibug.jpg)

#### LFPW

同样需要注意的是有时候图片中存在多张人脸，但是一个`pts`文件中只存储一个人脸的数据。  
![lfpw](https://yinguobing.com/blog/content/images/2017/12/lfpw.jpg)

看上去还不错，是吧！

## 接下来的工作

至此我们已经将数据集整理并验证完成。如果你有耐心，可以把所有的数据集都过一遍，你会发现数据集中的图片有大有小，样本人群多种多样，眼镜、首饰等等遮挡情况不时发生。总体看来这些都是不错的训练样本，不过也给我们后边的工作带来了一些麻烦。如果人脸只占图片中的一小部分，却将整张图片送入神经网络，然后要求学习特征点坐标，这意味着神经网络需要学习非常复杂的规则，花精力去区分非人脸部分，意味着网络的复杂度会提高，意味着参数的数量会上升，意味着需要更多的训练样本，更长的训练时间，以及最后在应用时更慢的运行速度。所以，我们怎么办？

这是我们在下一篇博文中要解决的问题：定义检测规则，并提取适合神经网络的训练样本。

---

1. [https://docs.python.org/3.5/library/os.html](https://docs.python.org/3.5/library/os.html?ref=yinguobing.com) [↩︎](#fnref1)
2. [https://docs.python.org/3.5/library/collections.html](https://docs.python.org/3.5/library/collections.html?ref=yinguobing.com) [↩︎](#fnref2)
3. [http://ffmpeg.org/](http://ffmpeg.org/?ref=yinguobing.com) [↩︎](#fnref3)
4. [StackOverflow: How can I extract a good quality JPEG image from an H264 video file with ffmpeg?](https://stackoverflow.com/questions/10225403/how-can-i-extract-a-good-quality-jpeg-image-from-an-h264-video-file-with-ffmpeg?ref=yinguobing.com) [↩︎](#fnref4)
5. [https://docs.python.org/3.5/tutorial/inputoutput.html](https://docs.python.org/3.5/tutorial/inputoutput.html?ref=yinguobing.com) [↩︎](#fnref5)
6. [https://github.com/yinguobing/image\_utility/blob/master/pts\_tools.py](https://github.com/yinguobing/image%5Futility/blob/master/pts%5Ftools.py?ref=yinguobing.com) [↩︎](#fnref6) [↩︎](#fnref6:1)
7. [https://docs.opencv.org/master/d7/d9f/tutorial\_linux\_install.html](https://docs.opencv.org/master/d7/d9f/tutorial%5Flinux%5Finstall.html?ref=yinguobing.com) [↩︎](#fnref7)