精华内容
下载资源
问答
  • PyTorch中带有MobileNet后端的RetinaFace推理代码 步骤1: cd cython python setup.py build_ext --inplace 第2步: python inference.py 评估(宽屏): Easy Val AP:0.8872715908531869 中值AP:0....
  • 更新2个模型 码:jks7 码:otx7 Update1 fix_gamma 在 mxnet 符号中,BN 有 ... 修复 bn gamma 模型已上传(/Retinaface/retinaface_mnet025_v1, /Retinaface/retinaface_mnet025_v2)。 更新 Retinaface 修复了 soft
  • PyTorch中的RetinaFace 实现:。 当Retinaface使用mobilenet0.25作为骨干网时,模型大小仅为1.7M。 我们还提供resnet50作为骨干网以获得更好的结果。 Mxnet中的官方代码可以在找到。 移动或边缘设备部署 从python...
  • Retinaface:人脸检测模型在Pytorch当中的实现 目录 性能情况 训练数据集 权值文件名称 测试数据集 输入图片大小 Easy Medium Hard Widerface-Train Retinaface_mobilenet0.25.pth Widerface-Val 1280x1280 89.76% ...
  • make Retinaface_NCNN ./Retinaface_NCNN test_carton.list make Retinaface_MNN ./Retinaface_MNN test_carton.list 增加NDK创建脚本和andrid armv7a库 可以构建android下的c ++嵌入程序,ncnn,MNN,opencv库采用...
  • 使用RetinaFace进行车牌检测 距离上次车牌检测模型更新已经过了一年多的时间,这段时间也有很多快速,准确的模型提出,我们利用单一物体检测算法Retinaface进行了车牌检测模型的训练,通过测试,检测效果和适用性都...
  • RetinaFace C ++重新实现源参考资源RetinaFace带有python代码。 模型转换工具MXNet2Caffe您需要自己添加一些层,并且在caffe中没有upsam RetinaFace C ++重新实现源参考资源RetinaFace用python代码提供在Insightface...
  • RetinaFace+arcface_torch人脸识别,效果比https://download.csdn.net/download/qq_26696715/16126478略差
  • >> > from fdet import io , RetinaFace >> > detector = RetinaFace ( backbone = 'RESNET50' ) >> > image = io . read_as_rgb ( 'path_to_image.jpg' ) >> > detector . detect ( image ) [{ 'box' : [ 511 , 47...
  • RetinaDetector是基于RetinaFace修改过的检测方法的,原论文是一种实用的单级面部检测器,最初在 数据 如下组织数据集目录: data/retinaface/ train/ images/ label.txt val/ images/ label.txt test/ ...
  • retinaface_mnet025_v2.zip

    2021-04-09 19:36:54
    pytorch-insightface retinaface_mnet025_v2预训练模型retinaface_mnet025_v2.zip
  • 在Tensorflow 2.0中重新实现了RetinaFaceRetinaFace:野外单阶段密集面Kong本地化,于2019年发布),并提供了预先训练的权重。 Resnet50主干。 原始纸-> 原始Mxnet实施-> 目录 示例输出: 安装 要安装依赖项,...
  • RetinaFace_Pytorch 用Pytorch重新实现RetinaFace 安装 克隆和安装要求 $ git clone https://github.com/supernotman/RetinaFace_Pytorch.git $ cd RetinaFace_Pytorch/ $ sudo pip install -r requirements.txt ...
  • 基于RetinaFace 当前模型 mobileNet V1 + FPN +上下文模块+回归器1.6MB CPU〜10FPS GPU 50FPU 火车:(请参考dataloader.py更改文件位置) python3 train.py -train该模型使用LS3D-W数据集,或将您的数据集更改为...
  • RetinaFace人脸识别.zip

    2021-03-26 14:06:39
    python RetinaFace人脸识别 可直接运行包含模型 缺失环境自己用pip安装
  • 人脸检测@ 500-1000 FPS 的100%Python3重新实现 ,在固体单次面本地化框架 。 用NumPy API替换了基于CUDA的锚点生成器功能。 通过dict来存储运行时锚,以避免重复计数。 通过向量计算方法优化了NMS算法。...
  • 视网膜面 此仓库基于构建 差异性 火车环已移至 IT增加了一组功能: 分布式培训 fp16 同步批次标准 ...from retinaface . pre_trained_models import get_model image = model = get_model ( "resnet50_
  • retinaFace caffe pycaffe 实现retinaFace caffe pycaffe 实现
  • retinaface_arcface.zip

    2021-04-09 11:34:05
    视频人脸识别 retinaface做人脸检测 定位,经过arcface进行人脸识别。 要求安装 pytorch
  • 基于RetinaFace+ArcFace的人脸识别测试和验证代码。可参考博客:https://blog.csdn.net/Bixiwen_liu/article/details/115677588?spm=1001.2014.3001.5501
  • retinaface_r50_v1.zip

    2021-04-09 19:35:13
    pytorch-insightface retinaface_r50_v1预训练模型retinaface_r50_v1.zip
  • retinaface_caffe 官方retinaface(mxnet实现): : 在使用前修改Makefile文件 网络模型的输入大小一般与检测速度反比,检测准确率正比 参考: :
  • 代码:https://github.com/deepinsight/insightface/tree/master/RetinaFace Pytorch复现:...RetinaFace于19年5月份出现,当时取得了state-of-the-art,可以说是目前开源的最强人脸检测算法,先看效果: ...

    论文:https://arxiv.org/pdf/1905.00641.pdf
    代码:https://github.com/deepinsight/insightface/tree/master/RetinaFace
    Pytorch复现:https://github.com/biubug6/Pytorch_Retinaface

    RetinaFace于19年5月份出现,当时取得了state-of-the-art,可以说是目前开源的最强人脸检测算法,它主要在以下五个方面作出贡献:

    • 在single-stage设计的基础上,提出了一种新的基于像素级的人脸定位方法RetinaFace,该方法采用多任务学习策略,同时预测人脸评分人脸框五个人脸关键点以及每个人脸像素的三维位置和对应关系

    • 在WILDER FACE hard子集上,RetinaFace的性能比目前the state of the art的two-stage方法(ISRN)的AP高出1.1% (AP等于91.4%)。

    • 在IJB-C数据集上,RetinaFace有助于提高ArcFace的验证精度(FAR=1e-6时TAR等于89:59%)。这表明更好的人脸定位可以显著提高人脸识别。

    • 通过使用轻量级backbone网络,RetinaFace可以在VGA分辨率的图片上实时运行

    • 已经发布了额外的注释和代码,以方便将来的研究

    RetinaFace

    与一般的目标检测不同,人脸检测具有较小的比例变化(从1:1到1:1.5),但更大的尺度变化(从几个像素到数千像素)。目前most state-of-the-art 的方法集中于single-stage设计,该设计密集采样人脸在特征金字塔上的位置和尺度,与two-stage方法相比,表现出良好的性能和更快的速度。在此基础上,我们改进了single-stage人脸检测框架,并利用强监督和自监督信号的多任务损失,提出了一种most state-of-the-art的密集人脸定位方法。RetinaFace的想法如图1所示。

    在这里插入图片描述
    由上图可以看到,RetinaFace的detect head有四个并行的分支:人脸分类,框回归,关键点检测和人脸像素的三维位置和对应关系

    RetinaFace的大致结构如下图:
    在这里插入图片描述

    一共有四个模块,分别是Backbone,FPN,Context ModuleMulti-task Loss。其中Multi-task Loss对应图1的四个并行分支Loss。

    Implementation details

    FPN
    RetinaFace采用从P2到P6的特征金字塔层,其中P2到P5通过使用自顶向下和横向连接(如[28,29])计算相应的ResNet残差阶段(C2到C5)的输出。P6是在C5处通过一个步长2的3x3卷积计算得到到。C1-C5是在ImageNet-11k数据集上预先训练好的ResNet-152[21]分类网络,P6是用“Xavier”方法[17]随机初始化的。

    Context Module
    RetinaFace在五个特征金字塔层应用单独的上下文模块来提高 感受野并增加刚性上下文建模的能力。从2018年 WIDER Face 冠军方案中受到的启发, 我们也在横向连接和使用可变形卷积网络(DCN)的上下文模块中替换所有 3x3的卷积,进一步加强非刚性的上下文建模能力。

    Multi-task Loss
    对于任何训练的anchor i,RetinaFace的目标是最小化下面的多任务的 loss:
    在这里插入图片描述
    包含四个部分:

    • 人脸分类loss Lcls(pi,pi*)。这里的pi是anchor i为人脸的预测概率,对于pi * 是1是positive anchor,0代表为negative anchor。分类loss Lcls采用softmax loss,即softmax loss在二分类的情况(人脸/非人脸)的应用。
    • 人脸框回归loss,Lbox(ti,ti*),这里的ti={tx,ty,tw,th},ti * ={tx *,ty *,tw * ,th *}分别代表positive anchor相关的预测框和真实框(ground-truth box)的坐标。我们按照 Fast r-cnn的方法对回归框目标(中心坐标,宽和高)进行归一化,使用Lbox(ti,ti *)=R(ti-ti *),这里R 是 smooth-L1 Loss
    • 人脸的landmark回归loss Lpts(li,li *),这里li={l x1,l y1,…l x5,l y5},li *={l x1 *,l y1 *,…l x5 *,l y5 *}代表预测的五个人脸关键点和基准点(ground-truth)。五个人脸关键点的回归也采用了基于anchor中心的目标归一化。
    • Dense回归loss Lpixel。

    loss调节参数 λ1-λ3 设置为0.25,0.1和0.01,这意味着在监督信号中,RetinaFace增加了边界框和关键点定位的重要性。

    anchors设置
    在这里插入图片描述
    如上表所示,RetinaFace从P2到P6的特征金字塔级别上使用特定于比例的锚点,例如[56]。 在这里,P2旨在通过平铺小anchors来捕获微小的面部,但要花费更多的计算时间,并且要冒更多的误报风险。RetinaFace将scale step设置为2^(1/3),aspect ratio设置为1:1。输入图像大小为 640*640 , anchors可以 覆盖 从16x16 到 406x406的特征金字塔层。从五个下采样(4,8,16,32,64)的feature map平铺anchors,每个feature map中的点预测3个anchors,总共有(160 * 160 + 80 * 80+40 * 40+400+100) * 3 = 102300个anchors,其中75%来自P2。不过在代码中,只用了8,16,32这三个下采样层的输出feature map,且每个点只放两个anchors。

    所以,对于640 * 640的输入,32,16,8的下采样输出,每个点的输出是【(1,4,20,20),(1,8,20,20),(1,20,20,20)】,【(1,4,40,40),(1,8,40,40),(1,20,40,40)】,【(1,4,80,80),(1,8,80,80),(1,20,80,80)】。
    其中4,8,20分别代表一个点两个anchors的类别数(2 anchors * 2类),(2 anchors * 框的信息),(2 anchors * 5个关键点信息(一个点x,y))

    数据增强
    WIDER FACE训练集中大约 有 20% 的小人脸 , RetinaFace遵循 [68, 49 ) 并从原始图像随机crop方形patches并调整这些 patches到 640*640 产生更大的训练人脸。更具体地说,在原始图像的短边[0.3,1]之间随机裁剪正方形patches。对于crop边界上的人脸,如果人脸框的中心在crop patches内,则保持人脸框的重叠部分。除了随机裁剪,我们还通过0.5概率的随机水平翻转和光度颜色蒸馏来增加训练数据。

    学习率调整
    使用warmup learning 策略,学习速率从10e-3,在5个epoch后上升到10e-2,然后在第55和第68个epochs时除以10。训练过程在第80个epochs结束。

    实验结果

    在这里插入图片描述
    RetinaFace与其他24个stage-of-the-art的人脸检测算法对比。RetinaFace在所有的验证集和测试集都达到的最好的AP,在验证集上的AP是96.9%(easy),96.1%(Medium)和91.8%(hard)。在测试集的AP是96.3%,95.6%,91.4%.相比与当前最好的方法(Improved selective refinement network for face detection)在困难的数据集(包含大量的小人脸)的AP对比(91.4% vs 90.3%)。

    在这里插入图片描述

    Retinaface是人脸识别得到更高的准确率。
    在这里插入图片描述
    此外,使用轻量Mobilenet-0.25作为backbone,对于VGA可以在CPU上达到实时。

    参考
    https://mp.weixin.qq.com/s/vGAQ4OXv_jHQJECG27o2SQ
    https://blog.csdn.net/TheDayIn_CSDN/article/details/95058236

    展开全文
  • retinaface人脸识别的年龄性别预测,包含模型 可直接运行 注意 如果缺少mxnet等类库 自行使用pip安装
  • RetinaFace-Pytorch源码阅读

    千次阅读 多人点赞 2020-05-27 14:12:13
    代码:https://github.com/deepinsight/insightface/tree/master/RetinaFace Pytorch复现:...关于RetinaFace理论知识请参考:RetinaFace论文解读 先回顾RetinaFace的结构,如下: 以上,Re

    论文:https://arxiv.org/pdf/1905.00641.pdf
    代码:https://github.com/deepinsight/insightface/tree/master/RetinaFace
    Pytorch复现:https://github.com/biubug6/Pytorch_Retinaface

    Pytorch真香~本文解读代码基于Pytorch复现版。关于RetinaFace理论知识请参考:RetinaFace论文解读
    先回顾RetinaFace的结构,如下:
    在这里插入图片描述
    以上,RetinaFace的结构主要由四个block组成,Backbone,FPN,Context Module,Loss计算四个部分,其中Context Module采用SSH结构。
    所以本文也依据RetinaFace的结构阅读其代码。

    注:该代码的head没有复现人脸像素的三维位置和对应关系这部分(说实话这部分看不太明白。。。),所以只关注人脸分类,框回归,landmark回归即可

    Backbone
    该复现代码使用MobileNetV1作为Backbone,如下:

    class MobileNetV1(nn.Module):
        def __init__(self):
            super(MobileNetV1, self).__init__()
            ''' stage1阶段进行三次下采样 '''
            self.stage1 = nn.Sequential(
                conv_bn(3, 8, 2, leaky = 0.1),    # 3
                conv_dw(8, 16, 1),   # 7
                conv_dw(16, 32, 2),  # 11
                conv_dw(32, 32, 1),  # 19
                conv_dw(32, 64, 2),  # 27
                conv_dw(64, 64, 1),  # 43
            )
            ''' stage2 进行一次下采样'''
            self.stage2 = nn.Sequential(
                conv_dw(64, 128, 2),  # 43 + 16 = 59
                conv_dw(128, 128, 1), # 59 + 32 = 91
                conv_dw(128, 128, 1), # 91 + 32 = 123
                conv_dw(128, 128, 1), # 123 + 32 = 155
                conv_dw(128, 128, 1), # 155 + 32 = 187
                conv_dw(128, 128, 1), # 187 + 32 = 219
            )
            ''' stage3进行一次下采样'''
            self.stage3 = nn.Sequential(
                conv_dw(128, 256, 2), # 219 +3 2 = 241
                conv_dw(256, 256, 1), # 241 + 64 = 301
            )
            self.avg = nn.AdaptiveAvgPool2d((1,1))
            self.fc = nn.Linear(256, 1000)
    
        def forward(self, x):
            x = self.stage1(x)
            x = self.stage2(x)
            x = self.stage3(x)
            x = self.avg(x)
            # x = self.model(x)
            x = x.view(-1, 256)
            x = self.fc(x)
            return x
    

    FPN

    class FPN(nn.Module):
        def __init__(self,in_channels_list,out_channels):
            super(FPN,self).__init__()
            leaky = 0
            if (out_channels <= 64):
                leaky = 0.1
            self.output1 = conv_bn1X1(in_channels_list[0], out_channels, stride = 1, leaky = leaky)
            self.output2 = conv_bn1X1(in_channels_list[1], out_channels, stride = 1, leaky = leaky)
            self.output3 = conv_bn1X1(in_channels_list[2], out_channels, stride = 1, leaky = leaky)
    
            self.merge1 = conv_bn(out_channels, out_channels, leaky = leaky)
            self.merge2 = conv_bn(out_channels, out_channels, leaky = leaky)
    
        def forward(self, input):
            # names = list(input.keys())
            input = list(input.values())
    
            output1 = self.output1(input[0])
            output2 = self.output2(input[1])
            output3 = self.output3(input[2])
    		''' 最后一层stage3的输出output3进行上采样,与stage2输出merge,形成output2'''
            up3 = F.interpolate(output3, size=[output2.size(2), output2.size(3)], mode="nearest")
            output2 = output2 + up3
            output2 = self.merge2(output2)
    		''' output2进行上采样与stage1的输出进行merge,形成output1'''
            up2 = F.interpolate(output2, size=[output1.size(2), output1.size(3)], mode="nearest")
            output1 = output1 + up2
            output1 = self.merge1(output1)
    	
            out = [output1, output2, output3]
            return out
    
    

    SSH

    class SSH(nn.Module):
        def __init__(self, in_channel, out_channel):
            super(SSH, self).__init__()
            assert out_channel % 4 == 0
            leaky = 0
            if (out_channel <= 64):
                leaky = 0.1
            self.conv3X3 = conv_bn_no_relu(in_channel, out_channel//2, stride=1)
    
            self.conv5X5_1 = conv_bn(in_channel, out_channel//4, stride=1, leaky = leaky)
            self.conv5X5_2 = conv_bn_no_relu(out_channel//4, out_channel//4, stride=1)
    
            self.conv7X7_2 = conv_bn(out_channel//4, out_channel//4, stride=1, leaky = leaky)
            self.conv7x7_3 = conv_bn_no_relu(out_channel//4, out_channel//4, stride=1)
    
        def forward(self, input):
            conv3X3 = self.conv3X3(input)
    
            conv5X5_1 = self.conv5X5_1(input)
            conv5X5 = self.conv5X5_2(conv5X5_1)
    
            conv7X7_2 = self.conv7X7_2(conv5X5_1)
            conv7X7 = self.conv7x7_3(conv7X7_2)
    
            out = torch.cat([conv3X3, conv5X5, conv7X7], dim=1)
            out = F.relu(out)
            return out
    

    定义了以上的Backbone,FPN,SSH,可以构建RetinaFace模型了。

    RetinaFace

    class RetinaFace(nn.Module):
        def __init__(self, cfg = None, phase = 'train'):
            """
            :param cfg:  Network related settings.
            :param phase: train or test.
            """
            super(RetinaFace,self).__init__()
            self.phase = phase
            backbone = None
            if cfg['name'] == 'mobilenet0.25':
                backbone = MobileNetV1()
                if cfg['pretrain']:
                    checkpoint = torch.load("./weights/mobilenetV1X0.25_pretrain.tar", map_location=torch.device('cpu'))
                    from collections import OrderedDict
                    new_state_dict = OrderedDict()
                    for k, v in checkpoint['state_dict'].items():
                        name = k[7:]  # remove module.
                        new_state_dict[name] = v
                    # load params
                    backbone.load_state_dict(new_state_dict)
            elif cfg['name'] == 'Resnet50':
                import torchvision.models as models
                backbone = models.resnet50(pretrained=cfg['pretrain'])
    
            self.body = _utils.IntermediateLayerGetter(backbone, cfg['return_layers'])
            in_channels_stage2 = cfg['in_channel']
            in_channels_list = [
                in_channels_stage2 * 2,
                in_channels_stage2 * 4,
                in_channels_stage2 * 8,
            ]
            out_channels = cfg['out_channel']
            self.fpn = FPN(in_channels_list,out_channels)
            self.ssh1 = SSH(out_channels, out_channels)
            self.ssh2 = SSH(out_channels, out_channels)
            self.ssh3 = SSH(out_channels, out_channels)
    
            self.ClassHead = self._make_class_head(fpn_num=3, inchannels=cfg['out_channel'])
            self.BboxHead = self._make_bbox_head(fpn_num=3, inchannels=cfg['out_channel'])
            self.LandmarkHead = self._make_landmark_head(fpn_num=3, inchannels=cfg['out_channel'])
    
        def _make_class_head(self,fpn_num=3,inchannels=64,anchor_num=2):
            classhead = nn.ModuleList()
            for i in range(fpn_num):
                classhead.append(ClassHead(inchannels,anchor_num))
            return classhead
        
        def _make_bbox_head(self,fpn_num=3,inchannels=64,anchor_num=2):
            bboxhead = nn.ModuleList()
            for i in range(fpn_num):
                bboxhead.append(BboxHead(inchannels,anchor_num))
            return bboxhead
    
        def _make_landmark_head(self,fpn_num=3,inchannels=64,anchor_num=2):
            landmarkhead = nn.ModuleList()
            for i in range(fpn_num):
                landmarkhead.append(LandmarkHead(inchannels,anchor_num))
            return landmarkhead
    	''' 构建RetinaFace模型'''
        def forward(self,inputs):
        	''' self.body 是mobilenetv1'''
            out = self.body(inputs)
    
            ''' self.fpn 是 fpn'''
            fpn = self.fpn(out)
    
            ''' self.ssh 是 ssh'''
            feature1 = self.ssh1(fpn[0])
            feature2 = self.ssh2(fpn[1])
            feature3 = self.ssh3(fpn[2])
            features = [feature1, feature2, feature3]
    
            bbox_regressions = torch.cat([self.BboxHead[i](feature) for i, feature in enumerate(features)], dim=1)
            classifications = torch.cat([self.ClassHead[i](feature) for i, feature in enumerate(features)],dim=1)
            ldm_regressions = torch.cat([self.LandmarkHead[i](feature) for i, feature in enumerate(features)], dim=1)
    
            if self.phase == 'train':
                output = (bbox_regressions, classifications, ldm_regressions)
            else:
                output = (bbox_regressions, F.softmax(classifications, dim=-1), ldm_regressions)
            return output
    

    下面根据代码直观展示RetinaFace的整体结构,我借鉴大佬@茴香豆502的图片,如有侵权,请联系删除
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    附上代码里打印出来的网络结构如下:

    RetinaFace(
    ''' body '''
      (body): IntermediateLayerGetter(
        (stage1): Sequential(
          (0): Sequential(
            (0): Conv2d(3, 8, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
            (1): BatchNorm2d(8, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (2): LeakyReLU(negative_slope=0.1, inplace=True)
          )
          (1): Sequential(
            (0): Conv2d(8, 8, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), groups=8, bias=False)
            (1): BatchNorm2d(8, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (2): LeakyReLU(negative_slope=0.1, inplace=True)
            (3): Conv2d(8, 16, kernel_size=(1, 1), stride=(1, 1), bias=False)
            (4): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (5): LeakyReLU(negative_slope=0.1, inplace=True)
          )
          (2): Sequential(
            (0): Conv2d(16, 16, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), groups=16, bias=False)
            (1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (2): LeakyReLU(negative_slope=0.1, inplace=True)
            (3): Conv2d(16, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)
            (4): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (5): LeakyReLU(negative_slope=0.1, inplace=True)
          )
          (3): Sequential(
            (0): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), groups=32, bias=False)
            (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (2): LeakyReLU(negative_slope=0.1, inplace=True)
            (3): Conv2d(32, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)
            (4): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (5): LeakyReLU(negative_slope=0.1, inplace=True)
          )
          (4): Sequential(
            (0): Conv2d(32, 32, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), groups=32, bias=False)
            (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (2): LeakyReLU(negative_slope=0.1, inplace=True)
            (3): Conv2d(32, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
            (4): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (5): LeakyReLU(negative_slope=0.1, inplace=True)
          )
          (5): Sequential(
            (0): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), groups=64, bias=False)
            (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (2): LeakyReLU(negative_slope=0.1, inplace=True)
            (3): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
            (4): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (5): LeakyReLU(negative_slope=0.1, inplace=True)
          )
        )
        (stage2): Sequential(
          (0): Sequential(
            (0): Conv2d(64, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), groups=64, bias=False)
            (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (2): LeakyReLU(negative_slope=0.1, inplace=True)
            (3): Conv2d(64, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
            (4): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (5): LeakyReLU(negative_slope=0.1, inplace=True)
          )
          (1): Sequential(
            (0): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), groups=128, bias=False)
            (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (2): LeakyReLU(negative_slope=0.1, inplace=True)
            (3): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
            (4): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (5): LeakyReLU(negative_slope=0.1, inplace=True)
          )
          (2): Sequential(
            (0): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), groups=128, bias=False)
            (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (2): LeakyReLU(negative_slope=0.1, inplace=True)
            (3): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
            (4): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (5): LeakyReLU(negative_slope=0.1, inplace=True)
          )
          (3): Sequential(
            (0): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), groups=128, bias=False)
            (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (2): LeakyReLU(negative_slope=0.1, inplace=True)
            (3): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
            (4): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (5): LeakyReLU(negative_slope=0.1, inplace=True)
          )
          (4): Sequential(
            (0): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), groups=128, bias=False)
            (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (2): LeakyReLU(negative_slope=0.1, inplace=True)
            (3): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
            (4): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (5): LeakyReLU(negative_slope=0.1, inplace=True)
          )
          (5): Sequential(
            (0): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), groups=128, bias=False)
            (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (2): LeakyReLU(negative_slope=0.1, inplace=True)
            (3): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
            (4): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (5): LeakyReLU(negative_slope=0.1, inplace=True)
          )
        )
        (stage3): Sequential(
          (0): Sequential(
            (0): Conv2d(128, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), groups=128, bias=False)
            (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (2): LeakyReLU(negative_slope=0.1, inplace=True)
            (3): Conv2d(128, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
            (4): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (5): LeakyReLU(negative_slope=0.1, inplace=True)
          )
          (1): Sequential(
            (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), groups=256, bias=False)
            (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (2): LeakyReLU(negative_slope=0.1, inplace=True)
            (3): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
            (4): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (5): LeakyReLU(negative_slope=0.1, inplace=True)
          )
        )
      )
      ''' fpn '''
      (fpn): FPN(
        (output1): Sequential(
          (0): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
          (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): LeakyReLU(negative_slope=0.1, inplace=True)
        )
        (output2): Sequential(
          (0): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
          (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): LeakyReLU(negative_slope=0.1, inplace=True)
        )
        (output3): Sequential(
          (0): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
          (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): LeakyReLU(negative_slope=0.1, inplace=True)
        )
        (merge1): Sequential(
          (0): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): LeakyReLU(negative_slope=0.1, inplace=True)
        )
        (merge2): Sequential(
          (0): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): LeakyReLU(negative_slope=0.1, inplace=True)
        )
      )
      ''' ssh '''
      (ssh1): SSH(
        (conv3X3): Sequential(
          (0): Conv2d(64, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (conv5X5_1): Sequential(
          (0): Conv2d(64, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): LeakyReLU(negative_slope=0.1, inplace=True)
        )
        (conv5X5_2): Sequential(
          (0): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (conv7X7_2): Sequential(
          (0): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): LeakyReLU(negative_slope=0.1, inplace=True)
        )
        (conv7x7_3): Sequential(
          (0): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
      )
      (ssh2): SSH(
        (conv3X3): Sequential(
          (0): Conv2d(64, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (conv5X5_1): Sequential(
          (0): Conv2d(64, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): LeakyReLU(negative_slope=0.1, inplace=True)
        )
        (conv5X5_2): Sequential(
          (0): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (conv7X7_2): Sequential(
          (0): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): LeakyReLU(negative_slope=0.1, inplace=True)
        )
        (conv7x7_3): Sequential(
          (0): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
      )
      (ssh3): SSH(
        (conv3X3): Sequential(
          (0): Conv2d(64, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (conv5X5_1): Sequential(
          (0): Conv2d(64, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): LeakyReLU(negative_slope=0.1, inplace=True)
        )
        (conv5X5_2): Sequential(
          (0): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
        (conv7X7_2): Sequential(
          (0): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): LeakyReLU(negative_slope=0.1, inplace=True)
        )
        (conv7x7_3): Sequential(
          (0): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
          (1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
      )
      ''' ClassHead '''
      (ClassHead): ModuleList(
        (0): ClassHead(
          (conv1x1): Conv2d(64, 4, kernel_size=(1, 1), stride=(1, 1))
        )
        (1): ClassHead(
          (conv1x1): Conv2d(64, 4, kernel_size=(1, 1), stride=(1, 1))
        )
        (2): ClassHead(
          (conv1x1): Conv2d(64, 4, kernel_size=(1, 1), stride=(1, 1))
        )
      )
      ''' BboxHead '''
      (BboxHead): ModuleList(
        (0): BboxHead(
          (conv1x1): Conv2d(64, 8, kernel_size=(1, 1), stride=(1, 1))
        )
        (1): BboxHead(
          (conv1x1): Conv2d(64, 8, kernel_size=(1, 1), stride=(1, 1))
        )
        (2): BboxHead(
          (conv1x1): Conv2d(64, 8, kernel_size=(1, 1), stride=(1, 1))
        )
      )
       ''' LandmarkHead '''
      (LandmarkHead): ModuleList(
        (0): LandmarkHead(
          (conv1x1): Conv2d(64, 20, kernel_size=(1, 1), stride=(1, 1))
        )
        (1): LandmarkHead(
          (conv1x1): Conv2d(64, 20, kernel_size=(1, 1), stride=(1, 1))
        )
        (2): LandmarkHead(
          (conv1x1): Conv2d(64, 20, kernel_size=(1, 1), stride=(1, 1))
        )
      )
    )
    

    参考
    https://editor.csdn.net/md?articleId=106363088
    https://github.com/biubug6/Pytorch_Retinaface

    展开全文
  • RetinaFace_Pytorch.rar

    2020-05-22 17:50:52
    适应小人脸检测的最新人脸检测代码,pytorch实现可以直接运行尤其是密集人脸检测准确度极高和无遗漏,属于轻量级人脸检测的模型
  • 人脸检测之Retinaface算法:论文阅读及源码解析

    千次阅读 多人点赞 2020-10-18 21:42:32
    文章目录前言一、Retinaface论文1.1 论文信息1.2 论文摘要翻译二、论文实现源码解析2.1 网络框架结构2.2 主干网络(Backbone)功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一...

    前言

         RetinaFace 是2019年5月份出现的人脸检测算法,当时取得了state-of-the-art,作者也开源了代码。本篇讲述内容包含两个部分:Retinaface论文的核心要点以及github上运用Pytorch框架复现的源码。
         本系列所有代码是用python3编写,可在平台Anaconda中运行实现,在使用代码时,默认你已经安装相关的python库。本篇对源码的解析完全是基于我的个人理解,如有问题,欢迎指出。

    一、Retinaface论文

    1.1 论文信息

    1.2 论文摘要翻译

         虽然在不受控条件下的人脸检测已经取得了非常显著的进展,在自然环境下准确有效的人脸检测依然具有挑战。本文提出了一种单步(single-stage)人脸检测器,取名RetinaFace,通过联合外监督(extra-supervised)和自监督(self-supervised)的多任务学习,RetinaFace对各种尺度条件下的人脸可以做到像素级别的定位(localisation)。尤其是,本文作了以下5个方面的贡献:

    • (1)我们在WIDER FACE数据集上手动注释五个人脸关键点,并在外监督信号的帮助下获得难人脸检测的提升。
    • (2)我们进一步添加自监督网格解码器分支,用于与现有监督分支并行地预测一个逐像素的3D人脸信息。
    • (3) 在WIDER FACE测试集上,RetinaFace的性能优于最好模型的AP1.1%,达到91.4%。
    • (4)在IJB-C测试集上,RetinaFace使最先进的人脸认证(ArcFace)能够改善他们在面部验证中的结果(FAR = 1e-6,TAR = 89.59%)。
    • (5)通过采用轻量级骨干网络,RetinaFace可以在单个CPU对VGA分辨率的图像实时运行。

    二、论文实现源码解析

    2.1 网络框架结构

    图2.1 retinaface网络框架结构

    图2.1 retinaface网络框架结构
         如上图2.1展示,Retinaface网络对待检测的图片①首先使用mobilenet0.25或者Resnet50进行主干特征网络提取,②然后使用FPN(Feature Pyramid Network)和SSH(Single Stage Headless)进行加强特征提取,③其次使用ClassHead、BoxHead、LandmarkHead网络从特征获取预测结果,最后④对预测结果进行decode解码并⑤通过NMS非极大抑制去除重复检测值得出最终结果。以下是对网络框架结构源码的详细解析。

         整个源码的讲解以对一张(719,1283,3)尺寸的图片为检测示例,该图如下所示:
    在这里插入图片描述

    待检测的图片

         我们使用Retinaface对该图片进行检测时,检测的每层网络主要干了什么事、输出什么样的结果如下图所示:
    在这里插入图片描述

    网络层总流程

         下面我们对每层网络结构源码做分析:

    2.2 主干网络(Backbone)

         Retinaface在第一阶段训练的时候要通过主干特征网络Backbone,实际训练的时候会提供两种主干网络mobilenet0.25和Resnet50(https://github.com/keras-team/keras-applications)。使用Resnet50检测具有更高精度,而使用轻量级神经网络mobilenet0.25可以在cpu上进行实时检测。
         本文以mobilenet0.25作为主干特征网络。MobileNets是为移动和嵌入式设备提出的高效模型。MobileNets基于流线型架构(streamlined),使用深度可分离卷积(depthwise separable convolutions)来构建轻量级深度神经网络。由下参数计算个数可见,深度可分离卷积计算参数量更小。

    1) 普通卷积

         普通卷积采用kernel=3x3,paddind=1。其参数计算公式如下:假设输入通道为16、输出通道为32,所需参数为16x3x3x32=4608个

    def conv_bn(inp, oup, stride = 1, leaky = 0.1):
        return nn.Sequential(
            nn.Conv2d(inp, oup, 3, stride, 1, bias=False),
            nn.BatchNorm2d(oup),
            nn.LeakyReLU(negative_slope=leaky, inplace=True)
        )
    
    2) 深度可分离卷积

         深度可分离卷积由kernel=3x3进行逐通道卷积depthwise conv和kernel=1x1进行逐点卷积pointwise conv两部分组合。与常规卷积相比,其参数数量和运算成本比较低。其参数计算公式如下:假设输入通道为16,输出通道为32,则逐通道卷积参数:16x3x3=144;逐点卷积参数:16x1x1*32=512,共144+512=656个

    # 深度可分离卷积块depthwise separable convolution(depthwise conv + pointwise conv)
    def conv_dw(inp, oup, stride = 1, leaky=0.1):
        return nn.Sequential(
            # depthwise conv 逐通道卷积,用groups=inp
            nn.Conv2d(inp, inp, 3, stride, 1, groups=inp, bias=False),
            nn.BatchNorm2d(inp),
            nn.LeakyReLU(negative_slope= leaky,inplace=True),
    
            # pointwise conv 逐点卷积
            nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
            nn.BatchNorm2d(oup),
            nn.LeakyReLU(negative_slope= leaky,inplace=True),
        )
    
    3) mobilenet0.25主干网络

         主干网络对输入的检测图像,进行三个步骤stage1、stage2和stage3的卷积:

    class MobileNetV1(nn.Module):
        def __init__(self):
            super(MobileNetV1, self).__init__()
            # 640,640,3 -> 80,80,64
            self.stage1 = nn.Sequential(
                # 640,640,3 -> 320,320,8
                conv_bn(3, 8, 2, leaky = 0.1),    # 3
                # 320,320,8 -> 320,320,16
                conv_dw(8, 16, 1),   # 7
    
                # 320,320,16 -> 160,160,32
                conv_dw(16, 32, 2),  # 11
                conv_dw(32, 32, 1),  # 19
    
                # 160,160,32 -> 80,80,64
                conv_dw(32, 64, 2),  # 27
                conv_dw(64, 64, 1),  # 43
            )
                # 80,80,64 -> 40,40,128
            self.stage2 = nn.Sequential(
                conv_dw(64, 128, 2),  # 43 + 16 = 59
                conv_dw(128, 128, 1), # 59 + 32 = 91
                conv_dw(128, 128, 1), # 91 + 32 = 123
                conv_dw(128, 128, 1), # 123 + 32 = 155
                conv_dw(128, 128, 1), # 155 + 32 = 187
                conv_dw(128, 128, 1), # 187 + 32 = 219
            )
                # 40,40,128 -> 20,20,256
            self.stage3 = nn.Sequential(
                conv_dw(128, 256, 2), # 219 +3 2 = 241
                conv_dw(256, 256, 1), # 241 + 64 = 301
            )
            self.avg = nn.AdaptiveAvgPool2d((1,1))
            self.fc = nn.Linear(256, 1000)
    
        def forward(self, x):
            x = self.stage1(x)
            x = self.stage2(x)
            x = self.stage3(x)
            x = self.avg(x)
            # x = self.model(x)
            x = x.view(-1, 256)
            x = self.fc(x)
            return x
    

         图像通过主干特征网络进行三阶段特征提取示意图如下2.2所示:
    在这里插入图片描述

    图2.2 Backbone网络特征结构

    2.3 FPN网络(特征金字塔网络)

         FPN(Feature pyramid network)特征金字塔网络会对主干网络输出的特征层进行1x1卷积后的通道数调整以及上采样+特征融合来进行特征加强提取。其中普通卷积和1x1卷积如下:

    def conv_bn(inp, oup, stride = 1, leaky = 0):
        return nn.Sequential(
            nn.Conv2d(inp, oup, 3, stride, 1, bias=False),
            nn.BatchNorm2d(oup),
            nn.LeakyReLU(negative_slope=leaky, inplace=True)
        )
    
    def conv_bn_no_relu(inp, oup, stride):
        return nn.Sequential(
            nn.Conv2d(inp, oup, 3, stride, 1, bias=False),
            nn.BatchNorm2d(oup),
        )
    
    def conv_bn1X1(inp, oup, stride, leaky=0):
        return nn.Sequential(
            nn.Conv2d(inp, oup, 1, stride, padding=0, bias=False),
            nn.BatchNorm2d(oup),
            nn.LeakyReLU(negative_slope=leaky, inplace=True)
        )
    

    特征金字塔网络结构如下:

    class FPN(nn.Module):
        def __init__(self, in_channels_list, out_channels):
            super(FPN,self).__init__()
            leaky = 0
            if (out_channels <= 64):
                leaky = 0.1
            #通道数调整
            self.output1 = conv_bn1X1(in_channels_list[0], out_channels, stride = 1, leaky = leaky)
            self.output2 = conv_bn1X1(in_channels_list[1], out_channels, stride = 1, leaky = leaky)
            self.output3 = conv_bn1X1(in_channels_list[2], out_channels, stride = 1, leaky = leaky)
    
            self.merge1 = conv_bn(out_channels, out_channels, leaky = leaky)
            self.merge2 = conv_bn(out_channels, out_channels, leaky = leaky)
    
        def forward(self, inputs):
            # names = list(inputs.keys())
            inputs = list(inputs.values())
    
            output1 = self.output1(inputs[0])
            output2 = self.output2(inputs[1])
            output3 = self.output3(inputs[2])
    
            #上采样+特征融合
            up3 = F.interpolate(output3, size=[output2.size(2), output2.size(3)], mode="nearest")
            output2 = output2 + up3
            output2 = self.merge2(output2)
    
            #上采样+特征融合
            up2 = F.interpolate(output2, size=[output1.size(2), output1.size(3)], mode="nearest")
            output1 = output1 + up2
            output1 = self.merge1(output1)
    
            out = [output1, output2, output3]
            return out
    

         通过特征金字塔网络特征提取示意图如下2.3所示:
    在这里插入图片描述

    图2.3 FPN网络特征输出

    2.4 SSH网络

         SSH(Single Stage Headless)网络对特征层继续进一步加强特征提取。SSH使用并行的三个卷积:第一个是3x3卷积,第二个是用两次3x3卷积代替5x5卷积,第三个是用3次3x3卷积代替5x5卷积。源码如下:

    # single stage headless
    class SSH(nn.Module):
        def __init__(self, in_channel, out_channel):
            super(SSH, self).__init__()
            assert out_channel % 4 == 0
            leaky = 0
            if (out_channel <= 64):
                leaky = 0.1
            self.conv3X3 = conv_bn_no_relu(in_channel, out_channel//2, stride=1)
    
            self.conv5X5_1 = conv_bn(in_channel, out_channel//4, stride=1, leaky = leaky)
            self.conv5X5_2 = conv_bn_no_relu(out_channel//4, out_channel//4, stride=1)
    
            self.conv7X7_2 = conv_bn(out_channel//4, out_channel//4, stride=1, leaky = leaky)
            self.conv7x7_3 = conv_bn_no_relu(out_channel//4, out_channel//4, stride=1)
    
        def forward(self, inputs):
            conv3X3 = self.conv3X3(inputs)
    
            conv5X5_1 = self.conv5X5_1(inputs)
            conv5X5 = self.conv5X5_2(conv5X5_1)
    
            conv7X7_2 = self.conv7X7_2(conv5X5_1)
            conv7X7 = self.conv7x7_3(conv7X7_2)
    
            out = torch.cat([conv3X3, conv5X5, conv7X7], dim=1)
            out = F.relu(out)
            return out
    

         通过SSH网络特征提取示意图如下2.4所示:
    在这里插入图片描述

    图2.4 SSH网络特征输出

    2.5 分类、预测框、关键点特征提取(ClassHead/BoxHead/LandmarkHead)

         ClassHead是提取先验框是否包含人脸,BoxHead是先验位置检测、LandmarkHead是人脸关键点位置检测。

    # 先验框是否包含人脸num_anchorx2
    class ClassHead(nn.Module):
        def __init__(self,inchannels=512,num_anchors=2):
            super(ClassHead,self).__init__()
            self.num_anchors = num_anchors
            self.conv1x1 = nn.Conv2d(inchannels,self.num_anchors*2,kernel_size=(1,1),stride=1,padding=0)
    
        def forward(self,x):
            out = self.conv1x1(x)
            out = out.permute(0,2,3,1).contiguous() #维度转换
            
            return out.view(out.shape[0], -1, 2)
    
    # 先验框的box,num_anchorx4
    class BboxHead(nn.Module):
        def __init__(self,inchannels=512,num_anchors=2):
            super(BboxHead,self).__init__()
            self.conv1x1 = nn.Conv2d(inchannels,num_anchors*4,kernel_size=(1,1),stride=1,padding=0)
    
        def forward(self,x):
            out = self.conv1x1(x)
            out = out.permute(0,2,3,1).contiguous()
    
            return out.view(out.shape[0], -1, 4)
    
    # 人脸关键点, num_anchorx10
    class LandmarkHead(nn.Module):
        def __init__(self,inchannels=512,num_anchors=2):
            super(LandmarkHead,self).__init__()
            self.conv1x1 = nn.Conv2d(inchannels,num_anchors*10,kernel_size=(1,1),stride=1,padding=0)
    
        def forward(self,x):
            out = self.conv1x1(x)
            out = out.permute(0,2,3,1).contiguous()
    
            return out.view(out.shape[0], -1, 10)
    

         通过ClassHead/BoxHead/LandmarkHead特征提取示意图如下2.5所示:
    在这里插入图片描述

    图2.5 检测特征输出

    2.6 decode(解码进行预测结果修正)

         通过上一步的预测我们获得的是三个有效的特征层,
    我们需要对预测框和人脸关键点检测进行位置调整。根据encode的公式可以反推出decode解码公式,如下图2.6所示:
    在这里插入图片描述

    图2.6 decode解码公式

         对先验框进行调整源码如下:

    # 对先验框进行调整,获得中心预测框 
    def decode(loc, priors, variances):
        # @loc:location predictions for loc layers,[37840, 4]
        # @priors:先验框 [37840, 4]
        # @variances:方差[0.1, 0.2]
        boxes = torch.cat((priors[:, :2] + loc[:, :2] * variances[0] * priors[:, 2:], #中心调整按公式 
                  priors[:, 2:] * torch.exp(variances[1] * loc[:, 2:])), dim=1) #长宽调整按公式
        
        # 转换坐标为左上角和右下角
        boxes[:, :2] -= boxes[:, 2:] / 2
        boxes[:, 2:] += boxes[:, :2]
        return boxes
    
    # 对先验框进行调整,获得人脸关键点
    def decode_landm(pre, priors, variances):
        # @pre: [37840, 10]
        landms = torch.cat((priors[:, :2] + priors[:, 2:] * variances[0] * pre[:, :2],
                            priors[:, :2] + priors[:, 2:] * variances[0] * pre[:, 2:4],
                            priors[:, :2] + priors[:, 2:] * variances[0] * pre[:, 4:6],
                            priors[:, :2] + priors[:, 2:] * variances[0] * pre[:, 6:8],
                            priors[:, :2] + priors[:, 2:] * variances[0] * pre[:, 8:10],
                           ), dim=1)
    
        return landms
    

         对先验框进行调整后,拼接分类、预测框和人脸关键点检测结果如下示意图2.7所示:
    在这里插入图片描述

    图2.7 decode解码

    2.7 NMS(非极大抑制)

         最后通过非极大抑制对解码结果去除重合度较高的预测框:

    # 非极大抑制
    def non_max_suppression(boxes, conf_thres=0.5, nms_thres=0.3):
        detection = boxes
        # 取阈值大于置信度的框
        mask = detection[:, 4] >= conf_thres
        detection = detection[mask]
        if not np.shape(detection)[0]:
            return []
        
        best_box = []
        # 对置信度从高至低排序
        scores = detection[:,4]
        arg_sort = np.argsort(scores)[::-1]
        detection = detection[arg_sort]
        
        # 去除重合度比较大的框
        while np.shape(detection)[0] > 0:
            best_box.append(detection[0])
            if len(detection) == 1:
                break
            ious = iou(best_box[-1], detection[1:])
            detection = detection[1:][ious < nms_thres]
            
        return np.array(best_box)
    
    # 交并比计算
    def iou(b1, b2):
        b1_x1, b1_y1, b1_x2, b1_y2 = b1[0], b1[1], b1[2], b1[3]
        b2_x1, b2_y1, b2_x2, b2_y2 = b2[:, 0], b2[:, 1], b2[:, 2], b2[:, 3]
        
        inter_rect_x1 = np.maximum(b1_x1, b2_x1)
        inter_rect_y1 = np.maximum(b1_y1, b2_y1)  
        inter_rect_x2 = np.minimum(b1_x2, b2_x2)  
        inter_rect_y2 = np.minimum(b1_y2, b2_y2)
        
        inter_area = np.maximum(inter_rect_x2 - inter_rect_x1, 0) * np.maximum(inter_rect_y2 - inter_rect_y1, 0)
        
        area_b1 = (b1_x2 - b1_x1) * (b1_y2 - b1_y1)
        area_b2 = (b2_x2 - b2_x1) * (b2_y2 - b2_y1)
        
        iou = inter_area / np.maximum((area_b1 + area_b2 - inter_area), 1e-6)
        return iou
    

         通过非极大抑制将原先37840个预测结果缩减为49个。如下示意图2.8所示:
    在这里插入图片描述

    图2.8 NMS非极大抑制

    2.8 Retinaface网络结构

         Retinaface的整个网络即时将之前的各层网络进行组合:

    class RetinaFace(nn.Module):
        def __init__(self, cfg = None, pretrained = False, phase = 'train'):
            """
            :param cfg:  Network related settings.
            :param phase: train or test.
            """
            super(RetinaFace,self).__init__()
            self.phase = phase
            backbone = None
            if cfg['name'] == 'mobilenet0.25':
                backbone = MobileNetV1()
                if pretrained:
                    checkpoint = torch.load("./model_data/mobilenetV1X0.25_pretrain.tar", map_location=torch.device('cpu'))
                    from collections import OrderedDict
                    new_state_dict = OrderedDict()
                    for k, v in checkpoint['state_dict'].items():
                        name = k[7:]  # remove module.
                        new_state_dict[name] = v
                    # load params
                    backbone.load_state_dict(new_state_dict)
            elif cfg['name'] == 'Resnet50':
                backbone = models.resnet50(pretrained=pretrained)
            
            # 'return_layers': {'stage1': 1, 'stage2': 2, 'stage3': 3}
            self.body = _utils.IntermediateLayerGetter(backbone, cfg['return_layers'])
    
            in_channels_stage2 = cfg['in_channel']
            in_channels_list = [
                in_channels_stage2 * 2,
                in_channels_stage2 * 4,
                in_channels_stage2 * 8,
            ]
            out_channels = cfg['out_channel']
            self.fpn = FPN(in_channels_list,out_channels)
            self.ssh1 = SSH(out_channels, out_channels)
            self.ssh2 = SSH(out_channels, out_channels)
            self.ssh3 = SSH(out_channels, out_channels)
    
            self.ClassHead = self._make_class_head(fpn_num=3, inchannels=cfg['out_channel'])
            self.BboxHead = self._make_bbox_head(fpn_num=3, inchannels=cfg['out_channel'])
            self.LandmarkHead = self._make_landmark_head(fpn_num=3, inchannels=cfg['out_channel'])
    
        def _make_class_head(self,fpn_num=3,inchannels=64,anchor_num=2):
            classhead = nn.ModuleList()
            for i in range(fpn_num):
                classhead.append(ClassHead(inchannels,anchor_num))
            return classhead
        
        def _make_bbox_head(self,fpn_num=3,inchannels=64,anchor_num=2):
            bboxhead = nn.ModuleList()
            for i in range(fpn_num):
                bboxhead.append(BboxHead(inchannels,anchor_num))
            return bboxhead
    
        def _make_landmark_head(self,fpn_num=3,inchannels=64,anchor_num=2):
            landmarkhead = nn.ModuleList()
            for i in range(fpn_num):
                landmarkhead.append(LandmarkHead(inchannels,anchor_num))
            return landmarkhead
    
        def forward(self,inputs):
            out = self.body(inputs)
    
            # FPN
            fpn = self.fpn(out)
    
            # SSH
            feature1 = self.ssh1(fpn[0])
            feature2 = self.ssh2(fpn[1])
            feature3 = self.ssh3(fpn[2])
            features = [feature1, feature2, feature3]
    
            bbox_regressions = torch.cat([self.BboxHead[i](feature) for i, feature in enumerate(features)], dim=1)
            classifications = torch.cat([self.ClassHead[i](feature) for i, feature in enumerate(features)], dim=1)
            ldm_regressions = torch.cat([self.LandmarkHead[i](feature) for i, feature in enumerate(features)], dim=1)
    
            if self.phase == 'train':
                output = (bbox_regressions, classifications, ldm_regressions)
            else:
                output = (bbox_regressions, F.softmax(classifications, dim=-1), ldm_regressions)
            return output
    

    2.9 图像人脸检测

         通过载入已经训练好的模型,对图像进行人脸检测,并绘制出检测框和人脸关键点,检测过程源码如下:

    class RetinafaceEx(object):
        _defaults = {
            "model_path": 'model_data/Retinaface_mobilenet0.25.pth',
            "confidence": 0.5,
            "backbone": "mobilenet",
            "cuda": False
        }
        
        def __init__(self, **kwargs):
            self.__dict__.update(self._defaults)
            self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
            self.cfg = cfg_mnet
            self.generate()    
            
        def generate(self):
            self.net = RetinaFace(cfg=self.cfg, phase='eval').eval() #测试模型时在前面使用,不启用 BatchNormalization 和 Dropout
            print('Loading weights into state dict...')
            state_dict = torch.load(self.model_path, map_location=self.device)
            self.net.load_state_dict(state_dict)
            print("finish!")
            
        def detect_image(self, image):
            # 绘制人脸框
            old_image = image.copy()
            
            image = np.array(image, np.float32)
            print("image.shape:", image.shape) #(719, 1280, 3)
            im_height, im_width, _ = np.shape(image)
            
            # 将归一化后的框坐标转换成原图的大小
            scale = torch.Tensor([np.shape(image)[1], np.shape(image)[0], np.shape(image)[1], np.shape(image)[0]])
            print("scale:", scale) # tensor([1280.,  719., 1280.,  719.])
            scale_for_landmarks = torch.Tensor([np.shape(image)[1], np.shape(image)[0], np.shape(image)[1], np.shape(image)[0],
                                                np.shape(image)[1], np.shape(image)[0], np.shape(image)[1], np.shape(image)[0],
                                                np.shape(image)[1], np.shape(image)[0]])
            print("scale_for_landmarks:", scale_for_landmarks) #tensor([1280.,  719., 1280.,  719., 1280.,  719., 1280.,  719., 1280.,  719.])
            
            # 预处理
            image = preprocess_input(image).transpose(2, 0, 1) #(3,719, 1280)
            # 增加batch_size维度
            image = torch.from_numpy(image).unsqueeze(0) 
            print("batch.image:", image.shape) #(1, 3, 719, 1280)
            # 计算先验框
            anchors = Anchors(self.cfg, image_size=(im_height, im_width)).get_anchors()
            print("anchors.shape:", anchors.shape) # torch.Size([37840, 4])
    
            with torch.no_grad():
                loc, conf, landms = self.net(image)  # forward pass: boxHead, classHead, landmarkHead
                print("loc.shape:", loc.shape) # bbox torch.Size([1, 37840, 4])
                print("conf.shape:", conf.shape) # classification torch.Size([1, 37840, 2])           
                print("landms.shape:", landms.shape, "\n") # landmark torch.Size([1, 37840, 10])     
                
                # 预测框(解码)
                boxes = decode(loc.data.squeeze(0), anchors, self.cfg['variance'])
                boxes = boxes * scale
                boxes = boxes.cpu().numpy()
                print("type(boxes):", type(boxes), boxes.shape)
                print("boxes:", boxes) # (37840, 4)
                
                # 置信度
                conf = conf.data.squeeze(0)[:, 1:2].cpu().numpy()
                print("conf.shape:", conf.shape) #(37840, 1)
                
                # 人脸关键点解码
                landms = decode_landm(landms.data.squeeze(0), anchors, self.cfg['variance'])
                landms = landms * scale_for_landmarks
                landms = landms.cpu().numpy()
                print("landms.shape:", landms.shape) #(37840, 10)
                
                # 非极大抑制
                boxes_conf_landms = np.concatenate([boxes, conf, landms], -1) 
                print("boxes_conf_landms1.shape:", boxes_conf_landms.shape) #(37840, 15)
                boxes_conf_landms = non_max_suppression(boxes_conf_landms, self.confidence)
                print("boxes_conf_landms2.shape:", boxes_conf_landms.shape) #(49, 15)
                
            for b in boxes_conf_landms:
                text = "{:.4f}".format(b[4])
                b = list(map(int, b)) #转换为整数
                # 人脸框绘制
                cv2.rectangle(old_image, (b[0], b[1]), (b[2], b[3]), (0,0,255), 2)
                cx = b[0]
                cy = b[1] + 12
                cv2.putText(old_image, text, (cx, cy), cv2.FONT_HERSHEY_DUPLEX, 0.5, (255,255,255))
                
                # 关键点绘制
                cv2.circle(old_image, (b[5], b[6]), 1, (0,0,255), 4)
                cv2.circle(old_image, (b[7], b[8]), 1, (0,255,255), 4)            
                cv2.circle(old_image, (b[9], b[10]), 1, (255,0,255), 4)            
                cv2.circle(old_image, (b[11], b[12]), 1, (0,255,0), 4)            
                cv2.circle(old_image, (b[13], b[14]), 1, (255,0,0), 4)            
                
            return old_image
    
    ##### ####################
    # 人脸检测测试
    imgPath = "img/timg.jpg"
    image = cv2.imread(imgPath)
    print("image.shape:", image.shape) #(719, 1280, 3)
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    retinaface = RetinafaceEx()
    r_image = retinaface.detect_image(image)
    
    r_image = cv2.cvtColor(r_image, cv2.COLOR_RGB2BGR)
    cv2.imshow("result", r_image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

         通过对测试图像进行检测,我们可以得到检测结果如下图2.9所示:
    在这里插入图片描述

    图2.9 测试图像检测结果

    转载声明:
    版权声明:非商用自由转载-保持署名-注明出处
    署名 :mcyJacky
    文章出处:https://blog.csdn.net/mcyJacky

    展开全文
  • Retinaface论文翻译及理解

    千次阅读 2020-03-31 11:47:23
    本文提出了一种单级(single-stage)人脸检测器:RetinaFace. 通过联合外监督(extra-supervised)和自监督(self-supervised)的多任务学习,RetinaFace对各种尺度条件下的人脸可以做到像素级别的定位。本文作了...

     Retinaface论æç¿»è¯åç解

    摘要

    虽然在不受控条件下的人脸检测已经取得了非常显著的进展,在自然环境下准确有效的人脸检测依然具有挑战。本文提出了一种单级(single-stage)人脸检测器:RetinaFace. 通过联合外监督(extra-supervised)和自监督(self-supervised)的多任务学习,RetinaFace对各种尺度条件下的人脸可以做到像素级别的定位。本文作了以下几个贡献:

    1、 我们手工标注了WIDER FACE数据集上的五点,在外监督信号的辅助下获得了难人脸检测的显著提升。(https://zhuanlan.zhihu.com/p/101600780

    2、添加了一个自监督网格编码分支,用于预测一个逐像素的3D人脸信息。该分支与已存在的监督分支并行。

    3、在WIDER FACE测试集上,RetinaFace(AP = 91.4%)比最好的模型AP高出1.1%

    4、在IJB-C测试集上,RetinaFace使当前最好的ArcFace在人脸认证(face verification)上进一步提升(TAR=89.59 FAR=1e-6)

    5、通过利用轻量级的骨架网络,RetinaFace可以在单一CPU上对一张VGA分辨率的图像实时运行

    标注和代码获取地址

    Deep Insightinsightface/tree/master/RetinaFace

    本节概念:TAR True Accept Rate 正确接受率; FAR False Accept Rate 错误接受率 ;

    VGA分辨率:图像大小640x480 HD图像:1920x1080 4K图像:4096x2160


    1 介绍

    自动人脸定位是人脸图像分析如人脸属性(表情,年龄,ID识别)的先决步骤。人脸识别传统的窄定义为:在没有任何尺度和位置先验信息的条件下估计人脸的包围框。然而在本文中,人脸定位是一个更宽泛的定义,包括人脸检测,人脸对齐,像素级别的人脸分解以及3D密度回归。那种密集的人脸定位可以为所有不同尺度的人脸提供准确的人脸位置信息。

    与通用的目标检测方法不同的是,人脸检测的特征宽高比变化很小(1:1到1:1.5),但是尺度变化非常大(从几个像素到几千个像素)。最近最好的方法关注于单级设计,在特征金字塔上进行密集的人脸位置和尺度采样,相比两级级联方法,这种设计获得了不错的性能和速度提升。依据这种路线,我们提升的单级人脸检测框架,并且通过利用强监督和自监督型号的多任务损失,提出了当前最好的密集人脸定位方法。思想如图1:

    图 1:单级逐像素人脸定位方法利用外监督和自监督的多任务学习同时包含人脸框分类及回归分支。每个positive anchor输出:人脸得分,人脸框,5个人脸关键点,投射的图像平面上的3D人脸顶点

    特别的,人脸定位训练过程包含分类和人脸框回归损失。在一个联合级联框架中将人脸检测和对齐结合,获得的人脸形状可以为人脸分类提供更好的特征。基于这种思想,MTCNN和STN同时检测人脸和五个关键点。基于训练数据的限制,JDA,MTCNN ,STN并没有验证小人脸是否可以从5个关键点的外监督中受益。本文想要回答的一个问题是,是否可以通过5个关键点重建的外监督信号将WIDER FACE难测试集当前最好的性能90.3%向前推进。

    在Mask R-CNN中,通过增加目标淹没的分支并与检测框识别和回归分支并行,检测性能显著提升。这证实了密集像素级别的标注对于提升检测性能也有用。然而,WIDER FACE中具有挑战的人脸无法获取密集的人脸标注。既然监督信号不易获取,那么问题就变成了我们是否可以利用无监督的方法进一步提升人脸检测呢。

    FAN提出一个anchor级别的注意力图来提升遮挡人脸检测。然而,提出的注意力图比较粗糙且不包含语义信息。最近,自监督3D形变模型获得了不错的自然条件下的3D人脸。特别的,Mesh Decoder利用在形状和纹理上的图卷积获得了超实时的速度。然而,将mesh decoder应用到单级检测器上的最大挑战是:(1)相机参数很难准确估计(2)联合潜在形状和纹理估计是从一个简单的特征向量预测的(特征金字塔上的1x1卷积)而不是通过RoI池化特征,这样就存在特征漂移的风险。本文通过对于一个像素级3D人脸形状的自监督学习与存在的监督分支并行,利用网络编码(mesh decoder)分支。

    总之,主要贡献:

    1. 基于单级设计,提出一个新的像素级人脸定位方法RetinaFace,利用多任务学习策略同事预测人脸评分,人脸框,5个段建店以及对应于每个人脸像素的3D位置。
    2. 在WIDER FACE难子集上,RetinaFace的AP=91.4%,比最好的两级级联方法ISRN提升1.1%
    3. 在IJB-C测试集上,RetinaFace将ArcFace在人脸认证(face verification)上进一步提升(TAR=89.59%FAR=1e-6)。这表示更好的人脸定位可以显著提升人脸识别
    4. 通过利用轻量级的骨架网络,RetinaFace可以在单一CPU上对一张VGA分辨率的图像实时运行
    5. 标注和源码

     

    2. 相关工作

    图像金字塔vs特征金字塔:滑动窗策略可以追溯到几十年前。里程碑式的工作是Viola-Jones提出级联链来拒绝图像金字塔中的错误人脸并达到实时,引领尺度不变形人脸检测框架框架的广泛引用。尽管在图像金字塔上的滑动窗是主要的检测方式,随着特征金字塔的出现,在多个尺度特征图上的滑动anchor被快速应用于人脸检测。

    两级vs单级:当前人脸检测方法继承了一些通用检测方法的成果,主要分为两类:两阶方法如Faster RCNN和单阶方法如SSD和RetinaNet。两阶方法应用一个“proposal and refinement”机制提取高精度定位。而单阶方法密集采样人脸位置和尺度,导致训练过程中极度不平衡的正样本和负样本。为了处理这种不平衡,采样和re-weighting方法被广泛使用。相比两阶方法,单阶方法更高效并且有更高的召回率,但是有获取更高误报率的风险,影响定位精度。

    上下文建模:为了增强模型对小人脸的上下文推理能力,SSH和PyramidBox在特征金字塔上采用了上下文模块来增强欧几里得网格中获取的感受野。为了增强CNN的非严格变换模拟能力,形变卷积网络(DCN)利用一个新的形变层来模拟几何形变。WIDER Face挑战2018的冠军方案说明对于人脸检测来说,严格(扩大)和非严格(变形)上下文建模是互补并且正交。

    多任务学习:联合人脸检测和对齐被广泛应用以提供更适用于提取人脸特征的人脸形状。在Mask R-CNN中,检测性能通过增加一个预测目标掩模的并行分支,检测性能得到显著提升。Densepose利用Mask-RCNN的架构,来获取每个选择区域的密集标签和位置。尽管如此,密集回归标签是通过监督学习训练的。此外,dense分支是一个很小的FCN,应用于每一个RoI上来预测像素到像素的密集映射。

    3. RetinaFace

    3.1 多任务损失

    对于每一个训练anchor i,我们最小化多任务损失:

    (1)人脸分类损失[公式] ,pi表示预测anchor i为人脸的概率,pi*表示真值,正样本anchor为1,负样本anchor为0.分类损失Lcls是softmax损失,对于二分类(是人脸/不是人脸);(2)人脸框回归损失 [公式] ,其中[公式] 表示与正样本anchor对应的预测框的位置和真实标注框的位置。归一化box回归目标并使用 [公式] 其中R表示smooth_L1鲁棒性回归函数(参见fast Rcnn)。(3)人脸关键点回归函数 [公式]

    分别表示正样本人脸anchor5关键点的预测和真值。与box回归一致,人脸5关键点会与同样使用目标归一化。(3)密集回归损失 [公式] 参见公式3。 [公式] 的值分别设置为0。25,0.1,0.01,意味着提升了来自监督信号的更好人脸框和五点位置的重要性。

    3.2 密集回归分支

    网格编码(Mesh Decoder)直接利用网格编码器(网格卷积和网格上采样),也就是基于快速局部谱滤波器(fast localised spectral filtering)的图卷积方法。为了获得更快的速度,我们联合形状和上下文解码。

    下面 将简单解释图卷积的概念并且说明他们可以被用于快速解码的原因。如图3(a)所示,一个2D卷积操作是一个欧几里得网格感受野中的“核加权近邻求和”(kernel-wighted neighbour sum)。类似的,图卷积利用了图3(b)中的类似感念。然而,图近邻距离的计算方法是通过连接两个顶点的边的最小数量。定义一个人脸网格 [公式] V是人脸顶点集合,包含形状和纹理信息。 [公式] 是一个稀疏临接矩阵用于编码两个顶点之间的连接状态。图拉普拉斯定义为 [公式]

    核 [公式] 的图卷积可以用切比雪夫K阶多项式展开来表示:

    [公式][公式]处的值。整体的计算过程非常高效,因为包含了K个离散矩阵向量相乘和一个密集矩阵乘法 [公式]

    图3a 2D卷积

    图3b 图卷积

    可微渲染器(Differentable Renderer)在预测了形状和纹理参数 [公式] 后,利用一个高效可微3D网格渲染器来投影彩色网格 [公式] 到一个2D图像平面上,利用相机参数 [公式] 也就是相机位置,相机形状和焦距以及光照参数 [公式] (光源位置,颜色值,环境光颜色)

    密集回归损失一旦得到了渲染2D人脸 [公式] 我们比较渲染得到人脸与原始2D人脸的像素差异:W H表示anchor区域I i j *的宽和高

    4 实验

    4.1 数据集

    WIDER FACE数据集包含32,203个图像和393,703个人脸框,尺度, 姿态,表情,遮挡和光照变化都很大。WIDER FACE数据集被分为训练40% 验证10% 和测试50%三个子集,通过在61个场景分类中随机采样。基于EdgeBox的检测率,通过递增合并难样本,困难程度分为3级:容易,中性和困难。

    额外标注

    如图4和表1,我们定义的5个人脸质量级别,依据人脸关键点标注困难程度并且标注5个关键点(眼睛中心,鼻尖,嘴角)。我们总共标注了84.6k个训练集人脸和18.5k个验证集人脸。

    图4

     

    表1

    4.2 应用详情

    特征金字塔 RetinaFace应用特征金字塔从P2到P6,其中P2到P5是从Resnet残差级(C2到C5)计算而来。P6将C5通过一个3x3,s=2的卷积得到。C1到C5来自于在ImageNet-11k数据集上预训练的ResNet-152分类框架而P6通过Xavier随机初始化。

    上下文模块 受启发于SSH和PyramidBox,我们也把独立上下文模块应用于5个特征金字塔上用于提高感受野和加强严格上下文建模能力。从WIDER Face Challenge2018中总结经验,我们将所有侧连的3x3卷积层和上下文模块都替换为了DCN,可以进一步加强非严格上下文建模能力

    损失头(Loss Head) 对于所有的负样本anchors,仅仅使用了分类损失。对于正样本anchors,则计算多任务损失。我们在不同的特征图之间 [公式] 利用一个共享损失头(1x1卷积).对于网格编码,我们利用一个预训练模型【70】,计算开销很小。

    Anchor设置 如表2所示,在从P2到P6的特征金字塔上使用特定尺度anchor。P2用于抓取小脸,通过使用更小的anchor,当然,计算代价会变大同事误报会增多。设置尺度步长为 [公式] ,宽高比1:1。输入图像640x640,anchor从16x16到406x406在特征金字塔上。总共有102,300个anchors,75%来自P2层。给大家解释一下这个102300怎么来的:(160*160+80*80+40*40+20*20+10)*3 = 102300。

    表2

    在训练阶段,ground-truth的IOU大于0.5的anchor被任务是正样本,小于0.3的anchor认为是背景。anchors中有大于99%的都是负样本,使用标准OHEM避免正负样本的不均衡。通过loss值选择负样本,正负样本比例1:3

    数据增强 WIDER FACE有20%的小脸,从原始图像随机crop 方形patch并缩放至640*640来生成更大的人脸。方形patch的截取大小是随机选择[0.3,1]被的原始图像短边长度。在crop边界上的人脸,保留中心在crop patch内的人脸框。除了随机crop,我们通过水平旋转一半的图像以及对另一半颜色扰动。

    训练细节 使用SGD优化器训练RetinaFace(momentum=0.9,weight decay = 0.0005,batchsize=8x4),Nvidia Tesla P40(24G) GPUs.起始学习率0.001,5个epoch后变为0.01,然后在第55和第68个epoch时除以10.

    测试细节 在WIDER FACE上测试,利用了flip和多尺度(500,800,1100,1400,1700)策略。使用IoU 阈值0.4,预测人脸框的集合使用投票策略。

    4.3 消融实验

    为了获得更好的理解,做了扩展实验i检验标注五点和密集回归分支对人脸检测性能的影响。除了在简单,中性和容易的数据集上使用标准的AP测量策略(IoU-0.5),我们也利用 难验证子集,测试更严格的AP在IoU=0.5:0.05:0.95.

    如表格3所示,评价了几个WIDER FACE验证集上的配置并关注难验证集 子集的AP 和mAP。通过使用FPN、上下文信息、形变卷积等策略,得到了一个很好的baseline。通过增加5点分支,在难样本上显著提升,说明5点对 人脸检测的提升很明显。对比而言,密集回归分支提升了简单和中性人脸的检测,但是对困难人脸的提升不大。把5点分支和密集分支一起使用,性能依然有提升。

    表3

    4.4 人脸框准确率

    分别在WIDER FACE验证集和测试集上评价了算法模型。将Retina与当前最好的24个人脸检测算法比较(i.e. Multiscale Cascade CNN [60], Two-stage CNN [60], ACFWIDER[58], Faceness-WIDER [59], Multitask Cascade CNN [66], CMS-RCNN [72], LDCF+ [37],HR [23], Face R-CNN [54], ScaleFace [61], SSH [36], SFD [68], Face RFCN[57], MSCNN [4], FAN [56], Zhu et al. [71], Pyramid-Box [49], FDNet [63], SRN [8], FANet [65], DSFD [27],DFS [50], VIM-FD [69], ISRN [67])..RetinaFace在AP上比以上算法都好。在验证集上,96.9%easy96.1%medium以及91.8%Hard,在测试集上,96.3%容易95.6中性91.4%困难。

    如图6所示,展示了密集人脸情况下的检出质量,在0.5以上的人脸检出900个,总共是1151人脸。除了人脸框准确之外,5点定位在不同的姿态遮挡分辨率下也都很鲁棒。尽管在严重遮挡的情况下密集人脸定位依然有失败的案例,但在一些清晰的大人脸上效果很好,甚至都可以看到一个表情变化。

    图6

    4.5 五点定位准确率

    为了评价五点定位准确率,我们在AFLW数据集上(24386人脸)和WIDER FACE验证集(18.5k人脸)比较了MTCNN和RetinaFace。使用人脸框大小归一化距离。如图7a所示,给出在AFLW上每个点的平均误差。RetinaFace将归一化平均误差 NME从MTCNN的2.72%下降为2.21%。在图7b中,展示了在WIDER FACE 验证集上的累积误差分布CED。与MTCNN相比,在NME阈值为10%时的漏报率由26.31%降到9.37%。

     

    4.6 密集人脸关键点准确性

    除了人脸框和5点,Retinaface还输入密集人脸关键点,并且是自监督训练。在AFLW2000-3D数据集上评价密集人脸关键点定位(1)2D投影下的68人脸关键点(2)所有关键点的3D坐标。平均误差通过人脸框大小归一化。图8a和图8b是当前最好方法的CED曲线。尽管自监督和有监督方法的性能差异还比较大,但是RetinaFace相比较而言是最好的方法。特别的,可以看出:(1)五点回归可以避免密集回归分支的训练困难并且可以显著提升密集回归的效果。(2)使用单级特征来预测密集人脸比使用RoI特征(如网格编码)要困难的多。如图8c所示,RetinaFace可以很容易的处理人脸姿态变化但是在复杂场景下就比较困难。这表示没有对齐以及过于压缩的特征表示(1x1x256)会妨碍单级框架获取高精度的密集回归输出。尽管如此,回归分支中投影出来的回归区域依然有助于人类检测结果的提升。

    图8(c) Result Analysis (Upper: Mesh Decoder; Lower: RetinaFace)

    4.7人脸识别准确率

    本文展示了我们的人脸检测方法是如何提升Arcface人脸识别方法准确率的。本文分别比较了使用MTCNN和Retinaface来检测和对齐所有的训练数据(MS1M)以及测试数据(LFW,CFP-FP,AGEDB-30,IJBC),保留原Arcface中使用的Resnet100基础网络以及损失函数。比较解过如表4所示,基于CFP-FP,证明Retinaface可以提升Arcface的验证正确率从98.37%到99.49%。这个结果展示了正脸-侧脸的人脸认证已经达到了正脸-正脸的人脸认证水平。(99.86%在LFW上)

    表4

    如图9所示展示了在FAR=1e-6时IJB-C数据中的ROC曲线。我们使用了两个tricks(镜像测试以及人脸检测评分去加权模板中的样本)来提升人脸认证准确率。使用retinaface代替mtcnn,TAR从88.29%提升到了89.59%。这表示(1)人脸检测和对齐对人脸识别影响很大(2)Retinaface相比MTCNN更加强壮。

    图9

    4.8 inference效率

    在测试时,retianface灵活而高效的进行人脸定位。除了权值较多的模型(ResNet-152,262M,AP=91.8%在WIDER FACE难样本集合上),我们也开发了一个轻量级的模型(MobileNet-0.25,1M,AP=78.2%在WIDER FACE难样本集合上)来加速预测。

    对于轻量级网络,我们可以通过一个步长为4,7x7的卷积,快速地减少数据大小,像文献【36】那样在P3,P4,P5后面添加密集anchors,并删除了形变层。此外,前两个卷积层使用imagenet预训练初始化后再训练时固定下来以获得更高的准确率。

    表格5展示了不同输入尺寸下两个模型的耗时。其中,密集回归分支的时间没有统计进去。使用TVM来加速模型预测,在NVIDIA Tesla P40 GPU,Intel i7-6700k cpu和ARM-RK3399.

    表5

    5 总结

    研究了在任意尺度图像下同时进行密集回归和对齐的问题,提出了第一个单级解决方案(RetinaFace)。我们的方法称为当前最好的检测方法,结合最好的人脸识别方法识别准确率也进一步提升。

    展开全文
  • 最新的人脸检测python代码
  • Pytorch-RetinaFace 详解

    2020-12-20 07:27:08
    参考:上面那套代码非常的详细,数据集都准备好了,下载后就可以直接训练了。下面简单对代码进行梳理...提一嘴哈,如果希望将retinaface转成onnx然后走tensorRT的话,需要改一下里面FPN使用的上采样方法,从F.in...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,458
精华内容 983
关键字:

retinaface