MTCNN目标检测实战—基于PyTorch的人脸检测算法实战

目录

一、MTCNN简介:

1、什么是MTCNN

2、MTTCNN的作用

3、MTCNN的优缺点

1优点

2缺点

二、人脸检测

三、MTCN的网络模型

四、准备训练样本

1、获取原始数据集

2、准备训练样本

1、训练样本的构成

2、建立数据集样本

3.生成样本数据代码

五、创建网络模型

1PNet

2RNet

3ONet

六、小工具IOU和NMS的创建

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的网络模型

image.png

在MTCNN中主要有三层网络分别是:

P网络:模型训练过程中输入12x12的图像,输出置信度和人脸偏移量。

R网络:输入图像的大小为24x24,对R网络的输出图像和偏移值作进一步处理,处理货的图像和偏移值输入到R网络

O网络:输入的图像的大为48x48,对R网络输出的图像进行最终的判别,最终确定人脸的位置。

四、准备训练样本

1、获取原始数据集

本次模型训练采用Celeba数据集,

下载好Celeba的数据集是一个压缩包,解压之后得到以下文件夹,

image.png

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

解压完成如图:

image.png

2、准备训练样本

1、训练样本的构成

1.种类

本项目需要准备三种样本:正样本:负样本:部分样本约等于1:1:3,之所以部分样本比较多是因为在级联处理中负样本会大量丢失,这样做的目的是为了保证最后进入R网络 和O网络三种样本的比例均衡。

正样本:整张图全是人脸

负样本:图像为背景

部分样本:一张图中有部分是人脸,另外一部分是非人脸。

2.形状

本项目的训练样本共涉及三种大小:12x12(用于P网络训练),24x24(用于R网络训练),48x48(用于O网络训练)

2、建立数据集样本

建立数据集样本应注意以下几点:

1:图片路径和标签一一对应,方便训练时读取数据

2:三个网络是分开训练,不同大小的图片各自有各自的正样本,负样本和部分样本。

3:部分和负样本样本是在正样本附近偏移得到得到

 

最终得到数据样本是这个样子:

image.pngimage.pngimage.png

image.png

image.png

image.png

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
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值