基于Pyramidbox实现的大规模人脸检测

  |   0 评论   |   0 浏览   |   夜雨飘零

前言

今天来水一片文章,基于开源的 Pyramidbox 大规模人脸检测编写的 PaddlePaddle 教程,为了方便训练预测,本教程做了一定的修改。这个模型虽然大,但是符合大规模人群中也可以准确地检测到人脸,就是遮挡比较严重也能正确检测。

本教程源码:https://resource.doiduoyi.com/#2mgg861

PyramidBox 是一种基于 SSD 的单阶段人脸检测器,它利用上下文信息解决困难人脸的检测问题。如下图所示,PyramidBox 在六个尺度的特征图上进行不同层级的预测。该工作主要包括以下模块:LFPN、Pyramid Anchors、CPM、Data-anchor-sampling。
在这里插入图片描述

LFPN: LFPN 全称 Low-level Feature Pyramid Networks, 在检测任务中,LFPN 可以充分结合高层次的包含更多上下文的特征和低层次的包含更多纹理的特征。高层级特征被用于检测尺寸较大的人脸,而低层级特征被用于检测尺寸较小的人脸。为了将高层级特征整合到高分辨率的低层级特征上,我们从中间层开始做自上而下的融合,构建 Low-level FPN。

Pyramid Anchors: 该算法使用半监督解决方案来生成与人脸检测相关的具有语义的近似标签,提出基于 anchor 的语境辅助方法,它引入有监督的信息来学习较小的、模糊的和部分遮挡的人脸的语境特征。使用者可以根据标注的人脸标签,按照一定的比例扩充,得到头部的标签(上下左右各扩充 1/2)和人体的标签(可自定义扩充比例)。

CPM: CPM 全称 Context-sensitive Predict Module, 本方法设计了一种上下文敏感结构(CPM)来提高预测网络的表达能力。

Data-anchor-sampling: 设计了一种新的采样方法,称作 Data-anchor-sampling,该方法可以增加训练样本在不同尺度上的多样性。该方法改变训练样本的分布,重点关注较小的人脸。

下面这张图可以体现 Pyramidbox 在大规模人群中人脸检测的强大,不知道你信不信,反正我信了。
在这里插入图片描述

训练

首先下载数据集,下载链接如下,把他们下载解压到项目根目录下的 data 目录中。

https://share.weiyun.com/5WjCBWV
https://share.weiyun.com/5ot9Qv1
https://share.weiyun.com/5vSUomP
http://mmlab.ie.cuhk.edu.hk/projects/WIDERFace/support/bbx_annotation/wider_face_split.zip

该数据集解压之后的结构是这样的,检测你的路径是否正确了。

data
|-- download.sh
|-- wider_face_split
|   |-- readme.txt
|   |-- wider_face_train_bbx_gt.txt
|   |-- wider_face_val_bbx_gt.txt
|   `-- ...
|-- WIDER_train
|   `-- images
|       |-- 0--Parade
|       ...
|       `-- 9--Press_Conference
`-- WIDER_val
    `-- images
        |-- 0--Parade
        ...
        `-- 9--Press_Conference

然后是下载预训练模型,下载链接如下,把预训练模型解压到项目的根目录下。

http://paddlemodels.bj.bcebos.com/vgg_ilsvrc_16_fc_reduced.tar.gz

最后直接执行 train.py 就可以了,模型比较大,如何显存不足,可以设置 batch_size 小一点。该模型支持多卡训练,可以通过设置 export CUDA_VISIBLE_DEVICES=0,1,2,3 指定使用的 GPU,并设置参数 num_devices 想要使用的 GPU 数量。

多少的读者应该也是使用 Windows 训练的吧,笔者也是,但 Windows 下 PaddlePaddle 不支持多线程读取数据,所以参数 use_multiprocess 需要设置为 False。

训练保存的模型存放在 output 目录中。

预测

上面训练保存的或者下载的模型都是是持久化参数,这里说一下,官方提供的 PyramidBox 模型下载地址为:http://paddlemodels.bj.bcebos.com/PyramidBox_WiderFace.tar.gz。这些持久化参数在预测中非常不方便,以下载的模型为例,解压下载的模型到根目录,下面写一段代码把这些持久化参数转换预测模型,预测参数会保存在 pyramidbox_model 中,只有 modelparams 两个文件。

import paddle.fluid as fluid
from pyramidbox import PyramidBox

use_gpu = True
model_dir = 'PyramidBox_WiderFace'
save_infer_model_path = 'pyramidbox_model'

place = fluid.CUDAPlace(0) if use_gpu else fluid.CPUPlace()
exe = fluid.Executor(place)
main_program = fluid.Program()
startup_program = fluid.Program()
image_shape = [3, 1024, 1024]
with fluid.program_guard(main_program, startup_program):
    network = PyramidBox(
        data_shape=image_shape,
        sub_network=True,
        is_infer=True)
    infer_program, nmsed_out = network.infer(main_program)
    fetches = [nmsed_out]
    fluid.io.load_persistables(exe, model_dir, main_program=infer_program)
    # save model and program
    fluid.io.save_inference_model(save_infer_model_path,
                                  ['image'], [nmsed_out], exe, main_program=infer_program,
                                  model_filename='model', params_filename='params')

接下来就是预测,编写 infer.py 代码,创建执行器并加载 Pyramidbox 模型,加载的就是上一步转换的预测模型。

import time
import cv2
import numpy as np
import paddle.fluid as fluid
from PIL import Image
from PIL import ImageDraw

