MTCNN详析

本文主要结合官方的示例代码解释MTCNN的Inference细节

###建立并加载MTCNN模型

模型结构如下:
在这里插入图片描述

建立模型:

with tf.Graph().as_default():
  sess = tf.Session()
  with sess.as_default():
    pnet, rnet, onet = align.detect_face.create_mtcnn(sess, None)

其中,create_mtcnn加载了预训练的模型数据并返回pnet,rnet,onet的运行入口

def create_mtcnn(sess, model_path):
    if not model_path:
        model_path,_ = os.path.split(os.path.realpath(__file__))

    with tf.variable_scope('pnet'):
        data = tf.placeholder(tf.float32, (None,None,None,3), 'input')
        pnet = PNet({'data':data})
        pnet.load(os.path.join(model_path, 'det1.npy'), sess)
    with tf.variable_scope('rnet'):
        data = tf.placeholder(tf.float32, (None,24,24,3), 'input')
        rnet = RNet({'data':data})
        rnet.load(os.path.join(model_path, 'det2.npy'), sess)
    with tf.variable_scope('onet'):
        data = tf.placeholder(tf.float32, (None,48,48,3), 'input')
        onet = ONet({'data':data})
        onet.load(os.path.join(model_path, 'det3.npy'), sess)
        
    pnet_fun = lambda img : sess.run(('pnet/conv4-2/BiasAdd:0', 'pnet/prob1:0'), feed_dict={'pnet/input:0':img})
    rnet_fun = lambda img : sess.run(('rnet/conv5-2/conv5-2:0', 'rnet/prob1:0'), feed_dict={'rnet/input:0':img})
    onet_fun = lambda img : sess.run(('onet/conv6-2/conv6-2:0', 'onet/conv6-3/conv6-3:0', 'onet/prob1:0'), feed_dict={'onet/input:0':img})
    return pnet_fun, rnet_fun, onet_fun

MTCNN检测

模型检测:

bounding_boxes, _ = align.detect_face.detect_face(img, minsize, pnet, rnet, onet, threshold, factor)

检测过程分为如下图的四个部分:建立金字塔,pnet过程,rnet过程,onet过程
在这里插入图片描述

建立金字塔
factor_count=0
total_boxes=np.empty((0,9))
points=np.empty(0)
h=img.shape[0]
w=img.shape[1]
minl=np.amin([h, w])
m=12.0/minsize
minl=minl*m
# create scale pyramid
scales=[]
while minl>=12:
  scales += [m*np.power(factor, factor_count)]
  minl = minl*factor
  factor_count += 1

下面解释一下建立金字塔的过程:

我们认为图像中人脸的最小尺寸为minsize,而图像的尺寸以图像的最短边picsize来表示

假设图像中的人脸大小为minl,则有:minsize<minl<picsize

由于训练MTCNN时采用的图片大小为12*12所以希望minl与这个大小相仿

当minl=minsize时,是缩放尺度最大的时候,应该缩放的尺度是12/minsize

当minl=picsize时,是缩放尺度最小的时候,应该缩放的尺度是12/minl

在[12/picsize, 12/minsize]的这个区间内,可以增加更多的尺度来适配不同facesize的情况。尺度的细粒度可以根据factor来调节,factor越接近1,金字塔的层数越多,考虑的facesize的情况越细致。一般情况下,factor取0.709,大概意思是每次将图像面积缩小一半

P-NET

P-NET操作最为复杂,类似于一个RPN网络

  • 经过金字塔操作之后的图片输入pnet

    pnet是全卷积结构,不限制输入图像的大小,输出一个人脸概率构成的heatmap,和对应的bbox regression

  • 根据heatmap的概率值筛选出目标框,并还原到原图大小,得到bbox:

# imap即heatmap,t为预设的threshold
y, x = np.where(imap >= t)
if y.shape[0]==1:
  dx1 = np.flipud(dx1)
  dy1 = np.flipud(dy1)
  dx2 = np.flipud(dx2)
  dy2 = np.flipud(dy2)
score = imap[(y,x)]
reg = np.transpose(np.vstack([ dx1[(y,x)], dy1[(y,x)], dx2[(y,x)], dy2[(y,x)] ]))
if reg.size==0:
	reg = np.empty((0,3))
