精华内容
下载资源
问答
  • 训练MTCNN

    千次阅读 热门讨论 2018-10-13 14:56:38
    训练MTCNNMTCNN训练记录已经遇到的问题现象2018.09.11:目前只训练了12net,召回率偏低。训练记录2018.09.112018.09.142018.09.172018.09....MTCNN训练记录 最近尝试使用Caffe复现MTCNN,感觉坑很大,记录一下训练...

    MTCNN训练记录

    最近尝试使用Caffe复现MTCNN,感觉坑很大,记录一下训练过程,目前还没有好的结果。网上也有很多童鞋在尝试训练MTCNN,普遍反映使用TensorFlow可以得到比较好的结果,但是使用Caffe不是很乐观。

    已经遇到的问题现象

    2018.09.11:目前只训练了12net,召回率偏低。

    blankWorld/MTCNN-Accelerate-Onet为baseline,blankWorld在FDDB上的测试性能如下图

    FDDB Result

    这个效果很不错,但是我自己生成样本后训练12net,召回率有明显下降。性能对比如下图

    FDDB 12net Compare

    暂且不管12net的测试结果为什么会这么差,两个模型的性能差距是可以反映的。

    训练记录

    2018.09.11

    训练数据生成

    参考AITTSMD/MTCNN-Tensorflow提供的prepare_data进行数据生成。数据集情况如下表

    PositiveNegativePartLandmark
    Training Set156728/189530470184/975229156728/547211313456/357604
    Validation Set10000100001000010000

    其中Pos:Neg:Part:Landmark = 1:3:1:2,样本比例参考原作的比例。Pos、Neg、Part来自于WiderFace,Landmark来自于CelebA。其中正样本进行了人工的数据筛选,筛选的原因是根据WiderFace生成的正样本,有很多都是质量很差的图像,包含人脸大面积遮挡或十分模糊的情况。之前召回率很差的性能来自没有经过筛选的训练集,因为使用了OHEM,只有loss值在前70%的样本才参与梯度计算,感觉如果质量差的样本占比较大,网络学习到的特征是错误的,那些质量好的图像可能得不到充分的学习。

    训练参数设置

    初始训练参数如下

    type:"Adam"
    momentum: 0.9
    momentum2:0.999
    delta:1e-8
    base_lr: 0.01
    weight_decay: 0.0005
    batch_size: 256
    

    第一轮训练在75000次迭代(17.5个epoch)时停止,测试记录如下

    I0911 10:16:25.019253 21722 solver.cpp:347] Iteration 75000, Testing net (#0)
    I0911 10:16:28.057858 21727 data_layer.cpp:89] Restarting data prefetching from start.
    I0911 10:16:28.072748 21722 solver.cpp:414] Test net output #0: cls_Acc = 0.4638
    I0911 10:16:28.072789 21722 solver.cpp:414] Test net output #1: cls_loss = 0.096654 (### 1 = 0.096654 loss)
    I0911 10:16:28.072796 21722 solver.cpp:414] Test net output #2: pts_loss = 0.008529 (### 0.5 = 0.0042645 loss)
    I0911 10:16:28.072801 21722 solver.cpp:414] Test net output #3: roi_loss = 0.0221648 (### 0.5 = 0.0110824 loss)
    

    注意:分类测试结果是0.4638是因为测试集没有打乱,1-10000为pos样本,10001-20000为neg样本,20001-30000为part样本,30001-40000为landmark样本。因此,实际分类正确率应该是0.9276

    降低学习率至0.001,训练135000次迭代(31.5个epoch)时停止,测试记录如下

    I0911 13:14:36.482010 23543 solver.cpp:347] Iteration 135000, Testing net (#0)
    I0911 13:14:39.629933 23660 data_layer.cpp:89] Restarting data prefetching from start.
    I0911 13:14:39.645612 23543 solver.cpp:414] Test net output #0: cls_Acc = 0.4714
    I0911 13:14:39.645649 23543 solver.cpp:414] Test net output #1: cls_loss = 0.0765401 (### 1 = 0.0765401 loss)
    I0911 13:14:39.645656 23543 solver.cpp:414] Test net output #2: pts_loss = 0.00756469 (### 0.5 = 0.00378234 loss)
    I0911 13:14:39.645661 23543 solver.cpp:414] Test net output #3: roi_loss = 0.0201988 (### 0.5 = 0.0100994 loss)
    

    实际分类正确率是0.9428。训练260000次迭代后停止,测试记录如下

    I0911 16:58:47.514267 28442 solver.cpp:347] Iteration 260000, Testing net (#0)
    I0911 16:58:50.624385 28448 data_layer.cpp:89] Restarting data prefetching from start.
    I0911 16:58:50.639556 28442 solver.cpp:414] Test net output #0: cls_Acc = 0.471876
    I0911 16:58:50.639595 28442 solver.cpp:414] Test net output #1: cls_loss = 0.0750447 (### 1 = 0.0750447 loss)
    I0911 16:58:50.639602 28442 solver.cpp:414] Test net output #2: pts_loss = 0.0074394 (### 0.5 = 0.0037197 loss)
    I0911 16:58:50.639608 28442 solver.cpp:414] Test net output #3: roi_loss = 0.0199694 (### 0.5 = 0.00998469 loss)
    

    实际分类正确率是0.943752。

    问题: 训练结果看似还可以,但是召回率很低,在阈值设置为0.3的情况下,召回率也才将将达到90%。阈值要设置到0.05,才能达到97%-98%的召回率,ROC曲线如下图。严格来说这个测试并不严谨,应该用检测器直接在图像中进行检测,但是为了方便,我直接用val集上的性能画出了ROC曲线,其中的FDDB曲线是将的人脸区域截取出来进行测试得到的。

    12net 1st ROC

    2018.09.14

    使用上述12net在WiderFace上提取正负样本,提取结果如下:

    ThresholedPositiveNegativePart
    0.058521036745286632861
    0.5662246299420354350

    2018.09.17

    准备24net的训练样本。由于生成12net检测到的正样本数目有限,训练24net的pos样本包含两部分,一部分是训练12net的正样本,一部分是经过筛选的12net检测到的正样本;neg样本和part样本全部来自12net的难例;landmark与12net共用样本。经过采样后达到样本比例1:3:1:2,样本数目如下表:

    PositiveNegativePartLandmark
    Training Set225172675516225172313456
    Validation Set10000100001000010000

    训练过程与12net类似,学习率从0.01下降到0.0001,最终的训练结果如下

    I0917 15:19:00.631140 36330 solver.cpp:347] Iteration 70000, Testing net (#0)
    I0917 15:19:03.305665 36335 data_layer.cpp:89] Restarting data prefetching from start.
    I0917 15:19:03.317827 36330 solver.cpp:414] Test net output #0: cls_Acc = 0.481501
    I0917 15:19:03.317865 36330 solver.cpp:414] Test net output #1: cls_loss = 0.0479137 (### 1 = 0.0479137 loss)
    I0917 15:19:03.317874 36330 solver.cpp:414] Test net output #2: pts_loss = 0.00631254 (### 0.5 = 0.00315627 loss)
    I0917 15:19:03.317879 36330 solver.cpp:414] Test net output #3: roi_loss = 0.0179083 (### 0.5 = 0.00895414 loss)
    

    实际分类正确率是0.963。ROC曲线如下图,同样使用val集上的性能画出曲线。

    24net 1st ROC

    2018.09.18

    使用24net在WiderFace上提取正负样本,提取结果如下:

    ThresholedPositiveNegativePart
    0.5, 0.58639683212225285

    利用以上数据生成48net的训练样本,由于24net生成的样本数量有限,结合前两次训练所用的数据,生成训练集:

    PositiveNegativePartLandmark
    Training Set283616850848283616567232
    Validation Set10000100001000010000

    2018.09.19

    在训练48net的过程中,首先尝试了Adam算法进行优化,后来发现训练十分不稳定。转而使用SGD进行优化,效果好转。训练初始参数如下:

    type:"SGD"
    base_lr: 0.01
    momentum: 0.9
    weight_decay: 0.0005
    

    48net的训练结果比较一般,性能如下:

    I0919 18:02:22.318362  3822 solver.cpp:347] Iteration 165000, Testing net (#0)
    I0919 18:02:25.877437  3827 data_layer.cpp:89] Restarting data prefetching from start.
    I0919 18:02:25.894898  3822 solver.cpp:414] Test net output #0: cls_Acc = 0.4662
    I0919 18:02:25.894937  3822 solver.cpp:414] Test net output #1: cls_loss = 0.0917524 (### 1 = 0.0917524 loss)
    I0919 18:02:25.894943  3822 solver.cpp:414] Test net output #2: pts_loss = 0.00566356 (### 1 = 0.00566356 loss)
    I0919 18:02:25.894948  3822 solver.cpp:414] Test net output #3: roi_loss = 0.0177907 (### 0.5 = 0.00889534 loss)
    

    实际的分类精度为0.9324。整体来看基本实现了文章中参考文献[19]在验证集上的性能,性能对比如下表

    CNN12-net24-net48-net
    [19]94.4%95.1%93.2%
    MTCNN94.6%95.4%95.4%
    Ours94.3%96.3%93.2%

    2018.09.20

    整个系统连通后进行测试,发现人脸框抖动比较厉害,这应该是训练过程和样本带来的问题。

    比较奇怪的问题是在Caffe上进行CPU运算时,速度极慢,尤其12net运行速度慢30倍左右。通过观察参数分布发现,有大量kernel都是全零分布,初步感觉是因为Adam和ignore label相互作用的结果,即ignore label的样本会产生0值loss,这些loss会影响Adam的优化过程,具体原因还需进一步理论推导。目前的解决方案是将含有大量0值kernel的层随机初始化,使用SGD进行训练。至于抖动的问题,需要进一步分析。重训后的模型性能如下表:

    12-net24-net48-net
    Accuracy94.59%96.52%93.94%

    2018.09.26

    目前来看回归器的训练是完全失败。失败的原因可能有以下几点:

    1. 对欧拉损失层做了代码修改,用来支持ignore label,可能代码出现问题
    2. 数据本身存在问题,需要验证数据的正确性

    先训练一个不带ignore label的回归器,看loss是否发生变化。

    2018.09.27

    做了一个实验,用20张图像生成一个数据集,经过训练,网络是可以完全过拟合这个数据集的,说明数据和代码是没有问题的,但是加大数据量后,bounding box的回归依旧不好。通过跟大神讨论,发现自己犯了一个很弱智的低级错误,回归问题是不可以做镜像的,回归问题是不可以做镜像的,回归问题是不可以做镜像的,重要的事情说三遍,如果图像做镜像操作,那么标注也需要进行镜像操作。

    重训后的模型性能如下表:

    12-net24-net48-net
    Accuracy94.35%97.45%94.67%

    2018.10.01

    实验还是不够顺利,有些受挫感。目前来看12net和24net的训练是相对顺利的,至少能够去除大量虚检并给出相对准确的回归框。48net的训练比较失败,存在虚检、定位不准以及关键点定位不准的问题。

    在训练12net和24net时,因为网络较小,而且这两个网络都不负责输出关键点,所以训练时只训练了分类和框回归问题。训练48net时三个loss都很重要,感觉按照原文的loss weight进行配置会造成回归问题学习不充分的问题,需要提高回归问题的loss weight。

    接下来的工作包括:

    1. 之前的训练为了快速验证思路,并没有严格按照后一阶段数据是前一阶段数据中的难例的原则,接下来需要重新走这个流程。
    2. 探索48net的训练过程。具体来说有几个点需要尝试,首先,是刚刚提到的难例挖掘;其次,调整各个loss的权重;最后,除了阶段间的难例挖掘,也应注意阶段内的难例挖掘。
    3. 以24net在landmark数据集上进行检测,将其输出作为48net进行landmark回归的输入。

    2018.10.13

    最近一次训练的记录如下:

    12net

    12net的样本由随机采样得来。

    PositiveNegativePart
    Training Set156728470184156728
    Validation Set100001000010000

    验证集上的性能为:

    Test net output #0: cls_Acc = 0.9435
    Test net output #1: cls_loss = 0.0747717 (### 1 = 0.0747717 loss)
    Test net output #2: roi_loss = 0.0168385 (### 0.5 = 0.00841924 loss)
    

    24net

    24net的样本全部来自12net的检测结果。

    PositiveNegativePart
    Training Set60149180447120298
    Validation Set50015001500

    验证集上的性能为:

    Test net output #0: cls_Acc = 0.977588
    Test net output #1: cls_loss = 0.0648633 (### 1 = 0.0648633 loss)
    Test net output #2: roi_loss = 0.0192365 (### 5 = 0.0961826 loss)
    

    48net

    48net的正样本和part样本来自于24net在widerface上的检测结果,负样本来自于24net在widerface和celeba上的检测结果,landmark样本来自于24net在celeba上的检测结果。

    PositiveNegativePartlandmark
    Training Set242862728586242862485724
    Validation Set5000500050005000

    在验证集上的性能为:

    Test net output #0: cls_Acc = 0.978155
    Test net output #1: cls_loss = 0.0694968 (### 1 = 0.0694968 loss)
    Test net output #2: pts_loss = 0.00119616 (### 5 = 0.00598078 loss)
    Test net output #3: roi_loss = 0.0111277 (### 1 = 0.0111277 loss)
    

    使用0.5,0.5,0.5作为阈值,在FDDB上测得的discROC曲线如下图

    ROC 20181013

    展开全文
  • MTCNN中用celebA样本生成landmark训练样本python代码解读 2017年08月28日 17:36:32 XZZPPP 阅读数:1136更多 <div class="...

    MTCNN中用celebA样本生成landmark训练样本python代码解读

    最近跑通了MTCNN的训练代码,对其中生成positive,negative,part样本gen_48net_data2.py代码进行修改。

    项目地址:https://github.com/dlunion/mtcnn

    对应代码地址:https://github.com/dlunion/mtcnn/blob/master/train/gen_48net_data2.py


    修改后实现:用celebA样本生成MTCNN的landmark训练样本。

    标注文件格式如下:

    000001.jpg 138 70 343 330 165 184 244 176 196 249 194 271 266 260
    000002.jpg 105 107 295 362 140 204 220 204 168 254 146 289 226 289
    000003.jpg 205 50 267 173 244 104 264 105 263 121 235 134 251 140
    000004.jpg 505 172 1069 932 796 539 984 539 930 687 762 756 915 756
    000005.jpg 241 98 365 239 273 169 328 161 298 172 283 208 323 207
    000006.jpg 149 95 291 270 202 158 259 165 234 196 197 228 243 233
    000007.jpg 71 93 256 335 129 198 204 190 163 242 140 277 202 271
    

    第1列表示: 图片名;

    第2-5列表示:标注的人脸bbox位置, x1,y1,x2,y2;

    第6-15列表示:标注的landmark位置,  ptx0,pty0,ptx1,pty1,ptx2,pty2,ptx3,pty3,ptx4,pty4;


    主要思路:在bbox基础上,随机扰动生成很多bbox,当且仅当五个landmark都在随机扰动生成的crop区域中时,认为是符合要求的landmark训练样本。   代码如下:


    import sys
    sys.path.append('D:\\Anaconda2\\libs')  #导入系统路径,以便cv2模块的导入
    

    import numpy as np
    import cv2
    import os
    import numpy.random as npr
    from utils import IoU

    stdsize = 48

    标注txt文件路径,celebA原图路径

    anno_file = “E:/face_alignment/data/CelebA/Anno/celebA_bbox_landmark.txt”
    im_dir = “E:/face_alignment/data/CelebA/Img/img_celeba.7z/img_celeba/”

    landmark样本的保存路径

    pos_save_dir = str(stdsize) + “/landmark”
    save_dir = “./” + str(stdsize)

    创建文件夹函数

    def mkr(dr):
    if not os.path.exists(dr):
    os.mkdir(dr)

    mkr(save_dir)
    mkr(pos_save_dir)

    f1 = open(os.path.join(save_dir, ‘landmark_’ + str(stdsize) + ‘.txt’), ‘w’)
    with open(anno_file, ‘r’) as f:
    annotations = f.readlines()
    num = len(annotations)
    print “%d pics in total” % num
    p_idx = 0 # positive
    d_idx = 0 # dont care
    idx = 0
    box_idx = 0

    for annotation in annotations:
    # strip():去除annotations开头、结尾处空白符,split(’ ‘)按空格进行切片
    annotation = annotation.strip().split(’ ')
    im_path = annotation[0] # 图片名
    bbox = map(float, annotation[1:-10]) # bbox 坐标
    pts = map(float, annotation[-10:]) # landmark 坐标
    boxes = np.array(bbox, dtype=np.float32).reshape(-1, 4) # 将bbox转化为矩阵,并将列resize为4
    im_path = im_dir + im_path # 图片全路径
    img = cv2.imread(im_path) # 读取图片
    idx += 1
    if idx % 100 == 0:
    print idx, “images done”

    height, width, channel = img.shape
    
    
    backupPts = pts[:]        
    for box in boxes:
        # box (x_left, y_top, x_right, y_bottom)
        x1, y1, x2, y2 = box
        w = x2 - x1 + 1
        h = y2 - y1 + 1
    
        # ignore small faces
        # in case the ground truth boxes of small faces are not accurate
        if max(w, h) < 12 or x1 < 0 or y1 < 0:
            continue
    
        # generate landmark examples and part faces
    	# 对bbox进行随机scale,offset,得到新的crop区域,即对样本进行扰动,做样本增强
        for i in range(10):
            pts = backupPts[:]
            size = npr.randint(int(min(w, h) * 0.9), np.ceil(1.1 * max(w, h)))
            # scale做(0.9~1.1)之间扰动,offser做(-0.1~0.1)之间扰动
            # delta here is the offset of box center
            delta_x = npr.randint(-w * 0.1, w * 0.1)
            delta_y = npr.randint(-h * 0.1, h * 0.1)
    
            nx1 = max(x1 + w / 2 + delta_x - size / 2, 0)
            ny1 = max(y1 + h / 2 + delta_y - size / 2, 0)
            nx2 = nx1 + size
            ny2 = ny1 + size
    
            if nx2 > width or ny2 > height:
                continue
            crop_box = np.array([nx1, ny1, nx2, ny2])
    
            offset_x1 = (x1 - nx1) / float(size)
            offset_y1 = (y1 - ny1) / float(size)
            offset_x2 = (x2 - nx2) / float(size)
            offset_y2 = (y2 - ny2) / float(size)
    
    		# 当且仅当五个landmark都在随机扰动生成的crop区域中时,才保持使用
            if pts[0] < nx1 or pts[0] > nx2:
                continue
            if pts[2] < nx1 or pts[2] > nx2:
                continue
            if pts[4] < nx1 or pts[4] > nx2:
                continue
            if pts[6] < nx1 or pts[6] > nx2:
                continue
            if pts[8] < nx1 or pts[8] > nx2:
                continue
    		
            if pts[1] < ny1 or pts[1] > ny2:
                continue
            if pts[3] < ny1 or pts[3] > ny2:
                continue
            if pts[5] < ny1 or pts[5] > ny2:
                continue
            if pts[7] < ny1 or pts[7] > ny2:
                continue
            if pts[9] < ny1 or pts[9] > ny2:
                continue
    		
            ptss = pts[:]
    		# 将landmark转化为相对于左上角的归一化值
            for k in range(len(ptss) / 2):
                pts[k] = (ptss[k*2] - nx1) / float(size);
                pts[k+5] = (ptss[k*2+1] - ny1) / float(size);
    
    		# 从原图中crop图片区域,并resize成stdsize大小
            cropped_im = img[int(ny1) : int(ny2), int(nx1) : int(nx2), :]
            resized_im = cv2.resize(cropped_im, (stdsize, stdsize), interpolation=cv2.INTER_LINEAR)
    
            box_ = box.reshape(1, -1)
    
    		# 将图片名,bbox偏移量写入txt文本中
            save_file = os.path.join(pos_save_dir, "%s.jpg"%p_idx)
            f1.write(str(stdsize)+"/landmark/%s.jpg"%p_idx + ' -1 -1 -1 -1 -1')
             
    		# 将landmark坐标写入txt文件中
            for k in range(len(pts)):
                f1.write(" %f" % pts[k])
            f1.write("\n")
            cv2.imwrite(save_file, resized_im)  # 保存图片
            p_idx += 1
    		
    
        box_idx += 1
        print "%s images done, pos: %s part: %s "%(idx, p_idx, d_idx)
    

    f1.close()



    得到的效果图如下:





    展开全文
  • 利用celebA数据集训练MTCNN网络

    千次阅读 多人点赞 2020-09-22 19:14:52
    利用celebA数据集训练MTCNN网络celebA数据集简介训练数据的处理网络和训练侦测部分结果展示 有问题可以联系我的邮箱:2487429219@qq.com 关于MTCNN网络可以看我上一篇博客:链接: 人脸检测算法:mtcnn简介 celebA...


    有问题可以联系我的邮箱:2487429219@qq.com
    关于MTCNN网络可以看我上一篇博客:链接: 人脸检测算法:mtcnn简介

    celebA数据集简介

    CelebA数据集包含202,599张名人人脸图片,有人脸框,特征点等标注信息,数据量大,可以用来训练mtcnn网络,官方下载链接:celebA

    下载链接里共有多个下载选项,我选择使用的是In-The-Wild Images,具体每个选择代表什么含义可以参照网上的celebA数据集详解。
    下载
    如果官方的链接下载不了或者速度太慢,可以去网上搜bdy链接。

    下载完成后会有如下文件:
    下载好的文件
    其中img_celeba文件夹里面是202, 599名人图片,但是图片不仅仅是包含人的脸部,需要进一步处理,这个后文会说明。
    图片
    Eval文件夹里的内容在我训练mtcnn时并没有用到,这里不介绍,有兴趣可以去查找celebA数据集介绍。

    Anno文件夹里面是对数据的一些标注,在训练人脸检测时,我只用到了人脸框和特征点的标注,对应list_bbox_celeba.txt和list_landmarks_celeba.txt
    标注

    训练数据的处理

    由于我们下载的celebA数据集的数据并非只有人的脸部,所以在数据上我们需要进行一些处理。利用标注好的人脸框从原数据中裁剪出人脸并做好标注,获得训练用的数据集。
    并且由于PNet,RNet,ONet所需要的数据大小不同(分别对应12,24,48),我们需要为每个网络准备对应的数据。多说无益,代码如下:
    这是iou计算:

    import numpy as np
    
    
    def iou(box, bbox, ismin=False):
        """
        :param box: true box
        :param bbox: other boxes
        :param ismin: Whether to use min mode
        :return: iou value
        """
        x1, y1, x2, y2 = box[0],  box[1],  box[2],  box[3]
        _x1, _y1, _x2, _y2 = bbox[:, 0], bbox[:, 1], bbox[:, 2], bbox[:, 3]
        # the area
        area1 = (x2 - x1) * (y2 - y1)
        area2 = (_x2 - _x1) * (_y2 - _y1)
        # find out the intersection
        xx1, yy1, xx2, yy2 = np.maximum(x1, _x1), np.maximum(y1, _y1), np.minimum(x2, _x2), np.minimum(y2, _y2)
        w, h = np.maximum(0, xx2-xx1), np.maximum(0, yy2-yy1)
        inter_area = w*h
        # the list to save the iou value
        iou_box = np.zeros([bbox.shape[0], ])
        # Prevents zeros from being divided.
        zero_index = np.nonzero(inter_area == 0)
        no_zero = np.nonzero(inter_area)
        iou_box[zero_index] = 0
        if ismin:
            iou_box[no_zero] = inter_area[no_zero] / (np.minimum(area1, area2)[no_zero])
        else:
            iou_box[no_zero] = inter_area[no_zero] / ((area1 + area2 - inter_area)[no_zero])
        return iou_box
    
    if __name__ == '__main__':
        box1 = [100, 100, 200, 200]
        bbox1 = np.array([[100, 90, 200, 200], [120, 120, 180, 180], [200, 200, 300, 300]])
        a = iou(box1, bbox1)
        print(a.shape)
        print(a)
    
    
    

    iou的概念不理解的可以看一下我上一篇博客:链接: 人脸检测算法:mtcnn简介
    这是获取数据的文件:

    from PIL import Image
    import os
    import numpy as np
    import utils
    
    
    def gen_data(path, size):
        """
        :param path: the path of images and label files
        :param size: the size of the img data
        """
        box_file = os.path.join(path, r'Anno/list_bbox_celeba.txt')  # the box label file
        landmarks_file = os.path.join(path, r'Anno/list_landmarks_celeba.txt')  # the landmarks label file
    
        saved_path = r'T:\mtcnn\celebA'  # the save path of label files and homologous images
        if not os.path.exists(saved_path):
            os.makedirs(saved_path)
        box_content = open(box_file, 'r', encoding='utf-8').readlines()  # the content of the box label file
        # the content of the landmarks label file
        landmarks_content = open(landmarks_file, 'r', encoding='utf-8').readlines()
    
        if not os.path.exists(os.path.join(saved_path, str(size))):
            os.makedirs(os.path.join(saved_path, str(size), r'positive'))
            os.makedirs(os.path.join(saved_path, str(size), r'negative'))
            os.makedirs(os.path.join(saved_path, str(size), r'part'))
    
        positive_num = 0
        negative_num = 0
        part_num = 0
    
        # txt to save the label
        f_positive = open(os.path.join(saved_path, str(size), 'positive.txt'), 'w', encoding='utf-8')
        f_part = open(os.path.join(saved_path, str(size), 'part.txt'), 'w', encoding='utf-8')
        f_negative = open(os.path.join(saved_path, str(size), 'negative.txt'), 'w', encoding='utf-8')
        f_positive_landmark = open(os.path.join(saved_path, str(size), 'positive_landmark.txt'), 'w', encoding='utf-8')
        f_part_landmark = open(os.path.join(saved_path, str(size), 'part_landmark.txt'), 'w', encoding='utf-8')
        f_negative_landmark = open(os.path.join(saved_path, str(size), 'negative_landmark.txt'), 'w', encoding='utf-8')
    
        for i, content in enumerate(box_content):
            if i < 2:  # skip the first two lines
                continue
            content_list = content.strip().split()  # the list to save a line of the box file's content
            landmarks_list = landmarks_content[i].strip().split()  # the list to save a line of the landmark file's content
            img = Image.open(os.path.join(os.path.join(path, r'img_celeba'), content_list[0]))
            # the times to use one image
            for _ in range(3):
                # Gets the coordinates and size of the box starting point
                x, y, w, h = int(content_list[1]), int(content_list[2]), int(content_list[3]), int(content_list[4])
                x1, y1, x2, y2 = x, y, x+w, y+h
                # Randomly crop the picture
                cx, cy = int(x + w / 2), int(y + h / 2)
                _cx, _cy = cx + np.random.randint(-0.2 * w, 0.2 * w + 1), cy + np.random.randint(-0.2 * h, 0.2 * h + 1)
                _w, _h = w + np.random.randint(-0.2 * w, 0.2 * w + 1), h + np.random.randint(-0.2 * h, 0.2 * h + 1)
                _x1, _y1, _x2, _y2 = int(_cx - _w / 2), int(_cy - _h / 2), int(_cx + _w / 2), int(_cy + _h / 2)
                # get the landmark point
                ex1, ey1, ex2, ey2 = int(landmarks_list[1]), int(landmarks_list[2]), int(landmarks_list[3]), int(landmarks_list
                                                                                                                 [4])
                nx1, ny1, mx1, my1 = int(landmarks_list[5]), int(landmarks_list[6]), int(landmarks_list[7]), int(landmarks_list
                                                                                                                 [8])
                mx2, my2 = int(landmarks_list[9]), int(landmarks_list[10])
                nex1, ney1, nex2, ney2 = (ex1 - _x1), (ey1 - _y1), (ex2 - _x1), (ey2 - _y1)
                nnx1, nny1, nmx1, nmy1 = (nx1 - _x1), (ny1 - _y1), (mx1 - _x1), (my1 - _y1)
                nmx2, nmy2 = (mx2 - _x1), (my2 - _y1)
    
                # Cut out pictures
                crop_img = img.crop([_x1, _y1, _x2, _y2])
                crop_img = crop_img.resize([size, size])
    
                # calculate the iou value
                iou = utils.iou([x1, y1, x2, y2], np.array([[_x1, _y1, _x2, _y2]]))
                # calculate the offset value
                try:
                    _x1_off, _y1_off, _x2_off, _y2_off = (x1 - _x1)/_w, (y1 - _y1)/_h, (x2 - _x2)/_w, (y2 - _y2)/_h
                except ZeroDivisionError:
                    continue
    
                if iou > 0.65:
                    crop_img.save(os.path.join(saved_path, str(size), r'positive', r'%s.jpg' % positive_num))
                    f_positive.write(f'{positive_num}.jpg 1 {_x1_off} {_y1_off} {_x2_off} {_y2_off}\n')
                    f_positive_landmark.write(f"{positive_num}.jpg {nex1/_w} {ney1/_h} {nex2/_w} {ney2/_h} {nnx1/_w} {nny1/_h} {nmx1/_w} {nmy1/_h} "
                                              f"{nmx2/_w} {nmy2/_h}\n")
                    f_positive.flush()
                    positive_num += 1
    
                elif iou > 0.4:
                    crop_img.save(os.path.join(saved_path, str(size), r'part', r'%s.jpg' % part_num))
                    f_part.write(f'{part_num}.jpg 2 {_x1_off} {_y1_off} {_x2_off} {_y2_off}\n')
                    f_part_landmark.write(f"{part_num}.jpg {nex1/_w} {ney1/_h} {nex2/_w} {ney2/_h} {nnx1/_w} {nny1/_h} {nmx1/_w} {nmy1/_h} "
                                              f"{nmx2/_w} {nmy2/_h}\n")
                    f_part.flush()
                    part_num += 1
    
                elif iou < 0.29:
                    crop_img.save(os.path.join(saved_path, str(size), r'negative', r'%s.jpg' % negative_num))
                    f_negative.write(f'{negative_num}.jpg 0 0 0 0 0\n')
                    f_negative_landmark.write(f'{negative_num}.jpg 0 0 0 0 0 0 0 0 0 0\n')
                    negative_num += 1
    
                # get the negative data
                w, h = img.size
                _x1, _y1 = np.random.randint(0, w), np.random.randint(0, h)
                _w, _h = np.random.randint(0, w - x1), np.random.randint(0, h - y1)
                _x2, _y2 = x1 + _w, y1 + _h
                crop_img1 = img.crop([_x1, _y1, _x2, _y2])
                crop_img1 = crop_img1.resize((size, size))
                iou = utils.iou(np.array([x1, y1, x2, y2]), np.array([[_x1, _y1, _x2, _y2]]))
                if iou < 0.29:
                    crop_img1.save(os.path.join(saved_path, str(size), r'negative', r'%s.jpg' % negative_num))
                    f_negative.write(f'{negative_num}.jpg 0 0 0 0 0\n')
                    f_negative_landmark.write(f'{negative_num}.jpg 0 0 0 0 0 0 0 0 0 0\n')
                    negative_num += 1
            if i % 1000 == 0:
                print("%s/202599" % (i+1))
        # close the file
        f_positive.close()
        f_positive_landmark.close()
        f_part.close()
        f_part_landmark.close()
        f_negative.close()
        f_negative_landmark.close()
    
    
    if __name__ == '__main__':
        gen_data(r'F:\\', 12)
    
    

    这边我注释可能写的不太清楚,我对一些地方稍作解释:

    1. 我获取的不仅仅是正样本数据,即有人脸的数据,为了训练网络,我需要给网络投喂一些负样本数据,部分样本数据其实可以不需要。获取正样本和负样本数据的方法是,在人脸所在框附近小范围随机裁剪,再和原框位置计算iou,iou大于0.65的记为正样本,0.4到0.65之间的记为部分样本。
      正样本
      部分样本
      获取负样本的方法则较为简单,直接在图片中随机裁剪,iou小于0.29的记为负样本。
      用这样的方法获取样本,可以获取更多的数据,利于训练。

    2. 每张图片我们裁剪多次,以充分利用数据,裁剪后resize成对应大小。

    3. 记录框和特征点的坐标时,采用的是记录偏移量,这样可以提高准确度,网络输出的也是偏移量,所以最后侦测的时候需要反算一波。

    网络和训练

    下面是网络,这部分没什么好介绍的,看网络结构对着写就可以:

    import torch.nn as nn
    import torch
    
    
    class PNet(nn.Module):
        def __init__(self):
            super(PNet, self).__init__()
    
            self.pre_layer = nn.Sequential(
                nn.Conv2d(3, 10, kernel_size=3, stride=1, padding=1),
                nn.PReLU(),
                nn.MaxPool2d(kernel_size=3, stride=2),
                nn.Conv2d(10, 16, kernel_size=3, stride=1),
                nn.PReLU(),
                nn.Conv2d(16, 32, kernel_size=3, stride=1),
                nn.PReLU(),
            )
            self.offset_layer = nn.Conv2d(32, 4, kernel_size=1, stride=1)
            self.landmark_layer = nn.Conv2d(32, 10, kernel_size=1, stride=1)
            self.confidence_layer = nn.Conv2d(32, 1, kernel_size=1, stride=1)
    
        def forward(self, x):
            x = self.pre_layer(x)
            offset = self.offset_layer(x)
            landmark = self.landmark_layer(x)
            confidence = torch.sigmoid(self.confidence_layer(x))
            return offset, landmark, confidence
    
    
    class RNet(nn.Module):
        def __init__(self):
            super(RNet, self).__init__()
    
            self.pre_layer = nn.Sequential(
                nn.Conv2d(3, 28, kernel_size=3, stride=1, padding=1),
                nn.PReLU(),
                nn.MaxPool2d(kernel_size=3, stride=2),
                nn.Conv2d(28, 48, kernel_size=3, stride=1),
                nn.PReLU(),
                nn.MaxPool2d(kernel_size=3, stride=2),
                nn.Conv2d(48, 64, kernel_size=2, stride=1),
                nn.PReLU(),
            )
            self.linear = nn.Sequential(
                nn.Linear(64 * 3 * 3, 128),
                nn.PReLU()
            )
            self.offset = nn.Linear(128, 4)
            self.landmark = nn.Linear(128, 10)
            self.confidence = nn.Linear(128, 1)
    
        def forward(self, x):
            x = self.pre_layer(x)
            x = x.view(x.size(0), -1)
            x = self.linear(x)
            offset = self.offset(x)
            landmark = self.landmark(x)
            confidence = torch.sigmoid(self.confidence(x))
            return offset, landmark, confidence
    
    
    class ONet(nn.Module):
        def __init__(self):
            super(ONet, self).__init__()
    
            self.pre_layer = nn.Sequential(
                nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1),
                nn.PReLU(),
                nn.MaxPool2d(kernel_size=3, stride=2),
                nn.Conv2d(32, 64, kernel_size=3, stride=1),
                nn.PReLU(),
                nn.MaxPool2d(kernel_size=3, stride=2),
                nn.Conv2d(64, 64, kernel_size=3, stride=1),
                nn.PReLU(),
                nn.MaxPool2d(kernel_size=2, stride=2),
                nn.Conv2d(64, 128, kernel_size=2, stride=1),
                nn.PReLU(),
            )
            self.linear = nn.Sequential(
                nn.Linear(128*3*3, 256),
                nn.PReLU()
            )
            self.offset = nn.Linear(256, 4)
            self.landmark = nn.Linear(256, 10)
            self.confidence = nn.Linear(256, 1)
    
        def forward(self, x):
            x = self.pre_layer(x)
            x = x.view(x.size(0), -1)
            x = self.linear(x)
            offset = self.offset(x)
            landmark = self.landmark(x)
            confidence = torch.sigmoid(self.confidence(x))
            return offset, landmark, confidence
    
    

    这书数据集:

    import torch
    from torch.utils.data import Dataset
    from PIL import Image
    from torchvision import transforms
    import os
    
    
    class FaceData1(Dataset):  # 用的celebA 有特征点
        def __init__(self, path):
            self.path = path
            self.dataset = list()
            self.path_box = ["negative", "positive", "part"]
            f_positive_box = open(os.path.join(self.path, "positive.txt"))
            f_part_box = open(os.path.join(self.path, "part.txt"))
            f_negative_box = open(os.path.join(self.path, "negative.txt"))
            self.positive_box = f_positive_box.readlines()
            f_positive_box.close()
            self.part_box = f_part_box.readlines()
            f_part_box.close()
            self.negative_box = f_negative_box.readlines()
            f_negative_box.close()
            self.dataset.extend(self.positive_box)
            self.dataset.extend(self.part_box)
            self.dataset.extend(self.negative_box)
            self.dataset_landmark = list()
            f_positive_landmark = open(os.path.join(self.path, "positive_landmark.txt"))
            f_part_landmark = open(os.path.join(self.path, "part_landmark.txt"))
            f_negative_landmark = open(os.path.join(self.path, "negative_landmark.txt"))
            self.positive_landmark = f_positive_landmark.readlines()
            f_positive_landmark.close()
            self.part_landmark = f_part_landmark.readlines()
            f_part_landmark.close()
            self.negative_landmark = f_negative_landmark.readlines()
            f_negative_landmark.close()
            self.dataset_landmark.extend(self.positive_landmark)
            self.dataset_landmark.extend(self.part_landmark)
            self.dataset_landmark.extend(self.negative_landmark)
            self.transform = transforms.Compose([
                transforms.RandomRotation(15),
                transforms.ToTensor(),
                transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
            ])
    
        def __len__(self):
            return len(self.dataset)
    
        def __getitem__(self, index):
            offset_data = self.dataset[index].strip().split()  # offset and confidence data
            landmark_data = self.dataset_landmark[index].strip().split()  # landmark data
            offset = torch.Tensor([float(offset_data[2]), float(offset_data[3]), float(offset_data[4]),
                                   float(offset_data[5])])
            confidence = torch.Tensor([int(offset_data[1])])
            landmark = torch.Tensor([float(landmark_data[1]), float(landmark_data[2]), float(landmark_data[3]),
                                     float(landmark_data[4]), float(landmark_data[5]), float(landmark_data[6]),
                                     float(landmark_data[7]), float(landmark_data[8]), float(landmark_data[9]),
                                     float(landmark_data[10]), ])
            img = Image.open(os.path.join(self.path, self.path_box[int(offset_data[1])], offset_data[0]))
            img_data = self.transform(img)
            return img_data, confidence, offset, landmark
    
    
    class FaceData2(Dataset):  # wider-face数据集
        def __init__(self, path):
            self.dataset = list()
            self.path = path
            self.path_box = ["negative", "positive", "part"]
            f_positive_box = open(os.path.join(self.path, "positive.txt"))
            f_part_box = open(os.path.join(self.path, "part.txt"))
            f_negative_box = open(os.path.join(self.path, "negative.txt"))
            self.positive_box = f_positive_box.readlines()
            f_positive_box.close()
            self.part_box = f_part_box.readlines()
            f_part_box.close()
            self.negative_box = f_negative_box.readlines()
            f_negative_box.close()
            self.dataset.extend(self.positive_box)
            self.dataset.extend(self.part_box)
            self.dataset.extend(self.negative_box)
            self.transform = transforms.Compose([
                transforms.RandomRotation(15),
                transforms.ToTensor(),
                transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
            ])
    
        def __len__(self):
            return len(self.dataset)
    
        def __getitem__(self, index):
            offset_data = self.dataset[index].strip().split()  # offset and confidence data
            offset = torch.Tensor([float(offset_data[2]), float(offset_data[3]), float(offset_data[4]),
                                   float(offset_data[5])])
            confidence = torch.Tensor([int(offset_data[1])])
            img = Image.open(os.path.join(self.path, self.path_box[int(offset_data[1])], offset_data[0]))
            img_data = self.transform(img)
            return img_data, confidence, offset
    
    
    if __name__ == '__main__':
        f1 = FaceData1(r"T:\mtcnn\celebA\48")
        f2 = FaceData2(r"T:\mtcnn\widerface\12")
        f1_img, f1_conf, f1_offset, f1_landmark = f1[1]
        f2_img, f2_conf, f2_offset = f2[1]
        print(f1_conf.shape)
        print(f2_img.shape)
    
    
    
    
    
    
    
    
    

    这是训练:

    from torch.utils.data import DataLoader
    import torch
    import sample
    import os
    import nets
    import torch.nn as nn
    
    
    class Trainer:
        def __init__(self, net, save_path, dataset_path, iscuda=True):
            """
            :param net: the net to train
            :param save_path: the param's save path
            :param dataset_path: dataset path 
            :param iscuda: is to use cuda
            """
            self.net = net  # nets to train
            self.save_path = save_path  # the path to save the trained model
            self.dataset_path = dataset_path  # the dataset path
            self.iscuda = iscuda
            # use cuda to speed up
            if iscuda:
                self.net.cuda()
            # load the saved model
            if os.path.exists(self.save_path):
                self.net.load_state_dict(torch.load(self.save_path))
            # confidence loss function
            self.conf_loss = nn.BCELoss()  # 二分类交叉熵损失函数
            # offset and landmark loss function
            self.label_loss = nn.MSELoss()  # 均方损失函数
            # optimizer
            self.optimizer = torch.optim.Adam(self.net.parameters())
    
        def train(self):
            face_data = sample.FaceDataSet(self.dataset_path)  # get the sample
            # get the face loader
            face_loader = DataLoader(face_data, batch_size=512, shuffle=True, num_workers=4, drop_last=True)
            for _ in range(50):
                for i, (img, offset, landmark, conf) in enumerate(face_loader):
                    if self.iscuda:
                        img = img.cuda()
                        offset = offset.cuda()
                        landmark = landmark.cuda()
                        conf = conf.cuda()
                    # use net to predict
                    _offset, _landmark, _conf = self.net(img)
                    _offset, _landmark, _conf = _offset.view(-1, 4), _landmark.view(-1, 10), _conf.view(-1, 1)
                    # print(_conf)
                    # get the positive and index
                    label_index = torch.nonzero(conf > 0)
                    # get the loss
                    offset_loss = self.label_loss(_offset[label_index[:, 0]], offset[label_index[:, 0]])
                    landmark_loss = self.label_loss(_landmark[label_index[:, 0]], landmark[label_index[:, 0]])
                    # get the positive and negative index
                    conf_index = torch.nonzero(conf < 2)
                    # get the loss
                    conf_loss = self.conf_loss(_conf[conf_index[:, 0]], conf[conf_index[:, 0]])
                    # all loss
                    loss = offset_loss + landmark_loss + conf_loss
                    # clear the gradient
                    self.optimizer.zero_grad()
                    # calculate the gradient
                    loss.backward()
                    # optimizer
                    self.optimizer.step()
    
                    # save the model
                    if (i + 1) % 300 == 0:
                        print(f"i:{i//300} loss:{loss.cpu().data} conf:{conf_loss.cpu().data} offset:"
                              f"{offset_loss.cpu().data} landmark:{landmark_loss.cpu().data}")
                        torch.save(self.net.state_dict(), self.save_path)
                        print("Save successfully")
    
    
    if __name__ == '__main__':
        save_path1 = r'./param/rnet.pt'
        dataset_path1 = r'T:\mtcnn\24'
        net = nets.RNet()
        t = Trainer(net, save_path1, dataset_path1, True)
        t.train()
    
    
    
    

    训练时,我是用的对于置信度和特征点的损失函数是均方差损失函数,对偏移量的损失函数是二分类交叉熵损失函数。

    侦测部分

    侦测部分我只列出比较重要的部分
    这是p网络侦测部分,包含图像金字塔,不了解的可以看我上一篇:

        def p_detect(self, img):
            scale = 1  # the scaling value
            w, h = img.size  # the size of img
            min_length = min(w, h)  # the min edge
            box_list = []   # to save box
    
            while min_length >= 12:
                img_data = self.transforms(img)  # to tensor
                if self.iscuda:
                    img_data = img_data.cuda()
                img_data.unsqueeze_(0)  # Raise a dimension
                _offset, _landmark, _conf = self.pnet(img_data)  # predict
                _offset, _landmark, _conf = _offset[0].cpu().data, _landmark[0].cpu().data, _conf[0][0].cpu().data
                positive_index = torch.nonzero(_conf > 0.6)
    			#  这部分是特征反算
                for idx in positive_index:
                    # The location in the original image
                    _x1 = (idx[1].float() * 2) / scale
                    _y1 = (idx[0].float() * 2) / scale
                    _x2 = (idx[1].float() * 2 + 12) / scale
                    _y2 = (idx[0].float() * 2 + 12) / scale
                    # The original image size
                    _w, _h = _x2 - _x1, _y2 - _y1
                    offset = _offset[:, idx[0], idx[1]]  # offset
                    landmark = _landmark[:, idx[0], idx[1]]  # landmark
                    # box in the original image
                    x1 = offset[0] * _w + _x1
                    y1 = offset[1] * _h + _y1
                    x2 = offset[2] * _h + _x2
                    y2 = offset[3] * _w + _y2
                    # landmark in the image
                    ex1, ey1, ex2, ey2 = landmark[0]*_w + x1, landmark[1]*_h + y1, landmark[2]*_w + x1, landmark[3]*_h + y1
                    nx, ny = landmark[4]*_w + x1, landmark[5]*_h + y1
                    mx1, my1, mx2, my2 = landmark[6]*_w + x1, landmark[7]*_h + y1, landmark[8]*_w + x1, landmark[9]*_h + y1
    
                    box_list.append([_conf[idx[0], idx[1]], ex1, ey1, ex2, ey2, nx, ny, mx1, my1, mx2, my2, x1, y1, x2, y2])
                # 缩放
                scale *= 0.7
                min_length *= 0.7
                w, h = int(w*0.7), int(h*0.7)
                img = img.resize([w, h])
    
            return utils.nms(np.array(box_list), 0.5)
    

    下面是nms:

    def nms(boxes, thresh=0.3, ismin=False):
        """
        :param boxes: 框
        :param thresh: 阈值
        :param ismin: 是否除以最小值
        :return: nms抑制后的框
        """
        if boxes.shape[0] == 0:  # 框为空时防止报错
            return np.array([])
        # 根据置信度从大到小排序(argsort默认从小到大,加负号从大到小)
        _boxes = boxes[(-boxes[:, 0]).argsort()]
        r_box = []  # 用于存放抑制后剩余的框
        while _boxes.shape[0] > 1:  # 当剩余框大与0个时
            r_box.append(_boxes[0])  # 添加第一个框
            abox = _boxes[0][11:]
            bbox = _boxes[1:][:, 11:]
            idxs = np.where(iou(abox, bbox, ismin) < thresh)  # iou小于thresh框的索引
            _boxes = _boxes[1:][idxs]  # 取出iou小于thresh的框
        if _boxes.shape[0] > 0:
            r_box.append(_boxes[0])  # 添加最后一个框
        return np.stack(r_box)
    

    剩余的侦测可以仿照PNet的侦测完成,这部分就大家自己写了。

    结果展示

    下面给几张检测的结果:在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述
    以上测试图片来自网络。

    那么,本文到此结束了,有问题可以联系我的邮箱:2487429219@qq.com
    文章有错误的话欢迎大佬指正,不甚感激。
    都看到这了,点个赞呗…QwQ…

    展开全文
  • 任务描述:利用celebA进行mtcnn多任务级联卷积神经网络的训练数据制作,目标输出txt里应该含有图片id*1、人脸框*4,人脸特征点坐标*10,共15列数据 代码实现: from tool import utils import traceback import ...

    任务描述:利用celebA进行mtcnn多任务级联卷积神经网络的训练数据制作,目标输出txt里应该含有图片id*1、人脸框*4,人脸特征点坐标*10,共15列数据

    代码实现:

    from tool import utils
    import traceback
    import numpy as np
    import os
    from PIL import Image, ImageFilter
    from PIL import ImageDraw
    from tool import utils
    
    label_position_path = r"H:\CelebA\Anno\list_bbox_celeba.txt"  # bbox
    
    # 标签路径
    img_path = r"H:\CelebA\Img\img_celeba"  # 图片路径
    handel_path = r"H:\CelebA\Save\img_celeba_2"  # 处理完样本保持路径
    label_landmark_path = r"H:\CelebA\Anno\list_landmarks_celeba.txt"  # 关键点标签路径
    
    
    def mkdir(size):  # 创建样本目录
        print("gen %i image" % size)
        rootpath = os.path.join(handel_path, str(size))
        if not os.path.exists(rootpath):
            os.mkdir(rootpath)
        p_dirpath = os.path.join(rootpath, "positive")
        if not os.path.exists(p_dirpath):
            os.mkdir(p_dirpath)
        n_dirpath = os.path.join(rootpath, "negative")
        if not os.path.exists(n_dirpath):
            os.mkdir(n_dirpath)
        t_dirpath = os.path.join(rootpath, "part")
        if not os.path.exists(t_dirpath):
            os.mkdir(t_dirpath)
        return rootpath, p_dirpath, n_dirpath, t_dirpath
    
    
    def sample_handle(size):
        imgcount = 0
        r_path, p_path, n_path, t_path = mkdir(size)  # 创建目录
        p_file = open(r_path + "/positive.txt", "w")
        n_file = open(r_path + "/negative.txt", "w")
        t_file = open(r_path + "/part.txt", "w")
        f_position = open(label_position_path).readlines()  # 读入人脸位置标签
        f_landmark = open(label_landmark_path).readlines()  # 读入人脸关键点坐标标签
        for index in range(len(f_landmark)):
            if index < 2:
                continue
            strs_postion = f_position[index].strip().split(" ")
            strs_landmark = f_landmark[index].strip().split(" ")
            strs_postion = list(filter(bool, strs_postion))
            strs_landmark = list(filter(bool, strs_landmark))
            filename = strs_postion[0]
            print(filename)
    
            x1 = float(strs_postion[1])
    
            y1 = float(strs_postion[2])
    
            w = float(strs_postion[3])
    
            h = float(strs_postion[4])
    
            x2 = x1 + w
    
            y2 = y1 + h
    
            # 5个特征点的坐标
    
            fx1 = float(strs_landmark[1])
    
            fy1 = float(strs_landmark[2])
    
            fx2 = float(strs_landmark[3])
    
            fy2 = float(strs_landmark[4])
    
            fx3 = float(strs_landmark[5])
    
            fy3 = float(strs_landmark[6])
    
            fx4 = float(strs_landmark[7])
    
            fy4 = float(strs_landmark[8])
    
            fx5 = float(strs_landmark[9])
    
            fy5 = float(strs_landmark[10])
    
            if max(w, h) < 40 or x1 < 0 or y1 < 0 or w < 0 or h < 0:
                continue
    
            cx = x1 + w * 0.5  # 中心点
    
            cy = y1 + h * 0.5
    
            side = np.maximum(w, h)
    
            img = Image.open(os.path.join(img_path, filename))
    
            width, high = img.size
    
            # r_img = ImageDraw.Draw(img)
    
            # r_img.rectangle((x1,y1,x2,y2))
    
            for count in range(5):
    
                # 随机浮动产生正方形正、负、部分样本
    
                offset_side = np.random.uniform(-0.2, 0.2) * side
    
                offset_x = np.random.uniform(-0.2, 0.2) * w / 2
    
                offset_y = np.random.uniform(-0.2, 0.2) * h / 2
    
                _cx = cx + offset_x
    
                _cy = cy + offset_y
    
                _side = side + offset_side
    
                _x1 = np.maximum(_cx - _side * 0.5, 0)
    
                _y1 = np.maximum(_cy - _side * 0.5, 0)
    
                _x2 = _x1 + _side
    
                _y2 = _y1 + _side
    
                # 计算偏移值
    
                offset_x1 = (x1 - _x1) / _side
    
                offset_y1 = (y1 - _y1) / _side
    
                offset_x2 = (x2 - _x2) / _side
    
                offset_y2 = (y2 - _y2) / _side
    
                offset_fx1 = (fx1 - _x1) / _side
    
                offset_fy1 = (fy1 - _y1) / _side
    
                offset_fx2 = (fx2 - _x1) / _side
    
                offset_fy2 = (fy2 - _y1) / _side
    
                offset_fx3 = (fx3 - _x1) / _side
    
                offset_fy3 = (fy3 - _y1) / _side
    
                offset_fx4 = (fx4 - _x1) / _side
    
                offset_fy4 = (fy4 - _y1) / _side
    
                offset_fx5 = (fx5 - _x1) / _side
    
                offset_fy5 = (fy5 - _y1) / _side
    
                # 计算IOU
    
                # [x1, y1, x2, y2, 置信度]
    
                box = np.array([x1, y1, x2, y2, 0])
    
                boxs = np.array([[_x1, _y1, _x2, _y2, 0]])
    
                per = utils.iou(box, boxs)
    
                per = per[0]
    
                tempimg = img.crop((_x1, _y1, _x2, _y2)) # 根据人脸框偏移量去原图中裁剪
                tempimg = tempimg.resize((size, size), Image.ANTIALIAS)
                imglist = []
    
                imglist.append(tempimg)
    
                # 图片模糊处理
    
                for _tempimg in imglist:
    
                    if per > 0.65:  # 正样本
    
                        imgcount += 1
    
                        _tempimg.save("{0}/{1}.jpg".format(p_path, imgcount))
    
                        p_file.write(
                            "{0}.jpg 1 {1} {2} {3} {4} {5} {6} {7} {8} {9} {10} {11} {12} {13} {14}\n".format(imgcount,
                                                                                                              offset_x1,
                                                                                                              offset_y1,
                                                                                                              offset_x2,
                                                                                                              offset_y2
    
                                                                                                              , offset_fx1,
                                                                                                              offset_fy1,
                                                                                                              offset_fx2,
                                                                                                              offset_fy2,
                                                                                                              offset_fx3,
                                                                                                              offset_fy3,
                                                                                                              offset_fx4,
                                                                                                              offset_fy4,
                                                                                                              offset_fx5,
                                                                                                              offset_fy5))
    
    
    
                    elif per < 0.3:  # 负样本
    
                        imgcount += 1
    
                        _tempimg.save("{0}/{1}.jpg".format(n_path, imgcount))
    
                        n_file.write("{0}.jpg 0 {1} {2} {3} {4} 0 0 0 0 0 0 0 0 0 0\n".format(imgcount, 0, 0, 0, 0))
    
    
    
                    elif (per > 0.4) and (per < 0.65):  # 部分样本
    
                        imgcount += 1
    
                        _tempimg.save("{0}/{1}.jpg".format(t_path, imgcount))
    
                        t_file.write(
                            "{0}.jpg 2 {1} {2} {3} {4} {5} {6} {7} {8} {9} {10} {11} {12} {13} {14}\n".format(imgcount,
                                                                                                              offset_x1,
                                                                                                              offset_y1,
                                                                                                              offset_x2,
                                                                                                              offset_y2
    
                                                                                                              , offset_fx1,
                                                                                                              offset_fy1,
                                                                                                              offset_fx2,
                                                                                                              offset_fy2,
                                                                                                              offset_fx3,
                                                                                                              offset_fy3,
                                                                                                              offset_fx4,
                                                                                                              offset_fy4,
                                                                                                              offset_fx5,
                                                                                                              offset_fy5))
    
            # 再创建负样本
    
            for i in range(10):
    
                offset_side = np.random.uniform(-0.2, 0.2) * side
    
                _side = side + offset_side
    
                _x1 = np.random.uniform(0, width - _side)
    
                _y1 = np.random.uniform(0, high - _side)
    
                _x2 = _x1 + _side
    
                _y2 = _y1 + _side
    
                # 计算偏移值
    
                offset_x1 = (x1 - _x1) / _side
    
                offset_y1 = (y1 - _y1) / _side
    
                offset_x2 = (x2 - _x2) / _side
    
                offset_y2 = (y2 - _y2) / _side
    
                # 计算IOU
    
                # [x1, y1, x2, y2, 置信度]
    
                box = np.array([x1, y1, x2, y2, 0])
    
                boxs = np.array([[_x1, _y1, _x2, _y2, 0]])
    
                per = utils.iou(box, boxs)
    
                per = per[0]
    
                # 截取图片
    
                tempimg = img.crop((_x1, _y1, _x2, _y2))
    
                tempimg = tempimg.resize((size, size), Image.ANTIALIAS)
    
                imglist = []
    
                imglist.append(tempimg)
    
                for _tempimg in imglist:
    
                    if per < 0.3:
                        imgcount += 1
    
                        _tempimg.save("{0}/{1}.jpg".format(n_path, imgcount))
    
                        n_file.write("{0}.jpg 0 {1} {2} {3} {4} 0 0 0 0 0 0 0 0 0 0\n".format(imgcount, 0, 0, 0, 0))
    
        p_file.close()
    
        n_file.close()
    
        t_file.close()
    
    
    if __name__ == '__main__':
        sample_handle(12)
        sample_handle(24)
        sample_handle(48)
    

    main函数里对应的是P、R、O三个网络在训练时所要求的图片尺寸大小。

    展开全文
  • 最简单最详细的MTCNN讲解

    千次阅读 2020-04-15 17:53:37
    特别是P-Net的输入到底是图像金字塔还是12x12固定尺寸,以及P-Net到底生不生产Landmark,都没说到。还是自己去看了原论文,对照着源代码,弄清了一些模糊的东西。 MTCNN P-net:通过一个浅层的CNN快速筛选出许多...
  • MTCNN训练全程

    2020-11-09 14:30:25
    本文设计的网络包含三个不同的网络结构,Second Network的训练数据需要用到First Network训练好的模型生成,Third Network的训练数据由Second Network训练好的模型生成。 所以在模型训练阶段,需要面向多个任务,在...
  • MTCNN训练

    千次阅读 2018-09-02 00:27:47
    MTCNN是当前效果最好的开源人脸检测算法之一,作者只提供了训练好的模型以及matlab部署代码,其训练和优化在github上有很多,记录成文。 如果只是部署而没有时间训练的话,建议使用下面这个 ...
  • 人脸检测:MTCNN训练数据处理

    千次阅读 2018-05-19 11:40:32
    转载:https://zhuanlan.zhihu.com/p/31761796MTCNN是多任务级联CNN的人脸检测深度学习模型,该模型中综合考虑了人脸边框回归和面部关键点检测。该级联的CNN网络结构包括PNet,RNet,ONet。本文主要介绍人脸检测中...
  • MTCNN+facenet实现人脸识别

    千次阅读 2019-07-12 20:06:00
    人脸识别其实又分为两部分,人脸检测和人脸识别,在本次博客中,人脸检测使用MTCNN模型,然后利用facenet进行人脸识别。 Github链接:https://github.com/ALittleLeo/FaceRcognization/tree/master 觉得有用的话请...
  • MTCNN开源训练(PNet训练

    千次阅读 2019-04-26 11:09:11
    from train_models.MTCNN_config import config sys.path.append("../prepare_data") print(sys.path) from prepare_data.read_tfrecord_v2 import read_multi_tfrecords,read_single_tfrecord import random ...
  • MTCNN配置及训练详细步骤

    千次阅读 2019-07-03 14:03:19
    配置环境为win7 64位,主要完成的任务是用MTCNN完成人脸检测,即使用目标检测框将图像中的人脸框出来,配置过程如下: 1、环境配置 安装anaconda 进入官网: https://www.anaconda.com/download/ 根据python版本...
  • MTCNN训练与测试小结

    千次阅读 2018-09-12 12:35:41
    本文重点介绍其中一篇关注度比较高的文章《Joint Face Detection and Alignment using ...训练数据:该算法训练数据来源于wider和celeba两个公开的数据库,wider提供人脸检测数据,在大图上标注了人脸框groundtr...
  • 为了验证我们训练MTCNN的O-Net在训练集上的表现,我们写了一个测试代码,来测试训练图片的landmark的mean error。 landmark标签格式如下所示: 48/landmark/0.jpg -1 -1 -1 -1 -1 0.224199 0.505338 0.334520 0....
  • 训练代码: import os from torch.utils.data import DataLoader import torch import torch.nn as nn import torch.optim as optim from Getdatas import getdatas class Trainer: def __init__(self,net,...
  • 这一篇主要记录模型的...简单描述MTCNN训练过程: 训练PNET:其输入图宽高为12X12,输出为人脸得分与人脸框位置; 训练RNET:将得到的PNET作用于人脸数据集,并将得到的人脸框与参考人脸框计算IOU,再根据其大小分为
  • 人脸检测:MTCNN Joint Face Detection and Alignment using Multi-task Cascaded Convolutional Networks(MTCNN) Facial Landmark Detection by D...
  • MTCNN训练GitHub: MTCNNpaper: 下载数据集 wider face数据集下载→wider face该数据集包含32,203 images and ...landmark数据集下载→landmark训练数据该数据有训练和测试两个数据集,其中训练数据有10000张...
  • 【tensorflow】MTCNN网络Pnet训练

    千次阅读 2019-07-10 18:50:00
    pnet数据生成后,进行pnet训练训练过程如下: # coding:utf-8 import tensorflow as tf import numpy as np import random import os from easydict import EasyDict as edict from datetime import ...
  • config.gpu_options.allow_growth = True session = InteractiveSession(config=config) 解决后结果图: 程序成功运行,每次迭代之后的准确率、分类损失、bbox损失、landmark损失等都打印出来了。 补充: 你如果在跑...
  • MTCNN训练数据整理

    2019-04-16 17:58:57
    MTCNN训练数据整理
  • import numpy as np import os from tool.utils import IouDo from PIL import Image, ImageFilter from PIL import ImageDraw img_path = r"D:\CelebA\img_celeba...label_landmark_path = r"D:\BaiduNetdiskDownl...
  • 人脸检测-MTCNN算法笔记和代码解读

    千次阅读 多人点赞 2018-11-28 18:16:11
    多任务级联卷积神经网络(MTCNN, Multi-task Cascaded Convolutional Networks)用以同时处理人脸检测和人脸关键点定位(5个关键点)问题,该论文入选2016的ECCV。 最近刚刚开始写博客,欢迎大家评论,如果大家对...
  • 最近在整MTCNN相关的东西,于是从github大佬处clone了一份,无奈本人手残,跑起来处处是报错。帖子上的询问均是石沉大海,只有遍搞边记录了,希望能坚持到跑通的那一天吧= =。 博文CSDN:...
  • 上一篇博客是为PNet网络生成TFRecord文件,现在开始对PNet进行训练。 1 PNet的训练 进入train_models文件夹...from train_models.mtcnn_model import P_Net from train_models.train import train def train_PNet...
  • MTCNN理解到亲手训练

    万次阅读 多人点赞 2018-11-25 20:41:10
    模型理解 MTCNN是目前比较流行的人脸检测方法,通过...接下来我会从我在训练中的理解来解释MTCNN模型都干了什么。 三个模型要按顺序训练,首先是PNet,然后RNet,最后ONet。 PNet: PNet是全卷积网络,主要为了...
  • WIDER Facefor face detection andCelebafor landmark detection WIDER Face 总共62个场景的文件夹,每个文件夹中多张图片 文件中保存的是每个图片中所有人脸框的位置,表示意义如下: Cel...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 620
精华内容 248
关键字:

landmark训练mtcnn