【课题】人脸真伪辨识
本文文件仓库
本文所用到的所有内容(运行前)均保存在该仓库中:https://github.com/chmoe/Real_Fake_Face
由于代码内容较多,正文部分不会展示全部代码,有需要的小可爱可以去github上clone。
另外本文内容为试验(Test)而非实验(Experiment)。
环境
开发环境
项目 | 版本 |
---|---|
Device | MacMini(2020) M1 |
OS | macOS Big [email protected] |
RAM | 16G |
SSD | 1T |
Python | 3.8.6 based conda |
IDE | PyCharm 2021.2.2(Community Edition) |
运行环境
项目 | 版本 |
---|---|
CPU | AMD Ryzen 7 3800X @ 3.90GHz |
GPU | Nvidia GeForce RTX2080 SUPER (8GB) |
OS | CentOS |
RAM | 64GB (DDR4-3200) |
SSD | SSD 1TB |
Python | 3.8.8 based conda |
整体思想
步骤
- 使用MTCNN对于人脸有一个初步的识别,并且提取人脸部分的图像。
- 对于提取出的人脸部分的图像使用SLIC算法进行superpixel处理。
- 根据superpixel处理后的区块对于图像进行分割。
- 将分割后的图像送入神经网络进行学习。
- 构建一个自动化程序完成上述步骤的2、3、4三个部分
预计达成的试验目标
- 在使用SLIC算法对于图像分割时,找到较优的分割块数(K): 20~64 块
- 神经网络训练时,找到较优的神经网络: 经过Fine-tuning化的VGG16、Resnet50、Xception
文件树
本程序运行在一个名为AllInOne
的文件夹中,为方便调试程序,因此生成的所有文件夹都将会位于AllInOne的同级目录中,可以在config.py
文件中进行修改相关目录。
1 | . |
开发
关于数据集合
数据集
本文的数据集采用了Kaggle的Real and Fake Face Detection数据集的real_and_fake_face
部分,并对数据集进行预处理。
数据集预处理
针对本文中的MTCNN部分可以参见上一篇文章中的MTCNN部分,这里不再过多赘述。
但是本来觉得会如此轻松的过程却遇到了问题。不知道是不是M1晶片引起的问题,在使用MTCNN的时候发现该函数会造成内存泄漏,同样在检索的时候发现这条Issue一直是开放的状态,所以应该不是我自己的问题。
为了解决这个问题,只好对于代码进行一定的改造,既然内存泄漏会导致程序被操作系统杀死,那么只好让程序分段进行。
具体做法就是看着程序的处理进度,在程序被杀死之后立刻手动调整运行参数,让程序继续处理。由于本次试验使用的数据集不大,因此相比于用代码实现自动化,手动处理会更加节省时间,因此决定使用这种方式,代码部分如下。
1 | import cv2 # conda install opencv |
上述代码中第20行的851是需在程序终止运行时手动提供的运行参数,程序最开始运行时应为0.
代码运行结束后可获得文件夹result
,其内部结构如本文中文件树所示。fake
中放置生成的人脸,real
中放置真实的人脸,这两个文件夹中都是经过预处理后单独提取脸部内容。
pipeline
本栏内部介绍的每一个py文件均为一个同名的类,会介绍类的部分作用和参数含义。
Net
描述
Net类的作用是三种网络的调用类,需要传入SLIC的分割数,fine-tuning的冷冻层数以及网络名称。
注:本类中的init
部分的图像宽高以及epoch和batch数需要在本类中进行控制。
保存训练过程
本类中的save_history
会读取model.history的训练结果,然后根据每一个网络和分割数将下述内容存入对应的txt文件
- accuracy
- loss
- val_accuracy
val_loss(由于回调函数Validation中未计算损失值,因此该项不可用)
生成网络
本类中的create_model
方法用于生成网络,根据传入的网络名称来决定调用哪一个网络,其中可选网络如下
调用名称 | 对应类 | 对应网络 |
---|---|---|
Config.name_vgg16 | VGG | VGG16 |
Config.name_resnet | Resnet | Resnet50 |
Config.name_xception | XCeption | XCeption |
生成训练用数据
本类中的generate_train
方法用于生成训练用数据集,使用的是keras。preprocessing.image
下的ImageDataGenerator
类对图片进行的处理。
经过该类的处理,训练用图片集合被转换为300*300大小的图片生成器。
生成验证用数据
由于任务处理的特殊性,训练集合和验证集合对于图片的处理方式应该有所不同。
训练数据输入时,应该按照每一个分割对应一个标签进行输入,而验证数据需要将一张图片的所有分割部分统一进行输入,然后对于这些分割部分的评价采用多数表决的方式决定整张图片的判定结果。然后判定验证集中的每一图片进而计算验证集的精确度。
并且因为ImageDataGenerator.flow_from_directory
是根据文件夹对于图片进行分类,因此需要自行使用yield
关键词构建生成器方法。(不了解生成器方法的小可爱可以看这里的生成器部分)
根据下文的Split部分分割的验证集文件结构如SLIC_result所示,验证集的fake|real文件夹中的每一个子文件夹对应一个面容图片文件,子文件夹中每一个图像文件均为SLIC分割后的图像。
因此在构建生成器
的时候,需要将x值的形状设置为(batch_size, K, width, heigh, RGB)=(32, 40, 300, 300, 3)(这里的K指的是SLIC算法的分割数),y值的形状应该为(batch_size, result)=(32, 1)。
在函数内部构建一个while True的循环,每一次使用yield关键词返回batch_size个numpy数组。
同时为方便训练时计算验证集何时完成一个循环,需要在每次返回时同时返回一个bool值finish_flag
,在每次完成一个循环时返回为真,否则为假。在回调函数Validation中计算验证集Accuracy时每一次检测该bool值是否为真,如若为真则结束本次计算。
开始训练
经过上文生成的网络需要送入到类中fit
方法进行训练,而本方法在训练之前会执行几种操作。
首先是使用keras.callbacks
下的ModelCheckpoint
方法创建检查点,并且分别调用generate_train
方法与Validation
类进行生成训练生成器和验证用回调函数。
在执行正式训练之前,先检查对应的checkpoint保存目录,获取上次的训练情况,加载上次训练的情况后继续进行训练,同时设置ModelCheckpoint
和Validation
回调函数。
代码中还有一个keras.callbacks
下的ReduceLROnPlateau
类的调用,但是由于本程序中没有验证集的损失值,因此这个类即使被调用也不会产生任何效果,在程序运行时候会Warning提示该回调函数会被忽略,因此可以删除此回调函数也可继续保留。,已经修正为根据准确率调整。
另外:本程序使用了model.fit_generator
方法进行训练,但是其内部也同样是调用的model.fit
方法进行训练,并且会产生警告提示该方法即将被抛弃不可用。
保存
该类中的save
方法将会保存两种内容,首先是将训练好的模型进行保存。另外会调用该类中的save_history
方法对于训练过程进行保存。
Resnet
该类的目标为创造一个可以用于训练的模型,需要输入的值为图像的shape=(width, height, 3),以及需要冻结的层数,默认为前100层。
类内部调用了keras.applications.resnet50
类的ResNet50
方法,并且根据resnet的Fine-tuning方法构建相应模型,最后返回构建好的模型。
SLIC
本类的作用是根据输入的slic处理分割数和训练集验证集比例,调用skimage.segmentation
类下的slic方法进行slic算法计算分隔结果,将计算好的分割的结果送入Split
类中进行图像分割。
本类中的重点是由于训练集合和验证集合的数据保存方式不同,因此需要分别对于两种情况分别处理。
Split
根据试验可知,slic算法的输出结果是一个由区间组成的二维数组(当且仅当输入也为二维时),每一个数字标记的像素对应为一种色块,每个色块之间数值不会有重复。
因此在分割图像时,可以根据SLIC算法的这一特点入手进行编写。
根据要求,图片需要处理成当前色块显示原图像的部分,其他不属于当前色块的地方设置为0,因此遍历SLIC类传入的数组,一次对于数组中每一个值所对应的色块进行处理并且保存为图像。
此时,由于train和validation的数据需求不同,因此保存时候需要分别进行处理,训练集合保存为{}_{}.jpg
的形式,而验证集保存为{}/{}.jpg
的形式。(形式描述采用python的string.format形式)
Validation
Validation类是继承回调函数keras.callbacks.Callback
的子类,其功能是在每一次epoch结束后,使用Net类中的generate_validation
方法生成的验证集生成器计算val_accuracy,同时将每一次的epoch结果保存在数组中,在每一次训练结束后将该数组写入model.history.history
。
注:本类中初始化和最终保存时涉及到了loss值,但是在计算的时候没有计算loss值,因此写入到model.history.history
中的val_loss值为空,调用可能会出错。
VGG
该类的目标为创造一个可以用于训练的模型,需要输入的值为图像的shape=(width, height, 3),以及需要冻结的层数,默认为前15层。
类内部调用了keras.applications.vgg16
类的VGG16
方法,并且根据resnet的Fine-tuning方法构建相应模型,最后返回构建好的模型。
XCeption
该类的目标为创造一个可以用于训练的模型,需要输入的值为图像的shape=(width, height, 3),以及需要冻结的层数,默认为101层。
类内部调用了keras.applications.xception
类的Xception
方法,并且根据resnet的Fine-tuning方法构建相应模型,最后返回构建好的模型。
SLIC_result
该文件夹将由程序自动生成,位于main.py
文件的上一级目录中。
由于该程序设置的分割值为20~64,因此该SLIC_result
文件夹的结构如下。
1 |
|
由于文件树太过庞大,因此只使用分割数位20的地方进行部分展示,其他分割数以此类推。
config
config文件中的Config类承载了整个程序运行的所有可变常量,具体内容如下
常量名称 | 常量值 | 功能|含义 |
---|---|---|
name_fake | fake | 非真实图片的名称,会作用于文件夹命名等 |
name_real | real | 真实图片的名称,会作用于文件夹命名等 |
name_train | train | 用于存放训练用图片的文件夹名称,会作用于文件夹命名等 |
name_validation | validation | 用于存放验证用图片的文件夹名称,会作用于文件夹命名等 |
name_vgg16 | vgg | 使用VGG16网络时调用的名称 |
name_resnet | resnet | 使用resnet网络时调用的名称 |
name_xception | xception | 使用xception网络时调用的名称 |
frozen_layer_vgg16 | 15 | 使用vgg16网络时冻结的层数 |
frozen_layer_resnet | 100 | 使用resnet网络时冻结的层数 |
frozen_layer_xception | 101 | 使用xception网络时冻结的层数 |
original_face_path | result/ | 人脸区域原始图像 |
slic_result_path | …/SLIC_result/ | 超像素处理后的位置 |
checkpoint_path | …/checkpoint/ | 检查点的公共文件夹 |
model_path | …/models/ | 存放模型的公共文件夹 |
history_path | …/history/ | 存放历史记录的公共文件夹 |
另外,config文件中还保存了一些全局会使用到的静态方法
方法 | 输入 | 输出 | 功能 |
---|---|---|---|
path_exist | path: 一个路径 | 返回输入的路径 | 判断路径是否存在,不存在创建这样一个路径,返回原来的路径目的是可以直接将函数放在路径中 |
is_image_file | 文件名 | bool值: 表示是否为图片 | 判断一个文件是否为图片文件 |
get_image_file_list | path: 一个路径, is_train_data: bool值是否为训练文件 | 返回一个所有图片路径的list | 根据给定路径获取路径下所有图片文件 |
path | 若干路径 | 一个拼接好的路径 | 将给定的内容拼接成为路径,并且替换掉//…/ |
get_child_folder | 一个路径 | 路径下所有子文件夹的list | 根据给定的路径返回所有子文件夹的路径,不包含内部文件 |
Debug
本类中模仿C#的Debug类,重点是每次调用时,在输出的内容之前使用time.localtime
自动加上系统时间。
main
在Main
类中调用pipeline.SLIC
与pipeline.Net
类进行调用,并且使用循环控制SLIC分割数,对于每个分割数分别调用三个网络进行训练。
总结
本文在发布之时,程序运行还没有结束,因此后续待运行结束后会进行更新。
参考及素材来源
利用screen和nohup让Linux服务器后台运行程序:https://blog.csdn.net/Cowry5/article/details/80630324
Python不定长参数(*args、**kwargs含义): https://zhuanlan.zhihu.com/p/70649428
Python replace()方法: https://www.runoob.com/python/att-string-replace.html
Keras自定义评估函数: https://www.cnblogs.com/shixiangwan/p/9041707.html
在Keras中使用数据生成器的详细示例: https://blog.csdn.net/qq_36763031/article/details/108278105
Python 正则表达式: https://www.runoob.com/python/python-reg-expressions.html
图片输入大小问题-keras/PIL.Image: https://blog.csdn.net/qq_23590921/article/details/80912350
改变输入形状尺寸的微调与Keras: https://www.kismetceyiz.com/2019/06/24/change-input-shape-dimensions-for-fine-tuning-with-keras/
Python将多个list合并为1个list: https://blog.csdn.net/roytao2/article/details/54180182
编写自己的回调函数: https://tensorflow.google.cn/guide/keras/custom_callback?hl=zh-cn
创建一个回调: https://keras-zh.readthedocs.io/callbacks/
判断python字典中key是否存在的: https://blog.csdn.net/tao546377318/article/details/52160942
生成器: https://www.liaoxuefeng.com/wiki/1016959663602400/1017318207388128
TensorFlow-Keras 2.自定义Loss与metrics: https://blog.csdn.net/BIT_666/article/details/108796486
Python之Lambda表达式和if not…else用法: https://blog.csdn.net/liudinglong1989/article/details/78728683
python中os.listdir( )函数读取文件夹下文件的乱序和排序问题: https://blog.csdn.net/qq_22227123/article/details/79903116
在服务器中住着的AKI娘会检测您的输入内容哦, 如果被判断为垃圾内容是看不到的呢!当然抹茶也会定期检查AKI娘的所作所为的!