前言
今天来水一片文章,基于开源的Pyramidbox大规模人脸检测编写的PaddlePaddle教程,为了方便训练预测,本教程做了一定的修改。这个模型虽然大,但是符合大规模人群中也可以准确地检测到人脸,就是遮挡比较严重也能正确检测。
本教程源码:https://download.csdn.net/download/qq_33200967/14029049
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
该数据集解压之后的结构是这样的,检测你的路径是否正确了。
代码语言:javascript复制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
然后是下载预训练模型,下载链接如下,把预训练模型解压到项目的根目录下。
代码语言:javascript复制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
中,只有model
和params
两个文件。
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')
该函数获取变换图片到一定范围的尺度, 通过这个尺度改变输入图片的大小。
代码语言:javascript复制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
编写一个可以显示预测图像的函数,在图像中画上预测的框,并显示在桌面。
代码语言:javascript复制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()
以下是本程序执行之后的效果图: