Face_Keypoints_Detection1

1 Prepare

数据

  • 包括 2000 张图片以及相应标注信息。

python文件

  • generate_train_test_list.py
    生成训练与测试数据列表。
    训练列表:train.txt
    测试列表:test.txt
  • detector.py
    程序主体,用来进行模型训练/验证
  • data.py
    用来处理数据。由 detector.py 进行调用
  • predict.py
    调用训练好的模型预测关键点。同样由 detector.py 进行调用

2 stage1

本阶段,深入体会流程,完成 detector.py,并运行,进而完成训练,生成第一阶段的 detector 模型。再次运行 detector.py 并由其调用predict.py,用所训模型在人脸图片上画出关键点。

2.1 综述:

人脸关键点检测,目的是通过 CNN 方法,进而在已有人脸检测框的基础上【注意:不是在全图上】,进行关键点检测,输出关键点坐标。
所谓人脸关键点,是一系列人为定义的点。人们主观认为,这些点最能体现人脸信息,比如轮廓、五官样貌等。人脸关键点并不唯一,少到 4 点 5 点,多道 100 点 200 点都有。具体需要多少,要视实际情况而定。本项目点数适中,采用 21 点。

2.2 原始数据

数据是任何 CNN 项目最为关键的一环,没有之一。甚至整个项目,如果没有数据,那么生成训练用数据能够占到整个项目一半以上的时间,有时甚至贯穿整个项目。【实际工作中,这一步很可能将由实习生或者数据标注公司完成,千万不要忽略这一步的重要性以及耗时】数据结构如下:
在这里插入图片描述
文件夹 I 与 II 中,分别有 1000 张图,共 2000 张。
I 与 II 中除图片外,还另有 label.txt,为标注信息。每行为一张图像的具体标注内容。
共分 3 部分:

  1. 图像位置;
  2. 人脸框;
  3. 相对于整张图像的人脸关键点信息,顺序为 x1 y1 x2 y2… x21 y21.

具体请看实例:

在这里插入图片描述
这是 label.txt 中一行所包含的信息,标注中,可能有<0 的情况出现,可以当做 0 或者忽略该行信息即可【请牢记:此时所有数据坐标均是关于原始图像大小的。通常情况下,原始坐标信息总是关于原始图像的。虽然几乎不可能应用原始坐标,但是为了数据的可重用性,原始坐标信息总是应该保持为关于原始图像大小的】因此,在训练中,为了让程序知悉图片位置以及得以应用标注信息,需要完成“生成数据列表”的任务。

生成 train/test.txt
A. 建议首先画出人脸边框以及相应关键点以熟悉程序操作、坐标表示以及检验标注数据是否正确,如下图所示:【为省地方,只截取了部分图】
在这里插入图片描述
在这里插入图片描述

B. 我们可以看到,有时由于标注的不仔细或不同标注任务标注标准不同,关键点可能会超出人脸框的范围。所以适当扩大人脸框的范围是必要的。这里,可以选取原始人脸框的 0.25 倍进行 expand。expand 时,请注意扩增后的人脸框不要超过图像大小。如下图:
在这里插入图片描述
在这里插入图片描述

C. 真正有用的部分是人脸以及人脸关键点。所以,希望对人脸进行截取。同时截取过后人脸关键点坐标即变为相对于截取后的人脸图了。此步非常简单,只需要用关键点坐标减去人脸边框左上角点坐标即可:

在这里插入图片描述

D. 生成 train/test.txt.此时我们已可以生成训练以及测试数据集。(如果严谨些,还可生成验证数据集,这里只是项目举例,所以只简单生成训练与测试数据。) 训练与测试数据的比例,依个人喜好,通常 7:3 至 9:1 均能接受。并且此时可以选择对数据进行 shuffle。(不 shuffle 也可。Caffe 或 pytorch 里均有可以 shuffle 的功能)最终训练集/测试集应该长成这样:

在这里插入图片描述

  1. 每行对应一张扩增人脸
  2. .jpg 为原图位置
  3. 后续四个数字为 expand 后的人脸边框坐标
  4. 后续 42 个数字为相对于人脸边框的人脸关键点坐标

E. 验证:为确保生成数据的准确性。生成后仍需要验证。具体可为利用生成的数据截取人脸,并画出关键点,检验正确性。以上为数据准备的全部流程。

2.2 网络搭建

