目录
IOU(Intersection over Union)交并比
NMS(Non-maximum suppression)非极大值抑制
一、MTCNN简介:
1、什么是MTCNN
MTCNN的“MT”是指多任务学习(Multi-Task),CNN(Convolutional Neural Networks, CNN)是指卷积神经网络。MTCNN多任务级联卷积神经网络,基于级联的特定目标检测器,在人脸识别中有着出色的表现。
2、MTTCNN的作用
在目标检测中,MTCNN可以用来检测和识别学习到的目标,这个目标是有一定讲究的,比如对于同类外观相差不大的不同目标可以很好地区分,如果用来区分不同种类的不同目标可能会有些大材小用。
3、MTCNN的优缺点
1优点
1、设备要求低:使用了级联思想,将复杂问题分解,使得模型能够在小型设备上运行,比如人脸监测模型可以在没有GPU的设备上运行。
2、容易训练:三个级联网络都较小,训练模型时容易收敛。
3、精度较高:使用了级联思想,逐步提高精度。
2缺点
1、误检率较高:因为采用了级联的思想,使得模型在训练过程中的负样本偏少,学到的模型不够100%准确。
2、改进空间大:MTCN原论文模型在发表时距今已过去好几年了,随着技术的不断进步,对于原模型可以做出很多优化。
二、人脸检测
人脸检测是指对于任意一幅给定的图像,采用一定的策略对其进行搜索以确定其中是否含有人脸,如果是则返回一脸的位置、大小和姿态。(来源:百度百科)
人脸识别与人脸检测的关系
人脸识别包含三个阶段:
第一阶段人脸检测;第二阶段:特征提取;第三阶段:人脸对比
常用人脸检测架构:MTCNN、yolo系列、SSD、RCNN系列,FastRCNN系列
三、MTCN的网络模型

在MTCNN中主要有三层网络分别是:
P网络:模型训练过程中输入12x12的图像,输出置信度和人脸偏移量。
R网络:输入图像的大小为24x24,对R网络的输出图像和偏移值作进一步处理,处理货的图像和偏移值输入到R网络
O网络:输入的图像的大为48x48,对R网络输出的图像进行最终的判别,最终确定人脸的位置。
四、准备训练样本
1、获取原始数据集
本次模型训练采用Celeba数据集,
下载好Celeba的数据集是一个压缩包,解压之后得到以下文件夹,

打开第img\img_celeba.7z文件夹,一次性选中所有的的文件夹,一次性选中所有的压缩包解压到指定位置,笔者存放的路径为E:\CelebA\Img\img_celeba.7z\img_celeba
解压完成如图:

2、准备训练样本
1、训练样本的构成
1.种类
本项目需要准备三种样本:正样本:负样本:部分样本约等于1:1:3,之所以部分样本比较多是因为在级联处理中负样本会大量丢失,这样做的目的是为了保证最后进入R网络 和O网络三种样本的比例均衡。
正样本:整张图全是人脸
负样本:图像为背景
部分样本:一张图中有部分是人脸,另外一部分是非人脸。
2.形状
本项目的训练样本共涉及三种大小:12x12(用于P网络训练),24x24(用于R网络训练),48x48(用于O网络训练)
2、建立数据集样本
建立数据集样本应注意以下几点:
1:图片路径和标签一一对应,方便训练时读取数据
2:三个网络是分开训练,不同大小的图片各自有各自的正样本,负样本和部分样本。
3:部分和负样本样本是在正样本附近偏移得到得到
最终得到数据样本是这个样子:






