MTCNN人脸识别
Posted the-home-of-123
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MTCNN人脸识别相关的知识,希望对你有一定的参考价值。
最近看了一些人脸识别的综述及几篇经典论文。这里简单记录下MTCNN论文及Tensorflow的复现过程。感觉人脸检测属于目标检测下的一个方向,不过由通用目标检测改为人脸检测,即多分类改为2分类,且为小目标检测。而且人脸检测还加上了关键点检测,可以依靠关键点增加召回率。主要思想还是依靠的通用目标检测,除了yolo和R-CNN系列,还多出的一个算法系列为级联网络结构,MTCNN为其中一个代表。以前讲过通用目标检测的两种方法,这次主要讲一下级联网络的MTCNN这一种方案。
附带一句我理解的阅读论文思路。首先看综述,梳理脉络。之后找到经典方法即有重大突破的几个节点,之后搜该方法的博客,明白大致意思。之后看论文,因为论文中细节更加丰富,之后结合论文,看源码,进行复现。源码是用的其他人的,非本人写的
主要参考博客https://blog.csdn.net/qq_41044525/article/details/80820255
https://zhuanlan.zhihu.com/p/31761796
MTCNN分为三个网络,P-net,R-Net,O-Net. 先来下预测部分总体流程图(我只复现了预测部分源码,不过会讲一下loss函数)。
1. Stage 1
与训练时不同,单预测部分来说,输入图片非固定大小,而是将原图进行缩放,生成图像金字塔,即系列的图片,最小规格为12*12的。R-net网络结构如图,整体可看成将原图整图进行卷积(利用12*12,strides=2),之后生成预测的分类及位置偏移(无脸部关键点),网络输出中每一个1*1的方格映射到原图的感受野为12*12。由此生成系列的预测值,由此可认为输入为12*12大小。网络部分代码就不列出了,就一个网络结构,很好理解。预处理代码如下:
###作用是将原图整图输入,生成图像金字塔,输入网络中,以增加准确性。 factor_count=0 total_boxes=np.empty((0,9)) points=[] h=img.shape[0] w=img.shape[1] minl=np.amin([h, w]) m=12.0/minsize minl=minl*m # creat scale pyramid scales=[] while minl>=12: scales += [m*np.power(factor, factor_count)] minl = minl*factor factor_count += 1 # first stage ###将产生的最小为12*12系列的图片送入pnet网络中,获得输出的回归框。scale为缩放比例,可以用来推测在原图中的坐标 for j in range(len(scales)): scale=scales[j] hs=int(np.ceil(h*scale)) ws=int(np.ceil(w*scale)) im_data = imresample(img, (hs, ws)) ###此时输入非12*12,而是每一个单元的感受野为12,这样每一块就生成了系列的预测框 im_data = (im_data-127.5)*0.0078125 img_x = np.expand_dims(im_data, 0) img_y = np.transpose(img_x, (0,2,1,3)) out = pnet(img_y) ###输入pnet网络中,获得输出 out0 = np.transpose(out[0], (0,2,1,3)) ###二分类,即为人脸的概率 out1 = np.transpose(out[1], (0,2,1,3)) ###预测框偏移回归 out0 size(1,H/12,W/12,2)
out1 size(1,H/12,W/12,4)
##输出为第一层的预测框坐标合集,如何产生系列的预测框。
boxes, _ = generateBoundingBox(out1[0,:,:,1].copy(), out0[0,:,:,:].copy(), scale, threshold[0])
下面是genereteBoundingBox代码.函数最用为输出第一层每12*12大小的坐标,偏移,及预测得分
def generateBoundingBox(imap, reg, scale, t): ###reg为偏移 imag为是否为正类 scal缩小的比例 t 为阈值 # use heatmap to generate bounding boxes stride=2 cellsize=12 #### 转置计算很常见,目的只要为了方便比大小,做运算。通用目标检测中也常用,对位置坐标进行转置 imap = np.transpose(imap) dx1 = np.transpose(reg[:,:,0]) dy1 = np.transpose(reg[:,:,1]) dx2 = np.transpose(reg[:,:,2]) dy2 = np.transpose(reg[:,:,3]) y, x = np.where(imap >= t) ###筛选出大于阈值的坐标。因为每个小单元格有一个预测概率值,四个坐标偏移值 H/12,W/12,y,x可看成index 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)] ])) ###预测为满足条件的人脸image预测框坐标偏移 if reg.size==0: reg = np.empty((0,3)) bb = np.transpose(np.vstack([y,x])) ###为何*2+1?应该为*2+4? q1,q2值应为在原图中每一个预测框的左上角,右下角坐标 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]) return boundingbox, reg ##返回每一个12*12块大小的坐标及对应偏移及该块得分
之后对预测值进行修正,预修剪,产生proposal的坐标,将预测出的回归框部分提取出来,以便输入到第二个网络中。代码如下
1 # inter-scale nms 对预测出的预测框进行nms,筛选预测框 2 pick = nms(boxes.copy(), 0.5, ‘Union‘) 3 if boxes.size>0 and pick.size>0: 4 boxes = boxes[pick,:] 5 total_boxes = np.append(total_boxes, boxes, axis=0) 6 7 numbox = total_boxes.shape[0] ####筛选出的预测框个数 8 if numbox>0: 9 pick = nms(total_boxes.copy(), 0.7, ‘Union‘) ###提高阈值,进一步进行nms 10 total_boxes = total_boxes[pick,:] 11 regw = total_boxes[:,2]-total_boxes[:,0] 12 regh = total_boxes[:,3]-total_boxes[:,1] 13 qq1 = total_boxes[:,0]+total_boxes[:,5]*regw 14 qq2 = total_boxes[:,1]+total_boxes[:,6]*regh 15 qq3 = total_boxes[:,2]+total_boxes[:,7]*regw 16 qq4 = total_boxes[:,3]+total_boxes[:,8]*regh 17 total_boxes = np.transpose(np.vstack([qq1, qq2, qq3, qq4, total_boxes[:,4]]))###依次为修正后的左上角,右下角坐标及该部分得分 18 total_boxes = rerec(total_boxes.copy()) ####使预测框变为正方形 19 total_boxes[:,0:4] = np.fix(total_boxes[:,0:4]).astype(np.int32) ##取整 20 dy, edy, dx, edx, y, ey, x, ex, tmpw, tmph = pad(total_boxes.copy(), w, h) #####对坐标进行修剪,使其不超出图片大小
2. Stage 2
网络结构如图所示。此时的输入必须为24*24大小,输入的图像为stage1产生的porposals,抠出来,输入到stage2中。此处很像Faster-RCNN的第一步,初步筛选出纯图像,将不相关部分过滤掉。这也是级联的意义。注,预测网络输出同样只有得分与预测框坐标修正,无关键点信息。以下为代码,与第一步很相似。
1 numbox = total_boxes.shape[0] 2 if numbox>0: ###由第一步得出的预测框,在原图进行裁剪,resize,输入到第R-Net中 3 # second stage 4 tempimg = np.zeros((24,24,3,numbox)) 5 for k in range(0,numbox): 6 tmp = np.zeros((int(tmph[k]),int(tmpw[k]),3)) 7 tmp[dy[k]-1:edy[k],dx[k]-1:edx[k],:] = img[y[k]-1:ey[k],x[k]-1:ex[k],:] 8 if tmp.shape[0]>0 and tmp.shape[1]>0 or tmp.shape[0]==0 and tmp.shape[1]==0: 9 tempimg[:,:,:,k] = imresample(tmp, (24, 24)) ####将porposalsresize成24*24大小。 10 else: 11 return np.empty() 12 tempimg = (tempimg-127.5)*0.0078125 13 tempimg1 = np.transpose(tempimg, (3,1,0,2)) 14 out = rnet(tempimg1) ###输入到R-Net中得到输出 15 out0 = np.transpose(out[0]) ####预测框坐标偏置 16 out1 = np.transpose(out[1]) ######预测得分 17 score = out1[1,:] 18 ##第一步中筛选出的预测框坐标。此时的坐标为原图中的坐标偏移,并非resize之后的坐标偏置。即直接将偏移加到原图中坐标即可 19 ipass = np.where(score>threshold[1]) 20 total_boxes = np.hstack([total_boxes[ipass[0],0:4].copy(), np.expand_dims(score[ipass].copy(),1)]) 21 mv = out0[:,ipass[0]] ###第二步得出的偏移值 22 if total_boxes.shape[0]>0: 23 pick = nms(total_boxes, 0.7, ‘Union‘) 24 total_boxes = total_boxes[pick,:] ###先nms,第一步中调高阈值 25 total_boxes = bbreg(total_boxes.copy(), np.transpose(mv[:,pick])) ####加偏移后的坐标 26 total_boxes = rerec(total_boxes.copy())###变为正方形
3. Stage 3
第三步网络没什么好说的,和第二步一样的流程。不过此时多了一个关键点预测。最后得到了结果。
1 numbox = total_boxes.shape[0] 2 if numbox>0: 3 # third stage ###仿照第二步,将第二步得出的预测图像输入到第三个网络中 4 total_boxes = np.fix(total_boxes).astype(np.int32) 5 dy, edy, dx, edx, y, ey, x, ex, tmpw, tmph = pad(total_boxes.copy(), w, h) 6 tempimg = np.zeros((48,48,3,numbox)) 7 for k in range(0,numbox): 8 tmp = np.zeros((int(tmph[k]),int(tmpw[k]),3)) 9 tmp[dy[k]-1:edy[k],dx[k]-1:edx[k],:] = img[y[k]-1:ey[k],x[k]-1:ex[k],:] 10 if tmp.shape[0]>0 and tmp.shape[1]>0 or tmp.shape[0]==0 and tmp.shape[1]==0: 11 tempimg[:,:,:,k] = imresample(tmp, (48, 48)) 12 else: 13 return np.empty() 14 tempimg = (tempimg-127.5)*0.0078125 15 tempimg1 = np.transpose(tempimg, (3,1,0,2)) 16 out = onet(tempimg1) 17 out0 = np.transpose(out[0]) 18 out1 = np.transpose(out[1]) 19 out2 = np.transpose(out[2]) 20 score = out2[1,:] 21 points = out1 22 ipass = np.where(score>threshold[2]) 23 points = points[:,ipass[0]] 24 total_boxes = np.hstack([total_boxes[ipass[0],0:4].copy(), np.expand_dims(score[ipass].copy(),1)]) 25 mv = out0[:,ipass[0]] 26 27 w = total_boxes[:,2]-total_boxes[:,0]+1 28 h = total_boxes[:,3]-total_boxes[:,1]+1 29 points[0:5,:] = np.tile(w,(5, 1))*points[0:5,:] + np.tile(total_boxes[:,0],(5, 1))-1 30 points[5:10,:] = np.tile(h,(5, 1))*points[5:10,:] + np.tile(total_boxes[:,1],(5, 1))-1 31 if total_boxes.shape[0]>0: 32 total_boxes = bbreg(total_boxes.copy(), np.transpose(mv)) 33 pick = nms(total_boxes.copy(), 0.7, ‘Min‘) 34 total_boxes = total_boxes[pick,:] 35 points = points[:,pick] 36 37 return total_boxes, points ####得出最终的预测值
此处只有预测部分的代码,在训练时,有一个与其他目标检测不同的地方在于,有5个人关键点检测,也计入了损失函数中,以此进行训练,可增加识别的准确性。
来勉强解读一下。。这部分看的很粗略。
1. 脸分类损失函数。交叉熵损失函数
2. 预测框损失函数。 平方损失
3. 关键点损失函数。同样为平方损失
3. 综合训练,整体损失函数。每部分网络的权重不同。
以上是关于MTCNN人脸识别的主要内容,如果未能解决你的问题,请参考以下文章