MTCNN(Tensorflow)学习记录(生成PNet的人脸数据样本)

创作此文章的目的是详细的记录MTCNN的学习过程。作为一个新手,我会把代码的所有内容尽量解释一遍。

1生成PNet的人脸数据样本

进入prepare_data文件夹打开脚本:gen_12net_data.py

#coding:utf-8
import os
import cv2
import numpy as np
import numpy.random as npr

from utils import IoU 

anno_file = "wider_face_train.txt"       #下载的wider face数据集对应的每张图片的人脸方框数据
im_dir = "../../DATA/WIDER_train/images" #将图片解压到这个文件夹
pos_save_dir = "../../DATA/12/positive"  #生成的正样本存放路径
part_save_dir = "../../DATA/12/part"     #生成的无关样本存放路径
neg_save_dir = '../../DATA/12/negative'  #生成的负样本存放路径
save_dir = "../../DATA/12"
if not os.path.exists(save_dir):         #路径的创建
    os.mkdir(save_dir)
if not os.path.exists(pos_save_dir):
    os.mkdir(pos_save_dir)
if not os.path.exists(part_save_dir):
    os.mkdir(part_save_dir)
if not os.path.exists(neg_save_dir):
    os.mkdir(neg_save_dir)

f1 = open(os.path.join(save_dir, 'pos_12.txt'), 'w')  #对应的样本的文档建立
f2 = open(os.path.join(save_dir, 'neg_12.txt'), 'w')
f3 = open(os.path.join(save_dir, 'part_12.txt'), 'w')
with open(anno_file, 'r') as f:
    annotations = f.readlines()                       #按行读取存放进列表annotations里面
