人脸检测的一些坑

来源:互联网 发布:2017年网络歌曲大全 编辑:程序博客网 时间:2024/05/22 03:35

做了大概2个多月人脸检测,从查最近三年的论文到确定技术路线,再到实现其中的几篇,遇到了不少坑。这些坑都是一些小细节,这种小细节在论文里面基本都是一笔带过或者根本就没说,而国内外的一些博客和问答网站上,又基本没有给出过太好的答案。在此总结一下,同时也希望给在做或者准备做这方面的人一些有用的提示。

 

1.怎样选择合适的算法做人脸检测

最近人脸检测算法的流派包括三类以及这三类之间的组合:viola-jones(后面简称vj)框架,dpm和cnn。如果准备搞学术刷性能,建议搞dpm和cnn这两个,尤其是cnn目前比较有搞头。如果是为了在服务器上搭一个性能不错但是也要兼顾一些速度的人脸检测服务,建议搞cnn和vj这两个,因为dpm实在是太慢了,如果是dpm+cnn更是慢上加慢。相比之下,单独使用cnn就可以获得很不错的性能,同时如果有gpu服务器的话,速度也尚可。vj的话,性能一般但是速度是没问题的。如果准备在移动端or嵌入式上使用(就是计算能力,内存,甚至代码和模型大小都有限制),而且还想实时的话,基本上就只能选vj了,当然特征不一定要用haar。hog,lbp,pixel-difference以及一系列手工设计的特征都可以一战。其实如果待检测的人脸没有特别大幅度的姿态变化,传统的vj就够了,新的方法主要还是在表达能力(说白了就是能检测到更多千奇百怪的人脸)上增强了很多。如果你的人脸没那么奇怪(例如:严重遮挡、旋转的特别厉害、特别不清晰等等),用vj也就够了。

 

2.怎样收集负样本图片

没有人脸的那种风景照片基本是不行的。说白了人脸检测器就是把一张图片中的人脸和背景区分开,所以负样本也应该是从带有人脸的图片中收集。例如像aflw这种公开数据集中,人脸比较小,背景变化也比较大,就可以用一些确定没有人脸的图片遮挡住一张图片中的人脸位置,这张图片就可以当作负样本图片了。当然,大多数公开数据集并没有把一张图片中的所有人脸标注出来,所以多数情况下还需要自己把上述方法处理过的图片再过一下。

 

3.在每个stage怎样生成负样本

我目前在搞基于vj和cnn的两套结构。这个问题主要存在于vj框架中,因为vj框架在每个stage开始训练之前,都需要利用当前的人脸检测器,把正样本过一遍,留下true positive,然后再从负样本图片中选取一些被错分的区域(patch),即false positive,用作补充,和之前stage被错分的负样本放在一起,作为当前stage的负样本。我查阅了一些论文和网上的资料,再加上我个人分析,一般来讲,合理的负样本生成策略如下:

(1)随机选取一张负样本图片

(2)从负样本图片中随机选取一个区域(left,top,size)

(3)将(2)中的区域送入当前的分类器,如果被错分成正样本,就保留下来,用于接下来stage的训练。

(4)重复(1)(2)(3)直到收集到足够的用于接下来stage训练的负样本。一般来讲,新生成的负样本+上个stage剩下的负样本=当前stage剩下的正样本。例如在第6个stage,剩下10000正样本,5000负样本,那么需要收集的负样本数量是5000。

再次申明,上面是我的个人理解,如果有问题欢迎指正or讨论。乍一看上面的策略好像没啥问题,但是当stage越来越多,当前分类器的fpr越来越小时,被错分的负样本就会越来越少,生成负样本也越来越难。例如:当前fpr=1e-5,如果要生成10000负样本,就需要测试10e4/1e-5=10e9(10亿)个区域。所以训练到最后,大部分的耗时都在生成负样本,而不是训练weak learner了。对于这种情况,我目前找的策略如下:

(1)尽量多收集负样本图片,增加负样本的多样性。

(2)如果内存允许,尽量多的把负样本图片读取进内存,这样在读取图片的时候会快很多,和运行分类器相比,磁盘读取和图片解码就慢多了。

(3)如果cpu允许,openmp使劲开多线程。

 

4.训练集、验证集和测试集

vj的原论文和soft-cascade的论文中都提到,要把数据分成train set, validate set和test set。train set用于训练,validate set用于在每个stage训练完成后,测试当前分类器的tpr,fpr。test set用于最后测试分类器的实际性能。但在实际操作中,validate set这部分很少有人做,而缺少这部分训练出的分类器性能也是可以接受的,所以可以不用拘泥于这部分,毕竟数据那么有限,还是多放一些数据训练吧。

 

5.程序中的坑

(1)负样本图片太多太大

为了获取更好的性能,我按照一些论文中的说法,逐渐从aflw中生成负样本图片。在负样本只有几百到几千张的时候,图片解码后保存在内存中,大概只占用1-2G,没什么问题。但随着负样本图片越来越多,在达到上万张后,内存的占用量提升到了几十G。虽然我是在服务器上跑的,但考虑到一台服务器有多个人同时使用/跑多个训练程序,这个占用量也很惊人了。为此,我考虑了一些解决方法,都是利用时间换空间。基本上想要内存占用少,只能延长训练时间了。最极端的做法是,当某个负样本被需要时,才从硬盘中读取到内存,该方法因为存在大量磁盘io,使训练过程变得极慢。好一点的方法是,维护一定的内存大小,例如10G,然后只有10G的样本被载入内存,当那些没有被载入的样本需要使用时,就把目前内存中的一些样本干掉,这样就有空间载入新样本了。该方法对训练速度的影响要小一些(取决于你允许的内存使用量和所有样本量的比例),但是程序设计起来较复杂,如果使用第三方库来实现,也会增加额外的依赖。这个问题我目前也没有想到即省空间又不太影响时间的方案,如果哪位大神知道,还望不吝赐教。

(2)图像越界

对于取像素值的时候,最好还是做一些越界保护,例如:

int x=max(min(x,width),0)

int y=max(min(y,height),0)

release版训练程序跑到一半崩溃查越界也是够酸爽的

(3)如果需要生成随机数,尽量不要用C自带的

C的伪随机序列生成算法相对还是比较弱的(周期不够长),而且不支持多线程。网上有很多很好用的随机序列生成算法,我使用的是randomc。

 

6.如何测试程序正确性

说实话这个真的挺难,我的做法是,可以先自己伪造一些小规模数据,这样自己就知道输入和对应的输出了。单步调试的时候,算法每个步骤的中间结果基本也可以手算出来,然后和程序中的结果对比。但还是有些算法,不训练完是不知道模型是否正确的,这种情况就只能先用一些简单参数&小训练集搞一个比较弱但是也凑合能用的模型,在测试集上测试,如果弱爆了(预测结果跟随机一样),那就说明算法训练代码有问题。

 

1 0
原创粉丝点击