use_gpu = True
# 创建执行器
place = fluid.CUDAPlace(0) if use_gpu else fluid.CPUPlace()
exe = fluid.Executor(place)
exe.run(fluid.default_startup_program())

# 预测模型路径
save_path = 'pyramidbox_model/'
[infer_program,
 feeded_var_names,
 target_var] = fluid.io.load_inference_model(dirname=save_path,
                                             executor=exe,
                                             model_filename='model',
                                             params_filename='params')

该函数获取变换图片到一定范围的尺度, 通过这个尺度改变输入图片的大小。

def get_shrink(height, width):
    max_shrink_v1 = (0x7fffffff / 577.0 / (height * width)) ** 0.5
    max_shrink_v2 = ((678 * 1024 * 2.0 * 2.0) / (height * width)) ** 0.5

    def get_round(x, loc):
        str_x = str(x)
        if '.' in str_x:
            str_before, str_after = str_x.split('.')
            len_after = len(str_after)
            if len_after >= 3:
                str_final = str_before + '.' + str_after[0:loc]
                return float(str_final)
            else:
                return x

    max_shrink = get_round(min(max_shrink_v1, max_shrink_v2), 2) - 0.3
    if 1.5 <= max_shrink < 2:
        max_shrink = max_shrink - 0.1
    elif 2 <= max_shrink < 3:
        max_shrink = max_shrink - 0.2
    elif 3 <= max_shrink < 4:
        max_shrink = max_shrink - 0.3
    elif 4 <= max_shrink < 5:
        max_shrink = max_shrink - 0.4
    elif max_shrink >= 5:
        max_shrink = max_shrink - 0.5

    shrink = max_shrink if max_shrink < 1 else 1
    return shrink, max_shrink

编写一个可以显示预测图像的函数,在图像中画上预测的框,并显示在桌面。

def draw_image(img_path, bboxes):
    image = Image.open(img_path)
    draw = ImageDraw.Draw(image)
    for i in range(len(bboxes)):
        xmin, ymin, xmax, ymax = bboxes[i]
        (left, right, top, bottom) = (xmin, xmax, ymin, ymax)
        draw.line([(left, top), (left, bottom), (right, bottom), (right, top), (left, top)], width=4, fill='red')

    # 显示图像
    cv2.imshow('result image', cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR))
    cv2.waitKey(1)

该函数为使用模型检测人脸,该函数包括了图像预处理,首先是要把图片从 HWC 转化为 CHW,然后 PIL 打开图片是 RBG 的,但训练的时候是用的是 BGR,所以要转换为 BGR,最后减去均值和乘缩放值。

def detect_face(image, shrink):
    image_shape = [3, image.size[1], image.size[0]]
    if shrink != 1:
        h, w = int(image_shape[1] * shrink), int(image_shape[2] * shrink)
        image = image.resize((w, h), Image.ANTIALIAS)
        image_shape = [3, h, w]

    img = np.array(image)
    print(img.shape)
    # HWC to CHW
    if len(img.shape) == 3:
        img = np.swapaxes(img, 1, 2)
        img = np.swapaxes(img, 1, 0)
    print(img.shape)
    # RBG to BGR
    img = img[[2, 1, 0], :, :]
    mean = [104., 117., 123.]
    scale = 0.007843
    img = img.astype('float32')
    img -= np.array(mean)[:, np.newaxis, np.newaxis].astype('float32')
    img = img * scale
    img = [img]
    img = np.array(img)

    detection, = exe.run(infer_program,
                         feed={feeded_var_names[0]: img},
                         fetch_list=target_var,
                         return_numpy=False)
    detection = np.array(detection)
    # layout: xmin, ymin, xmax. ymax, score
    if np.prod(detection.shape) == 1:
        print("No face detected")
        return np.array([[0, 0, 0, 0, 0]])
    det_conf = detection[:, 1]
    det_xmin = image_shape[2] * detection[:, 2] / shrink
    det_ymin = image_shape[1] * detection[:, 3] / shrink
    det_xmax = image_shape[2] * detection[:, 4] / shrink
    det_ymax = image_shape[1] * detection[:, 5] / shrink

    det = np.column_stack((det_xmin, det_ymin, det_xmax, det_ymax, det_conf))
    return det

最后传一张图片的路径,预测并展示检测后的图像。通过设置阈值 confs_threshold 过滤掉得分比较低的人脸框。

def infer(image_path, confs_threshold):
    if True:
        image = Image.open(image_path)
        if image.mode == 'L':
            image = image.convert('RGB')
        shrink, max_shrink = get_shrink(image.size[1], image.size[0])

        start = time.time()
        det0 = detect_face(image, shrink)
        dets = det0
        end = time.time()
        print("预测时间: %f" % (end - start))

        keep_index = np.where(dets[:, 4] >= confs_threshold)[0]
        dets = dets[keep_index, :]
        draw_image(image_path, dets[:, 0:4])


if __name__ == '__main__':
    confs_threshold = 0.15
    image_path = 'dataset/images/0acc15e8965111eab2edc8ff285a4318.jpg'
    infer(image_path, confs_threshold)

    cv2.waitKey(0)
    cv2.destroyAllWindows()

以下是本程序执行之后的效果图:
在这里插入图片描述


标题:基于Pyramidbox实现的大规模人脸检测
作者:夜雨飘零
地址:https://blog.doiduoyi.com/articles/1594262220338.html

评论

发表评论