现在我们记录下如何在训练好P_net模型后,来把网络输出decode出人脸框的坐标,坐标偏移,以及landmarks
首先看下模型的输出,我们恢复P_net训练好的模型输入一张(height,width,3)的图片
输出为(height_,width_,2) (height_,width_,4)暂时先不考虑landmark的输出
由于整个P_net为fcn,所以height_和width_的大小完全取决于height,width的大小。也就是说是不固定的。
1.现在我们有了网络的输出,只需要根据这个输出来decode即可。首先在这里才考虑图像金字塔
#net_size : 这里为12
#min_face_size : 为20
current_scale = net_size / min_face_size
#然后我们根据这个current_scale将图片resize,得到一个scale下的tup
while min(im_resize.shape[0],im_resize.shape[1]) > net_size:
scale = current_scale * 0.79
这个就是图像金字塔
2.现在我们以某个scale下的图片为例进行解析
cls_map : P_net的输出face or no_face (height_,width_,2)
reg : P_net的第二个输出 (height_,width_,4)
#这里为什么要取第二个维度,和训练的时候有关,因为part在第二个,其余在第一个
cls_map = cls_map[1]
#0.5为score的阈值
t_indx = np.where(cls_map>0.5)
#找到满足上式的对于位置的offset
off_x1,off_y1,off_x2,off_y2 = [reg[t_indx[0],reg[t_indx[1],i] for i in range(4)]
reg = np.array([off_x1,off_y1,off_x2,off_y2])
#找到对应位置的score
score = cls_map[t_indx[0],t_indx[1]
#########现在我们要找对应到原图的bbox坐标
#这里的2是对应到构建网络是max_pooling步长为2所有要乘以2,t_indx[1]对应的哪一列,所有为x1 + 。
x1 = np.round(2 * t_indx[1]/scale)
y1 = np.round(2 * t_indx[0]/scale)
x2 = np.round((2 * t_indx[1] + 12) / scale)
y2 = np.round((2 * t_indx[0] + 12) / scale)
#这里我们可以看到,在MTCNN中也是使用一个类似于heat_map的特征图来decode出坐标信息,与cornernet不
#同的是,MTCNN只有一个heat_map图,每个点可以decode出四个坐标,而cornernet有左上和右下俩个#heatmap图,每个图只能decode出对应的点的坐标。其实这就是一个trade off的过程,MTCNN网络结构简
#单,但是导致只有要给heatmap,故每个点会decode出一个box,所有box会很多,也就是所谓的很多候选框,
#需要使用nmp来计算去,并且需要多层网络级联R,O。而cornernet网络输出比较复杂,但是解决了会有很多候
#选框的问题。
最后把decode出的信息放在一起方便处理
boundingbox = np.vstack(x1,y1,x2,y2,score,reg)
boundingbox = boundingbox.T
3 结合offset信息decode出最后映射到原图的坐标信息
我们以boundingbox中的一个box为例
w_b = box[:,2] - box[:,0] + 1
h_b = box[:,3] - box[:,1] + 1
r_x1 = box[:,0] + box[:,5] * w_b
r_y1 = box[:,1] + box[:,6] * h_b
r_x2 = box[:,2] + box[:,7] * w_b
r_y2 = box[:,3] + box[:,8] * h_b
这里我们解释下为什么要这么decode,offset在制作数据集时候的公式为 offset_x1 = (x1 - nx1)/size
这里size为裁剪后图片的大小,和这里的scale后的虽然数值不同,但是意义相同。带入我们decode公式发现
我们在boundingbox中的x1,这些原来是对应的nx1。这个很好理解,这里贴上nx1的推导公式就很明显了。
至此我们对于P_net的第一个输出和第二个输出的decode全部完成。这里再贴一下使用到的nms代码及原理
nms:非极大值抑制,字面理解,非极大值的极大值指的的候选框的score得分,那么怎么抑制呢?使用的是iou 大于某个阈值便抑制!!!
举例 A B C D E F四个候选框,根据score得分从大到小排序:F B C A E D
1 找得分最大的F
2 计算其余的与F 的IOU,假设 B 和E的IOU大于阈值则(认为他们与F是同一个,故去掉B,E,且标记F为一个true候选框)
3 由于第二部还剩下C A D三个候选框,继续重复上述过程
4 直到所有候选框都进行了上述操作
def py_nms(dets, thresh, mode="Union"):
x1 = dets[:, 0]
y1 = dets[:, 1]
x2 = dets[:, 2]
y2 = dets[:, 3]
scores = dets[:, 4]
#每一个候选框面积
areas = (x2 - x1 + 1) * (y2 - y1 + 1)
#从大到小排
order = scores.argsort()[::-1]
keep = []
while order.size > 0:
i = order[0]
keep.append(i)
xx1 = np.maximum(x1[i], x1[order[1:]])
yy1 = np.maximum(y1[i], y1[order[1:]])
xx2 = np.minimum(x2[i], x2[order[1:]])
yy2 = np.minimum(y2[i], y2[order[1:]])
w = np.maximum(0.0, xx2 - xx1 + 1)
h = np.maximum(0.0, yy2 - yy1 + 1)
inter = w * h
if mode == "Union":
ovr = inter / (areas[i] + areas[order[1:]] - inter)
elif mode == "Minimum":
ovr = inter / np.minimum(areas[i], areas[order[1:]])
#keep
inds = np.where(ovr <= thresh)[0]
order = order[inds + 1]
return keep