3.生成样本数据代码
import os
from PIL import Image
import numpy as np
from MTCNN.tool import utils
import traceback
anno_src = r"E:\CelebA\Anno\list_bbox_celeba.txt" #原来的样本数据(在生成样本时使用)
img_dir = r"E:\CelebA\Img\img_celeba.7z\img_celeba" #源图片(用于生成新样本)
save_path = r"E:\CelebA\MTCN\dataSet" #生成样本的总的保存路径
float_num = [0.1, 0.5, 0.5, 0.5, 0.9, 0.9, 0.9, 0.9, 0.9] #控制正负样本比例,(控制比例?)
def gen_sample(face_size,stop_value):
print("gen size:{} image" .format(face_size))
positive_image_dir = os.path.join(save_path, str(face_size), "positive") #仅仅生成路径名
negative_image_dir = os.path.join(save_path, str(face_size), "negative")
part_image_dir = os.path.join(save_path, str(face_size), "part")
for dir_path in [positive_image_dir, negative_image_dir, part_image_dir]: #生成路径
if not os.path.exists(dir_path):
os.makedirs(dir_path)
positive_anno_filename = os.path.join(save_path, str(face_size), "positive.txt")
negative_anno_filename = os.path.join(save_path, str(face_size), "negative.txt")
part_anno_filename = os.path.join(save_path, str(face_size), "part.txt")
positive_count = 0
negative_count = 0
part_count = 0
try: #抛出异常
positive_anno_file = open(positive_anno_filename, "w")
negative_anno_file = open(negative_anno_filename, "w")
part_anno_file = open(part_anno_filename, "w")
for i, line in enumerate(open(anno_src)): #txt开头的两行文件不是路径和标签,需要跳过
if i < 2:
continue
try:
strs = line.split() #列表,包含路径和坐标值
image_filename = strs[0].strip() #置信度 #Python strip() 方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。注意:该方法只能删除开头或是结尾的字符,不能删除中间部分的字符。
# print(image_filename)
image_file = os.path.join(img_dir, image_filename)
with Image.open(image_file) as img:
img_w, img_h = img.size #原图
x1 = float(strs[1].strip())
y1 = float(strs[2].strip())
w = float(strs[3].strip()) #人脸框
h = float(strs[4].strip())
x2 = float(x1 + w)
y2 = float(y1 + h)
px1 = 0#float(strs[5].strip())
py1 = 0#float(strs[6].strip())
px2 = 0#float(strs[7].strip())
py2 = 0#float(strs[8].strip())
px3 = 0#float(strs[9].strip())
py3 = 0#float(strs[10].strip())
px4 = 0#float(strs[11].strip())
py4 = 0#float(strs[12].strip())
px5 = 0#float(strs[13].strip())
py5 = 0#float(strs[14].strip())
if x1 < 0 or y1 < 0 or w < 0 or h < 0: #跳过坐标值为负数的
continue
boxes = [[x1, y1, x2, y2]] #当前真实框四个坐标(根据中心点偏移), 二维数组便于IOU计算
#求中心点坐标
cx = x1 + w / 2
cy = y1 + h / 2
side_len = max(w, h)
seed = float_num[np.random.randint(0, len(float_num))] #取0到9之间的随机数作为索引 #len(float_num) = 9 #float_num = [0.1, 0.5, 0.5, 0.5, 0.9, 0.9, 0.9, 0.9, 0.9]
count = 0
for _ in range(4):
_side_len = side_len + np.random.randint(int(-side_len * seed), int(side_len * seed)) #生成框
_cx = cx + np.random.randint(int(-cx * seed), int(cx * seed)) #中心点作偏移
_cy = cy + np.random.randint(int(-cy * seed), int(cy * seed))
_x1 = _cx - _side_len / 2 #左上角
_y1 = _cy - _side_len / 2
_x2 = _x1 + _side_len #右下角
_y2 = _y1 + _side_len
if _x1 < 0 or _y1 < 0 or _x2 > img_w or _y2 > img_h: #左上角的点是否偏移到了框外边,右下角的点大于图像的宽和高
continue
offset_x1 = (x1 - _x1) / _side_len #得到四个偏移量
offset_y1 = (y1 - _y1) / _side_len
offset_x2 = (x2 - _x2) / _side_len
offset_y2 = (y2 - _y2) / _side_len
offset_px1 = 0#(px1 - x1_) / side_len #offset偏移量
offset_py1 = 0#(py1 - y1_) / side_len
offset_px2 = 0#(px2 - x1_) / side_len
offset_py2 = 0#(py2 - y1_) / side_len
offset_px3 = 0#(px3 - x1_) / side_len
offset_py3 = 0#(py3 - y1_) / side_len
offset_px4 = 0#(px4 - x1_) / side_len
offset_py4 = 0#(py4 - y1_) / side_len
offset_px5 = 0#(px5 - x1_) / side_len
offset_py5 = 0#(py5 - y1_) / side_len
crop_box = [_x1, _y1, _x2, _y2]
face_crop = img.crop(crop_box) #图片裁剪
face_resize = face_crop.resize((face_size, face_size)) #对裁剪后的图片缩放
iou = utils.iou(crop_box, np.array(boxes))[0]
if iou > 0.65: #可以自己修改
positive_anno_file.write(
"positive/{0}.jpg {1} {2} {3} {4} {5} {6} {7} {8} {9} {10} {11} {12} {13} {14} {15}\n".format(
positive_count, 1, offset_x1, offset_y1,
offset_x2, offset_y2, offset_px1, offset_py1, offset_px2, offset_py2, offset_px3,
offset_py3, offset_px4, offset_py4, offset_px5, offset_py5))
positive_anno_file.flush() #flush() 方法是用来刷新缓冲区的,即将缓冲区中的数据立刻写入文件,同时清空缓冲区
face_resize.save(os.path.join(positive_image_dir, "{0}.jpg".format(positive_count)))
# print("positive_count",positive_count)
positive_count += 1
elif 0.65 > iou > 0.4:
part_anno_file.write(
"part/{0}.jpg {1} {2} {3} {4} {5} {6} {7} {8} {9} {10} {11} {12} {13} {14} {15}\n".format(
part_count, 2, offset_x1, offset_y1,offset_x2,
offset_y2, offset_px1, offset_py1, offset_px2, offset_py2, offset_px3,
offset_py3, offset_px4, offset_py4, offset_px5, offset_py5))
part_anno_file.flush()
face_resize.save(os.path.join(part_imag

7920

被折叠的 条评论
为什么被折叠?