A. 利用Netron/Netscope工具查看网络结构
B. 知识点:

  1. 数据在网络中的维度顺序是什么?
    经过conv1_1后,根据(W+2P-K)/S+1=(112+2*0-5)/2+1=54,所以这步过后是8x54x54(除法那步向下取整)
    经过avg_pool后,根据(W+2P-K)/S+1=(54+0-2)/2+1=27, 所以这步过后是8x27x27
    经过conv2_1后,(27+0-3)/1+1=25,这步过后是16x25x25
    经过conv2_2后,(25+0-3)/1+1=23,这步过后是16x23x23
    经过avg_pool后,(23+0-2)/2+1=12(注意程序这里用了ceil),这步过后是16x12x12
    经过conv3_1后,(12+0-3)/1+1=10,这步过后是24x10x10
    经过conv3_2后,(10+0-3)/1+1=8,这步过后是24x8x8
    经过avg_pool后,(8+0-2)/2+1=4,这步过后是24x4x4
    经过conv4_1后,(4+2-3)/1+1=4,这步过后是40x4x4
    经过conv4_2后,(4+2-3)/1+1=4,这步过后是80x4x4
    经过ip1后,为128
    经过ip2后,为128
    经过ip3后,为42

  2. nn.Conv2d()中参数含义与顺序?
    输入的C,出来的C,kernel大小,stride,padding

  3. nn.Linear()是什么意思?参数含义与顺序?
    输入的C,输出的C

  4. nn.PReLU()与 nn.ReLU()的区别?示例中定义了很多 nn.PReLU(),能否只定义一个PReLU?
    输入为负数时不都为0。每个PReLU有个控制斜率的参数,所以不能只定义一个。

  5. nn.AvgPool2d()中参数含义?还有什么常用的 pooling 方式?
    kernel size和stride。maxpool吧

  6. view()的作用?
    改变了维度,在这里等于flatten

  7. 体会 forward 中网络如何被构建。

  8. 注意返回值返回的并不是 loss

C. 开始新建一个 detector.py,写自己的 class Net
D.训练框架的搭建:
打开 detector.py,由主函数 main()出发。

2.3 训练框架的搭建

A. 第一部分
首先是参数设置部分,这些参数控制着整个程序。
在这里插入图片描述
B. 第二部分
在这里插入图片描述

此部分是一些“程序控制代码”,包括:

  1. 如何设置 GPU
    device = torch.device(“cuda” if use_cuda else “cpu”)

  2. 如何将数据/网络传入 CPU/GPU
    . to(device)

  3. 如何读取数据
    这个就不拷贝了,程序里挺长的

C. 第三部分
此部分是关于“训练控制代码”,知识点包括:

  1. 如何设置 loss

  2. loss 都有哪些。分别有什么作用(常用的即可)
    MSE,cross entropy,L1loss等

  3. 如何设置优化器
    SGD的话主要就是lr和momentum了

D. 第四部分
在这里插入图片描述

此部分是定义“程序处于什么阶段”的部分。
这里我们看到有四个阶段。分别是:
a. 训练
此部分将引导我们进入“训练代码”。训练代码是程序主体。模型学习来源于此部分
b. 测试
此部分将引导我们进入“测试代码”。如何总体评价我们训练的模型好坏,将由此部分做出
c. Finetune
有时我们会用别人训好的模型进行 finetune,或接着训练自己的模型。
d. 预测
有时我们也会直观检测我们训好的代码。比如,应用训好代码画出人脸关键点。

主体程序框架的搭建
在 detector.py 中完成main 程序主体。

读取数据:
在这里插入图片描述

2.4 如何读取数据

此时get_train_test_set()函数一定显得无比突兀。
数据的预处理与读取就藏于这里。我们可以通过文档加载了解到这个函数应该藏于 data.py。


A. 第一部分,关于主体:
首先,找到主体:
在这里插入图片描述

不难看到根源在 load_data()中。
另外main 主函数是用于检验处理后的数据的准确性的,切记随时检查准确性非常重要。


B. 第二部分,关于 load_data():
在这里插入图片描述
知识点:

  1. 请注意,train 与 test 的数据处理可以不同。虽然这里是相同的
  2. 按 train 与 test 不同,先定义数据变换及其顺序,这里是 Normalize+ToTensor,再将其作用在数据上,此处是 FaceLandmarksDataset()】