num = len(annotations)                                #里面的每一个元素对应着一张照片的人脸数据,所以这个列表的大小就是数据集的照片数量。
print("%d pics in total" % num)                       #打印出照片的数量
p_idx = 0                                             # positive
n_idx = 0                                             # negative
d_idx = 0                                             # don't care
idx = 0
box_idx = 0
for annotation in annotations:                        #for循环读取数据
    annotation = annotation.strip().split(' ')        #去掉每一行数据的首尾空格换行字符,同时以空格为界限,分成一个个的字符
    #image path
    im_path = annotation[0]                           #第0号元素代表的是一个路径
    #print(im_path)
    #boxed change to float type
    bbox = list(map(float, annotation[1:]))           #第一号元素开始到结束,每四个元素代表着一个人脸框
    #gt
    boxes = np.array(bbox, dtype=np.float32).reshape(-1, 4)  #将人脸框的坐标进行reshape操作,变成n行4列的array
    #load image
    img = cv2.imread(os.path.join(im_dir, im_path + '.jpg')) #将路径拼接后读取图片
    idx += 1
    #if idx % 100 == 0:
        #print(idx, "images done")

    height, width, channel = img.shape                   #读取图片的宽、高、通道数并记录下来

    neg_num = 0                                          #负样本数初始化为0
    #1---->50
    # keep crop random parts, until have 50 negative examples
    # get 50 negative sample from every image
    while neg_num < 50:                                   #负样本数小于50的时候
        #neg_num's size [40,min(width, height) / 2],min_size:40
        # size is a random number between 12 and min(width,height)
        size = npr.randint(12, min(width, height) / 2)    #size是一个随机数
        #top_left coordinate
        nx = npr.randint(0, width - size)                 #左上方的x坐标是一个随机数
        ny = npr.randint(0, height - size)                #左上方的y坐标是一个随机数
        #random crop
        crop_box = np.array([nx, ny, nx + size, ny + size]) #随机裁剪的样本
        #calculate iou
        Iou = IoU(crop_box, boxes)                          #引入Iou()函数,含有两个参数,随机裁剪的样本crop_box和实际的人脸框boxes,计
                                                            #算出Iou()值
		
        #crop a part from inital image
        cropped_im = img[ny : ny + size, nx : nx + size, :] #将这个部分样本裁剪下来
        #resize the cropped image to size 12*12
        resized_im = cv2.resize(cropped_im, (12, 12),       #resize这个样本成12*12       
        interpolation=cv2.INTER_LINEAR)


        if np.max(Iou) < 0.3:                               #当Iou的值小于0.3的时候为负样本                         
            # Iou with all gts must below 0.3
            save_file = os.path.join(neg_save_dir, "%s.jpg"%n_idx)
            f2.write("../../DATA/12/negative/%s.jpg"%n_idx + ' 0\n') #样本的路径保存下来
            cv2.imwrite(save_file, resized_im)                       #图片保存下来
            n_idx += 1
            neg_num += 1


    #for every bounding boxes
    for box in boxes:
        # box (x_left, y_top, x_right, y_bottom)
        x1, y1, x2, y2 = box
        #gt's width
        w = x2 - x1 + 1
        #gt's height
        h = y2 - y1 + 1
        #获取每一个样本的宽和高

        # in case the ground truth boxes of small faces are not accurate
        #忽略一些小的人脸和那些左顶点超出了图片的人脸框
        #防止那些小人脸的坐标不准确
        if max(w, h) < 20 or x1 < 0 or y1 < 0:
            continue

        #下面仍然是返回5个负样本,但是返回的样本一定是和真实的人脸框有一定的交集,即(0
        for i in range(5):
            #size of the image to be cropped
            size = npr.randint(12, min(width, height) / 2)
            # parameter high of randint make sure there will be intersection between bbox and cropped_box
            delta_x = npr.randint(max(-size, -x1), w) #求取(-size和-x1的最大值可以保证x1+delta_x一定大于等于0,
            delta_y = npr.randint(max(-size, -y1), h) #同上
            # max here not really necessary
            nx1 = int(max(0, x1 + delta_x))  #得到x1的偏移坐标nx1
            ny1 = int(max(0, y1 + delta_y))  #得到y1的偏移坐标ny1
            
            #如果裁剪框的右下坐标超出了图片的范围就跳过此次循环,进行下一次裁剪,注意这里的width是原始图片的宽度,不是真实人脸框的宽w
            if nx1 + size > width or ny1 + size > height:
                continue
            crop_box = np.array([nx1, ny1, nx1 + size, ny1 + size]) #获取裁剪后的矩形框
            Iou = IoU(crop_box, boxes)                              #计算IoU值
    
            cropped_im = img[ny1: ny1 + size, nx1: nx1 + size, :]
            '''后面的多了一个分号没有看懂,标记一下,我猜应该是图片的通道数,上面的图片裁剪这步操作也有第三个分号,之前没有注意'''
            #图片resize到12*12
            resized_im = cv2.resize(cropped_im, (12, 12), interpolation=cv2.INTER_LINEAR)
            #将符合条件的样本框保存,完成这部操作之后每张图片都生成了55个负样本
            if np.max(Iou) < 0.3:
                # Iou with all gts must below 0.3
                save_file = os.path.join(neg_save_dir, "%s.jpg" % n_idx)
                f2.write("../../DATA/12/negative/%s.jpg" % n_idx + ' 0\n')
                cv2.imwrite(save_file, resized_im)
                n_idx += 1
                
        #生成正样本和无关样本
        for i in range(20):
            # pos and part face size [minsize*0.8,maxsize*1.25]
            #设置正样本和部分样本的size
            size = npr.randint(int(min(w, h) * 0.8), np.ceil(1.25 * max(w, h)))

            # delta here is the offset of box center
            if w<5:
                print (w)
                continue
           
            #x1和y1的偏移量
            delta_x = npr.randint(-w * 0.2, w * 0.2)
            delta_y = npr.randint(-h * 0.2, h * 0.2)

            # deduct size/2 to make sure that the right bottom corner will be out of
            #nx1是人脸框的中点的x坐标加减0.2倍宽度再减去一半的size和0之间的最大值
            #ny1是人脸框的中点的y坐标加减0.2倍高度再减去一半的size和0之间的最大值
            nx1 = int(max(x1 + w / 2 + delta_x - size / 2, 0))
            ny1 = int(max(y1 + h / 2 + delta_y - size / 2, 0))
            nx2 = nx1 + size #获得右下角的nx2坐标
            ny2 = ny1 + size #获得右下角的ny2坐标
            
            #去掉超出图片的的坐标点
            if nx2 > width or ny2 > height:
                continue 
            crop_box = np.array([nx1, ny1, nx2, ny2])
            #yu gt de offset
            #这是一个bounding box regression操作
            offset_x1 = (x1 - nx1) / float(size)
            offset_y1 = (y1 - ny1) / float(size)
            offset_x2 = (x2 - nx2) / float(size)
            offset_y2 = (y2 - ny2) / float(size)
            #裁剪图片
            cropped_im = img[ny1 : ny2, nx1 : nx2, :]
            #resize操作
            resized_im = cv2.resize(cropped_im, (12, 12), interpolation=cv2.INTER_LINEAR)

            box_ = box.reshape(1, -1)  #reshape成行数等于一列数未知的数组
            iou = IoU(crop_box, box_)  #计算IoU值
            if iou  >= 0.65:           #保存为正样本
                save_file = os.path.join(pos_save_dir, "%s.jpg"%p_idx)
                f1.write("../../DATA/12/positive/%s.jpg"%p_idx + ' 1 %.2f %.2f %.2f %.2f\n'%(offset_x1, offset_y1, offset_x2, offset_y2))
                cv2.imwrite(save_file, resized_im)
                p_idx += 1
            elif iou >= 0.4:           #保存为部分样本
                save_file = os.path.join(part_save_dir, "%s.jpg"%d_idx)
                f3.write("../../DATA/12/part/%s.jpg"%d_idx + ' -1 %.2f %.2f %.2f %.2f\n'%(offset_x1, offset_y1, offset_x2, offset_y2))
                cv2.imwrite(save_file, resized_im)
                d_idx += 1
        box_idx += 1
        if idx % 100 == 0:
            print("%s images done, pos: %s part: %s neg: %s" % (idx, p_idx, d_idx, n_idx))
f1.close()
f2.close()
f3.close()

这样,三个样本的生成就结束了。一共生成了三个图片文件夹和对应的注释文档。值得注意的是负样本注释文档里面没有坐标的记录,而正样本和无关样本里面是有的,即负样本的注释文件里面每一行有2个参数,每个参数用空格分开,分别是图片的路径和表示这是个负样本的label值0;同样的,正样本和无关样本的注释文件里面有6个参数,多了的4个参数是bounding box的四个坐标,正样本的label是1,无关样本的label是-1
用到的IoU函数的代码解析在这个链接。
下一篇博客将介绍landmark数据的整理。

你可能感兴趣的:(MTCNN(Tensorflow)学习记录(生成PNet的人脸数据样本))