< Deeplearning > 博采众长,一个更加全面的人脸质量评价库

< Deeplearning > 博采众长,一个更加全面的人脸质量评价库

开始

今天给大家推荐一个效果很好速度又快(逃))的人脸质量评价库,这个人脸质量评价库是我自己训的,为了方便表示,我给它起个名字,就BFQ(很像BBQ).

其实网上有很多开源的人脸质量算法,其中的很多甚至开源了模型出来,这给需要用人脸质量评价算法来过滤人脸的人们提供了很好的工具,但是很多脸质量算法的效果并不好,或是不能满足自己的需要,或是只能满足一部分的需求,所以我们往往需要多个质量算法库联合去判断一个人脸的质量。

选择基础算法

我在网上找了一下几个觉得还不错的(人脸)质量评估算算法:

  • 拉布拉斯二阶梯度
  • 一阶梯度
  • MotionBlurNet(mbn)
  • FaceQualityNet(fqn)
  • PaQ-2-PiQ

这5中算法各有所长,比如fqn是针对人脸的一种质量评价,对于人脸本身的性质,如遮挡和角度具有一定的敏感性;mbn是针对运动模型的一个质量评价算法;PaQ-2-PiQ是针对全场景的一种质评价;而基于一阶/二阶的梯度算法则是用传统算法去求解梯度去衡量一张图片的清晰度。

假如我们想基于分类去训练一个人脸质量评价的网络,我们的目的是训练一个输出为0/1的二分类器,0代表质量不好,1代表质量好,我们可以是先通过上面五种质量评价算法去给我们的每一个人脸样本打上置信度,然后基于这五个置信度分别设定不同的阈值,联合去判断一个样本的质量标签(0或者1),然后基于这个标签去训练一个质量评价的分类网络。

当然上面的做法是有明显的缺点的,那就是没有给不同质量的人脸划分程度,质量最好的和质量次好的都往相同的目标去优化,同样的,质量最差的和质量次差的也是都向同样的目标去优化,这样显然是不合理的。

更加合理的做法是把这个任务当做回归的问题来看待,我们的目标是训练一个人脸质量评价归回网络,而不是一个分类网络。

标签设置

所以现在问题来了,怎么给每一张图打上对应的标签。有一个办法,就是通过上面的5个图像(人脸)质量评价算法给每一张图片打的置信度综合去给每一张图片的质量标签,这样下来,图片的质量标签就集合了上面5个图像(人脸)质量算法的特点,更加的全面。

通过给我们的训练图片打上标签,我们发现上面每一个质量评价算法的输出值的范围的差别很大。统计在训练集上的每一个质量评价库的输出值范围如下:

  • 拉布拉斯二阶梯度 (0-1427)
  • 一阶梯度 (0-23)
  • MotionBlurNet(mbn) (0-36)
  • FaceQualityNet(fqn) (0.3-0.81)
  • PaQ-2-PiQ (25-85)

具体的分布如下图所示:

调整分布

大家可以看到,不同算法的输出值的范围的差异还是很大的,我们我们要做的就是统计出每一个算法输出的最大值max和最小值min,然后用对应的最大值和最小值归一化每一个算法的输出。具体的方法如下:

1
val = (val - min)/(max - min)

这里需要说明一点,那就是这个min和max不是所有样本的统计值,从上面的直方图我们也可以看到,很多算法的min,max值是一种奇异值的存在,它们绝大部分的输出分布和min, max相差的较远,于是从我从上面的直方图大致估计了一下它们新的min,max,这个min,max所代表的的范围比实际的范围要小,所以也用这样的min,max去归一化输出值也会更加的精准。

归一化之后直方图如下:
分布

好,把各个算法的输出都归一化到同一个尺度后,我们就要想想怎么去组合每一个样本的5个质量评价的结果,然后去生成它自己的质量标签。可以直接通过给不同输出结果加权重的方式来生成一个新的回归标签,如下:

1
bfq_score = w1 * lp_blur_socre + w2 * g_sharp + w3 * MotionNet_score + w4 * FaceQnet_score + w5 * Paq-P2Q

w1到w5的这些权重就需要自己手动设置了,你可以以算法的重要性来设定权值,把比较符合自己task的算法输出的权值增大。

好,经过了上面的操作,我来看一下训练集的bfq_score的归一化分布情况:

通过上面的样本label分布图中,我们可以看到,数据呈现了明显长尾分布。所以我对分布较少的区域的做了不同程度的曾广,从而让不同label的分布的差异不至于这么大。

后面就开始训练了,这里我用了普通的L2_LOSS做归回,然后训练了大概50个epoc。然后用tensorflow自带的工具计算了一个模型的计算量,如下图:

计算量大概有13M(乘法加法算两次),应该还有降低的空间,这个后面再优化。

NCNN封装

为了更加灵活的使用,我打算用NCNN把bfq模型封装一下.这里,我在原始NCNN的基础上,拆分出了一个极简的实现,对bfq的ncnn封装都是基于这个极简的ncnn实现,代码会在最后放出来.

首先,由于我是使用tensorflow训练的,需要把TF使用的网络结构转换成NCNN的格式.

在网上找了一下,发现没有合适又好用的转换程序,于是自己写了一套,最后转换的结果如下所示(展示部分):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
7767517
182 195
Input layer_0 0 1 blob_0 0=80 1=80 2=3
Convolution layer_2 1 1 blob_0 blob_1 0=24 1=3 2=1 3=2 4=0 14=0 15=1 16=1 7=1 5=0 6=648
BatchNorm layer_7 1 1 blob_1 blob_2 0=24 1=1e-3
ReLU layer_8 1 1 blob_2 blob_3 0=0.0
Pooling layer_9 1 1 blob_3 blob_4 0=0 1=3 2=2 3=0 13=0 14=1 15=1 4=0 5=0
Convolution layer_11 1 1 blob_4 blob_5 0=24 1=1 2=1 3=1 4=0 14=0 15=0 16=0 7=1 5=0 6=576
BatchNorm layer_16 1 1 blob_5 blob_6 0=24 1=1e-3
ReLU layer_17 1 1 blob_6 blob_7 0=0.0
ConvolutionDepthWise layer_19 1 1 blob_7 blob_8 0=24 1=3 2=1 3=2 4=0 14=0 15=1 16=1 7=24 5=0 6=216
BatchNorm layer_24 1 1 blob_8 blob_9 0=24 1=1e-3
Convolution layer_26 1 1 blob_9 blob_10 0=24 1=1 2=1 3=1 4=0 14=0 15=0 16=0 7=1 5=0 6=576
BatchNorm layer_31 1 1 blob_10 blob_11 0=24 1=1e-3
ReLU layer_32 1 1 blob_11 blob_12 0=0.0
ConvolutionDepthWise layer_34 1 1 blob_4 blob_13 0=24 1=3 2=1 3=2 4=0 14=0 15=1 16=1 7=24 5=0 6=216
BatchNorm layer_39 1 1 blob_13 blob_14 0=24 1=1e-3
Convolution layer_41 1 1 blob_14 blob_15 0=24 1=1 2=1 3=1 4=0 14=0 15=0 16=0 7=1 5=0 6=576
BatchNorm layer_46 1 1 blob_15 blob_16 0=24 1=1e-3
ReLU layer_47 1 1 blob_16 blob_17 0=0.0
ShuffleTwo layer_48 2 1 blob_17 blob_12 blob_18
Split layer_52 1 2 blob_18 blob_19 blob_20
Convolution layer_54 1 1 blob_19 blob_21 0=24 1=1 2=1 3=1 4=0 14=0 15=0 16=0 7=1 5=0 6=576
BatchNorm layer_59 1 1 blob_21 blob_22 0=24 1=1e-3
ReLU layer_60 1 1 blob_22 blob_23 0=0.0
ConvolutionDepthWise layer_62 1 1 blob_23 blob_24 0=24 1=3 2=1 3=1 4=1 14=1 15=1 16=1 7=24 5=0 6=216
BatchNorm layer_67 1 1 blob_24 blob_25 0=24 1=1e-3
Convolution layer_69 1 1 blob_25 blob_26 0=24 1=1 2=1 3=1 4=0 14=0 15=0 16=0 7=1 5=0 6=576
BatchNorm layer_74 1 1 blob_26 blob_27 0=24 1=1e-3
ReLU layer_75 1 1 blob_27 blob_28 0=0.0
ShuffleTwo layer_76 2 1 blob_28 blob_20 blob_29
Split layer_80 1 2 blob_29 blob_30 blob_31
......

这里需要说明一下,上面的ncnn结构中有个ShuffleTwo的操作,这是在原始的ncnn中是没有的,这里我为了简化操作,自己实现了一个ShuffleTwo的操作.

其次,我需要把TF的模型权重转换成ncnn能够使用的二进制格式.这里需要注意一下,ncnn使用的默认权重格式是(out_c,in_c,h,w),而TF的默认权重格式是(h, w, in_c, out_c),所以需要在转换权重的时候,对权重加上transpose的操作.

Pybind11 封装

为了更方便的调用,决定用Pybind11把c++代码封装成python的库.

由于要编译python接口,所以在CMakeList中添加了一个按钮PY_WRAP,用来选择是编译python接口还是普通的c++接口.

1
2
3
4
5
6
7
8
9
set(PY_WRAP ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
add_subdirectory(src)
if(PY_WRAP)
add_subdirectory(python)
else()
add_subdirectory(examples)
endif()

最后

我把代码开源到了github上,暂时没有放二进制的网络权重,后面会放出来.

代码地址:

BFQ

< Deeplearning > 博采众长,一个更加全面的人脸质量评价库

https://zhengtq.github.io/2020/11/13/A-new-FaceQnet/

Author

Billy

Posted on

2020-11-13

Updated on

2021-03-18

Licensed under

Comments