C. 第三部分,关于变换 Normalize 与 ToTensor 注意 data.py 中,class Normalize 与 class ToTensor。注意到这里面的def __call__ 是非显示调用的。只管写,pytorch 会帮我们进行调用的。 另外传入的数据为 sample,这是一个 dictionary,代表了我们的数据包含了什么。 这里我们的数据只有两部分:

‘Image’ 为我们的图像
‘Landmarks’ 为我们的脸部关键点

Normalize:作用由 channel_norm 函数体现,为了将图片 normalize。
这是一个相对传统的做法,目前人们发现做不做这个 normalize对最终结果的影响似乎不是很大。
ToTensor:作用是将数据转成 pytorch 可使用的格式


D. 第四部分,FaceLandmarksDataset()

此部分为最终“将变换作用于数据”的部分。

可以看到这个 class 有三个部分,分别是:

__init__
__len__
__getitem__

此三部分,均不需显示调用。
它们分别对应:

类的初始化
一共有多少数据
对于每批数据应当做何变换
最关键的部分为__getitem__,这是变换的主体。

返回的将是真正用于训练的数据,因而它是一个 dictionary。代表了:
‘image’:图像是什么
‘landmarks’:关键点是什么
需要格外注意的是在处理图像的过程中有将图像 resize 到 train_boarder(这里是 112x112),但是 landmarks 得到的确是相对于 expand 过后的人脸 crop。

FaceLandmarksDataset()
a. data.py
b. 按照刚才的流程,重写读取数据部分
c. 补全在 FaceLandmarksDataset()中,对于 landmarks 的操作。使你的 landmarks 是针对 train_boarder size 的(示例中是 112x112),而非原始 expand facial crop 的。【另这里处理数据是用的 PIL 库,完全可以将其替换成熟悉的 OpenCV 库来进行图像操作。另外请注意,PIL 库中图像是 RGB顺序,OpenCV 为 BGR,两者不同】

main 函数
data.py 中:

以便验证自己的数据变换是正确的。

2.5 Train 部分

detector.py->def train().
这里就是训练代码的主体。包含两部分,其一是真正的训练部分;其二是 validating 部分。因为除了 train,我们要在 train 的过程中,实时监测训练结果,避免过拟。
知识点:

  1. print 的格式化如何实现的

  2. optimizer.zero()与 optimizer.step()的作用是什么?
    pytorch里头梯度会累积,zero就把所有梯度先赋零;step就是走一步learning step

  3. model.eval()产生的效果?

  4. model.state_dict()的目的是?
    把model各层用dict表示出来,以方便读写

  5. 何时系统自动进行 bp?
    在计算forward的时候会顺便把梯度算出来

  6. 如果自己的层需要 bp,如何实现?如何调用?
    需要自己把梯度存到weights里,step的时候更新这些weights

训练
至此训练的部分已经完整:
A. 在 detector.py 中完成 def train 函数
B. train 函数应由 main 函数 train 部分调用
C. 保存训练好的 model
D. 存留 log 信息的代码
E. train 函数 return 的部分,作用在于可以存下各个阶段的 loss,用于后续绘制loss 走势
F. 如果顺利至少在 train 时loss 低至 3 以下,在 validation 时loss 低至9 以下。如果你在应用自己的 criterion,那么请忽略此条。一切以画出的结果是否准确为准

2.6 Test、Predict 与 Finetune 部分

在 detector.py 中还有 Test、Predict 与 Finetune 三部分,分别在 main 函数args.phase==Test、Predict 与 Finetune 中定义。
请注意Test 的目的是直接利用已训练好 model作用在 test 数据集上。(为简便,这里 test 和 valid 可用一个数据集。)看平均 loss。
Predict 目的为利用已训练好 model 作用在某张图片上画出预测的 landmarks,直观看效果。相当于 model 的应用。
Finetune 为利用已训练好 model,继续训练。(通常finetune 会需要更小的 lr)。

Test\Predict\Finetune
A. load 已有 model
B. Finetune 时,有时还要固定某些层不参与训练,如何 freeze 某些层
我是直接将requires_grad设置成False

至此一套完整的流程已经全部建立
涉及到了:处理原始数据、网络搭建、流程控制、数据预处理、训练、检验等多个步骤。

3 stage2

此阶段,是对于上一阶段的补充。stage1 仅仅是起步。毕竟网络很简单、 loss 很简单、训练策略很简单、数据很简单等等。总之,一切都很简单。所以这个阶段是使 stage1 的各个部分变得 fancy。没有一定之规,总之,给出一个秒杀 stage1 的 model。

关于数据:
数据预处理非常简单。所以还可以尝试很多:

  1. 不要 normalize 可以么?
  2. 数据增广没做啊,做下试试?比如:水平翻转、小角度旋转、是否可以平移?

关于训练方法 :
训练采用最基本的 SGD,尝试更多方法?

  1. 如果换用 Adam 呢?
  2. 如果一开始用 Adam,之后换成 sgd 呢?
  3. 如果用 step 改变 lr 呢?
  4. 如果加上 batch normalization 呢?

关于网络:
网络非常简单,就是个线性网络。如果用其他的呢?

  1. 比如resnet?
  2. 比如 fpn?
  3. 比如其他的?
  4. 尽量避免过拟

以下为扩展内容
关于目标:
目标非常简单,就是直接回归坐标。但有的时候,我们也会回归 heatmap。这也可以是一个可以尝试的部分。
关于 loss:
我们的 loss 非常简单,就是 MSE,可以尝试下其他 loss,比如 smooth l1 loss。如果更改了训练目标,也应当配合训练目标适当更改 loss。

4 stage3

现实项目往往更加复杂。比如:现实情况下,你怎么能保证输入就是张人脸呢?因此真实场景中,是否应该再加入分类分支呢?
任务完成真实场景下人脸关键点检测:

A. 为网络加入分类分支。
真实场景下,不能保证输入就是人脸。
如果没有分类分支,那么一旦输入为非人脸,虽然此时不应输出任何关键点,但是网络势必仍然会输出伪关键点。因而一种解决方法就是为网络加入分类分支。如果分类认为输入图片为人脸,则输出人脸关键点;如果分类认为输入为非人脸,则此时不输出人脸关键点。

B. 生成非人脸数据。
非人脸数据的生成可由图片中不含有人脸的部分得到。可以认为,如果一个image crop,其与人脸重叠部分 iou<0.3,就是非人脸。

C. 重新生成 train/test.txt
以前生成的 train/test.txt 格式如下:
文件名 | 人脸框 | 人脸关键点
现在要生成新的 train/test.txt格式如下:
文件名 | 图像框 | 人脸关键点 | 1 【此时是人脸,称正样本】
文件名 | 图像框 | 0【此时非人脸,称负样本】

D. 对于 loss 的处理
这是最复杂的部分:
Loss 分为两部分:a. 分类 loss + b. 坐标 loss
关于 a:需要注意正负样本比例。同时还可以利用 weighted cross entropy loss 控制侧重于训练正样本还是负样本。学习如何使用 weighted loss;
关于 b:需要注意只有正样本拥有这个 loss,负样本是没有这个 loss 的。所以如何来控制?可以在样本计算 loss 时加入 mask,这个 mask 可以由 label 来生成。比如一个 batch 有 4 个样本,分别为:负样本、人脸、负样本、负样本。则这个mask 可以为[0, 1, 0, 0]。此时真正计算 b loss 的就只有第二个样本了。关于 mask的应用,可以参考 yolo-pytorch 版本。比如这个连接中 response、not response loss 的部分。
除此之外,由于 loss 分为两个部分,还可以给两个部分加入不同的权重,权重多的,会被侧重训练。如:
L o s s = a ∗ l o s s a + b ∗ l o s s b Loss=a*loss_a + b*loss_b Loss=alossa+blossb 
此时,如果 a>b,则会侧重训练 loss_a。

E. 关于检测
除了对于坐标 loss 的监测,还要对分类 loss 进行监测,并给出 accuracy。同时,此时的 accuracy 不能仅仅是总体样本的 accuracy,我们还要分别看正负样本的accuracy,所以这个需要去统计各个 batch 中,正负样本的个数并加以计算
至此stage3 也算是告一段落。
Stage3 是较为复杂的综合类问题。是可以当做真实项目处理的实际问题。可以看到与 stage1的区别。

5 Summary

通过三个阶段,每个阶段的提高,希望同学们能有所收获。
Stage1 的目的,仅仅在于熟悉流程,熟悉编程。
Stage2 的目的,在于应用一些小技巧和一些常用的框架,属于“熟悉主流文化”
Stage3 的目的,是希望熟悉对实际问题的感觉,里面会涉及到更多技巧与思想。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值