bb = np.transpose(np.vstack([y,x]))
q1 = np.fix((stride*bb+1)/scale)
q2 = np.fix((stride*bb+cellsize-1+1)/scale)
boundingbox = np.hstack([q1, q2, np.expand_dims(score,1), reg])
  • 得到的bbox经过nms作初步筛选后,和金字塔其他层所生成的bbox汇总。汇总后,再作一次nms

  • 对bbox这些作bbox regression进行位置微调

    # bbox regression
    regw = total_boxes[:,2]-total_boxes[:,0]
    regh = total_boxes[:,3]-total_boxes[:,1]
    qq1 = total_boxes[:,0]+total_boxes[:,5]*regw
    qq2 = total_boxes[:,1]+total_boxes[:,6]*regh
    qq3 = total_boxes[:,2]+total_boxes[:,7]*regw
    qq4 = total_boxes[:,3]+total_boxes[:,8]*regh
    total_boxes = np.transpose(np.vstack([qq1, qq2, qq3, qq4, total_boxes[:,4]]))
    
    total_boxes = rerec(total_boxes.copy())
    total_boxes[:,0:4] = np.fix(total_boxes[:,0:4]).astype(np.int32)
    dy, edy, dx, edx, y, ey, x, ex, tmpw, tmph = pad(total_boxes.copy(), w, h)
    
  • rerec:将regression后的坐标转化为矩形

  • pad:后面两个网络的输入需要将bbox对应的图像(称作src)从原图中抠出来(称作target),pad可以获取了bbox对应的src坐标(x,ex,y,ey)和target坐标 (dx,edx,dy,edy)

R-Net

R-Net的输入是P-net得到的roi,类似传统two-stage网络的head

    numbox = total_boxes.shape[0]
    if numbox>0:
        # second stage
        tempimg = np.zeros((24,24,3,numbox))
        for k in range(0,numbox):
            tmp = np.zeros((int(tmph[k]),int(tmpw[k]),3))
            tmp[dy[k]-1:edy[k],dx[k]-1:edx[k],:] = img[y[k]-1:ey[k],x[k]-1:ex[k],:]
            if tmp.shape[0]>0 and tmp.shape[1]>0 or tmp.shape[0]==0 and tmp.shape[1]==0:
                tempimg[:,:,:,k] = imresample(tmp, (24, 24))
            else:
                return np.empty()
        tempimg = (tempimg-127.5)*0.0078125
        tempimg1 = np.transpose(tempimg, (3,1,0,2))
        out = rnet(tempimg1)
        out0 = np.transpose(out[0])
        out1 = np.transpose(out[1])
        score = out1[1,:]
        ipass = np.where(score>threshold[1])
        total_boxes = np.hstack([total_boxes[ipass[0],0:4].copy(), np.expand_dims(score[ipass].copy(),1)])
        mv = out0[:,ipass[0]]
        if total_boxes.shape[0]>0:
            pick = nms(total_boxes, 0.7, 'Union')
            total_boxes = total_boxes[pick,:]
            total_boxes = bbreg(total_boxes.copy(), np.transpose(mv[:,pick]))
            total_boxes = rerec(total_boxes.copy())
  • 将P-net得到的bbox从原图中抠出来,并作了归一化处理,输入到R-net中

  • 根据score作初步筛选得到的结果,再通过nms进一步筛选

  • 作bbox regreesion,修正位置,并通过rerec转化为矩形

O-Net

O-Net与R-net操作基本一致,最终输出结果是bbox和特征点

由于MTCNN的输出要进一步交给后续的CNN网络进行识别,O-Net输出之后又有一些后处理,可以看到这里给bbox加了边框,裁剪出的图像经过了白化处理

det = np.squeeze(bounding_boxes[0,0:4])
bb = np.zeros(4, dtype=np.int32)
bb[0] = np.maximum(det[0]-margin/2, 0)
bb[1] = np.maximum(det[1]-margin/2, 0)
bb[2] = np.minimum(det[2]+margin/2, img_size[1])
bb[3] = np.minimum(det[3]+margin/2, img_size[0])
cropped = img[bb[1]:bb[3],bb[0]:bb[2],:]
aligned = misc.imresize(cropped, (image_size, image_size), interp='bilinear')
prewhitened = facenet_util.prewhiten(aligned)

官方的代码中并没有进行人脸对齐操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值