数据集通过网址 http://shuoyang1213.me/WIDERFACE/下载。
模型Pnet,Rnet 训练、测试选择使用的数据 WIDER FACE 。
WIDER FACE简介:数据标签只有bbox,没有landmark。注释:bbox [x1,y1,width,height]
模型Onet训练采用的数据通过网址http://mmlab.ie.cuhk.edu.hk/archive/CNN_FacePoint.htm 下载
CNN_FacePoint简介 :数据标签有bbox,以及landmark。注释:bbox[x1,x2,y1,y2]
landmark [x1,y1,x2,y2,x3,y3,x4,y4,x5,y5]
坑1:注意标签每个坐标含义。
工程需要做一个人脸识别的东东,所以准备把人脸识别的每块内容都认真的走一遍,步骤大概是:人脸检测 - 人脸矫正 - 活体检测 - 质量评估 - 人脸识别 ,为了快速搞定,利用别人的代码就是最便捷的方法,自己选择pytorch的一个版本。网址为:https://github.com/Sierkinhane/mtcnn-pytorch。
这里大概介绍一下mtcnn的工作原理
mtcnn网络结构如图1所示:
图1
他是一种多任务级联的网络,每一个网络都包含人脸分类任务,边界框回归任务、人脸关键点回归任务。P-Net 主要用来产生候选框可能的位置,R-Net 再过滤掉一部分候选框,O-Net 产生最终的候选框和landmark位置。
注意:PNet 输出的是map格式,而Rnet和ONet的输出的是条状的,原因是因为实际测试时,PNet输入图片大小是不固定的,所以维度是不固定的不能接入全连接层。
测试时,一张图片先通过PNet,按照一个比例系数进行resize 得到图片金字塔,分别在每张图片上面进行边界框预测输出,预测的方法是根据class_map上大于阈值的保留下来,作为边界框的左上角坐标,坐标通过resize 相应的系数映射回原图,右下角的坐标=(坐上角的坐标*stirde+12)/scale。对应红色的部分。stride =2 猜测比如输入一张256大小的图片,最后输出类别、位置信息时图像大小变为123 ,原图是特征图的二倍关系。
boundingbox = np.vstack([np.round((stride * t_index[1]) / scale), # x1 of prediction box in original image
np.round((stride * t_index[0]) / scale), # y1 of prediction box in original image
np.round((stride * t_index[1] + cellsize) / scale), # x2 of prediction box in original image
np.round((stride * t_index[0] + cellsize) / scale), # y2 of prediction box in original image #reconstruct the box in original image
score,
reg])
reg 是网络输出框的相对偏移的位置信息,最终的坐标输出以x1为例,为x1 = np.round((stride * t_index[1]) / scale)+ (np.round((stride * t_index[1] + cellsize) / scale)-np.round((stride * t_index[1]) / scale))*reg[:,0]
RNet输入是Pnet的输出,RNet主要会相对于Pnet输出位置进行调整。ONet同Rnet。landmark 输出的是相对位置,是相对boundingbox的相对位置坐标。
回到模型训练上,PNet,RNet,ONet模型的输入分别是12*12,24*24,48*48。输出的类别共包含0-负样本,1-正样本,-1-part样本就是部分含有正样本的数据,-2-landmark 样本。
需要注意的是模型训练的数据种类标签是否正确,否则训练的时候会出现某一个loss的梯度爆炸,原文作者代码的iou 计算输入有些问题,导致在训练ONet正负样本不均衡。
目前训练的PNet,RNet,ONet,比例大致是4:7:15 。
目前模型训练结果出现的问题:
1、框回归的不稳,抖动情况非常严重。这个问题目前没有解决。不知道该怎样优化。可能数据量还是不够。
2、关于参数初始化的问题,发现参数初始化不对loss降不太下去,现在每次训练都是在自己某一次实验结果上开始训练的。
自己写了个测试程序,分别统计检出率和准确率。目前iou=0.65,检出和准确率在0.7以上。可以通过这个小程序进行模型筛选。代码如下:
import cv2
import torch
from mtcnn.core.detect import create_mtcnn_net, MtcnnDetector
import numpy as np
import os
import mtcnn.core.utils as utils
testfiles = "anno_train.txt"
testdata = "face_landmark/CNN_FacePoint"
label = []
showlabel = True
with open(testfiles, "r") as f:
read = f.readlines()
print("test Data num is :", len(read))
pnet, rnet, onet = create_mtcnn_net(p_model_path="pnet_epoch.pt",
r_model_path="rnet_epoch.pt",
o_model_path="onet_epoch.pt",
use_cuda = False)
mtcnn_detector = MtcnnDetector(pnet=pnet, rnet=rnet, onet=onet, min_face_size=12)
pos = 0
numsdetect = 0
for file in read:
#print("file is :", file)
file = file.strip().split(" ")
img = cv2.imread(os.path.join(testdata, file[0].replace("\\", "/")))
print(os.path.join(testdata, file[0].replace("\\", "/")))
print("img.shape is :", img.shape)
#print(file[0])
boxes_align_r, bboxs, landmarks = mtcnn_detector.detect_face(img)
gtbox = list(map(float, file[1:5]))
print("gtbox is :", gtbox)
gtlandmarks = list(map(float, file[5:]))
numdetect = len(bboxs)
numsdetect = numsdetect + numdetect
gtbox = np.array(gtbox, dtype=np.float32).reshape(-1, 4)
for box in bboxs:
if showlabel:
cv2.rectangle(img, (int(box[0]), int(box[1])), (int(box[2]), int(box[3])), (0, 0, 255), 2, 1)
#cv2.rectangle(img, (int(gtbox[0][0]), int(gtbox[0][2])), (int(gtbox[0][1]), int(gtbox[0][3])), (255, 0, 0), 2, 1)
p1 = int(gtbox[0][0])+int(gtbox[0][2])
p2 = int(gtbox[0][1])+int(gtbox[0][3])
print("p1", int(gtbox[0][0]), int(gtbox[0][1]),p1,p2)
cv2.rectangle(img, (int(gtbox[0][0]), int(gtbox[0][1])), (p1, p2), (255, 0, 0),
2, 1)
iou = utils.IoU1(box, gtbox)
print("iou is :", iou)
if iou > 0.65:
pos = pos + 1
if showlabel:
cv2.imshow("img", img)
cv2.waitKey(1000)
print("recall is : %s accuracy is : %s "%(pos/len(read),pos/numsdetect))