对于MTCNN中使用的原理工具,这一节分为代码的三部分(NMS,IOU,框的改变),对于图像金字塔、三个网络(R、P、ONET)和数据集的处理部分放在模型搭建中来讲,要不突然讲起来感觉太虚空,没办法很好的理解,当然也可以先去读下论文,了解下,那个也是不错的。
做MTCNN前。先看看我们会使用到什么工具~
IOU(交并比)本来想先写NMS的,写着写着突然发现还是需要先讲解下IOU会好一点,这样理解起来也比较顺,那就先说IOU这个概念。
交并比这个概念就是我们经过网络训练生成了一个框,如下图中的红色框,而我们在数据集中早就标记好了一个真实框,这个就是我之前说的处理数据集时必不可少的一步:标记。因为只有这样我们才能更好的训练,然后验证。
好,我们得到了预测框,那么这个怎么衡量框的准不准呢?
也就是如何给网络得到的这一个框一个分数呢?我们可以使用交并比来估计,就是说使用两个框的交集的面积除以并集的面积(这个是最常用的),当然交并比有三种计算方法了(如下图)。
下面就是如何计算交并比的公式,
对于这一个分数计算,其实你可以试着算算,如何两个框是一样的,那比值就是1,也就是说分数为1,其实也可以理解成置信度(也就是概率),如果两个框没有在一起,也就是0,那么这个框就不是训练中框到人脸的那个框。
如果你理解了这个概念其实就差不多了,这个基本上就是IOU的概念,那么对于我在图中说的O_Net使用交集面积除以最小面积的计算方法,这个到后面使用到的时候再指明。
代码如下(其实这一块,没有办法很详细的讲解,有问题可以在下面留言,看到了我会回复):
import numpy as np # 定义IOU(交并比)计算公式, 传入真实框和其他移动后的框 def iou(box, boxes, isMin=False): # 计算原始真实框的面积 box_area = (box[2] - box[0]) * (box[3] - box[1]) # 计算移动后的框的面积,这里计算的是矩阵 boxes_area = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1]) # 找到两个框的内部点计算交集 x1 = np.maximum(box[0], boxes[:, 0]) y1 = np.maximum(box[1], boxes[:, 1]) x2 = np.minimum(box[2], boxes[:, 2]) y2 = np.minimum(box[3], boxes[:, 3]) # 然后找到交集区域的长和宽,有的框没有交集那么相差可能为负,所以需要使用0来规整数据 w = np.maximum(0, x2 - x1) h = np.maximum(0, y2 - y1) # 计算交集的面积 inter_area = w * h # 两种计算方法:1是交并比等于交集除以并集,2是交集除以最小的面积 if isMin: ovr_area = np.true_divide(inter_area, np.minimum(boxes_area, box_area)) else: ovr_area = np.true_divide(inter_area, (boxes_area box_area - inter_area)) # 返回交并比,也就是IOU return ovr_area
非极大值抑制(NMS)的实现
非极大值抑制(NMS)顾名思义就是抑制不是极大值的元素,搜索局部的极大值。
检测中,滑动窗口经提取特征,经分类器分类识别后,每个窗口都会得到一个分数。
但是滑动窗口会导致很多窗口与其他窗口存在包含或者大部分交叉的情况。
这时就需要用到NMS来选取那些邻域里分数最高(是人脸的概率最大),并且抑制那些分数低的窗口。
这个就是我们通过网络进行人脸坐标点回归(如何实现人脸坐标点的回归,这个在数据集时会说明)时,在进行人脸检测时出现的框很多,但是我们不可能每个都保留,那么这个非极大值抑制就是来帮我们确认留下哪个框。
看上面的图(当然不知道能不能看清),以女主的脸上的框而言,当我们经过网络训练后,可能出现一个带有分数(也就是置信度,你可以理解为人脸概率)的框,我们会设定一个阈值来得到这些框,比如大于0.7,那么经过网络大于0.7的框的都会被留在人脸附近(这个就是IOU的分数值),这就回到了如何保留最大的分数框的问题,因为我们最后的目的就是能保留唯一的一个框,不太可能给一个人脸上框好几个框,那个不符合我们的预期。
这里我们就使用到可NMS,意思就如上面讲的那样,先选择这一堆框中最大的,然后去除和这个框交并比大于一定比例的,然后保留下来小于一定比例的,意思就是,比如女主脸上的框,因为都是框到人脸了,那么这每个框的IOU其实都是蛮大的,所以我们只取里面最大的,就得到了下面女主脸上的框
但是因为有两个人脸,又不能取图中最大分数的框,那样就只剩一个框了,所以我们设定如果其他框和这个最大值分数的框的IOU小于0.3(这个值是自己定义的),那么先留下,然后再取出剩下框最大值,再求IOU,然后留下男主的框
这个流程就是:
网络回归生成人脸框 --取出分数最大的框保存在一个列表中--拿剩下其他的框和这个最大的框求IOU,留下IOU小于设定阈值的框--然后再对保留的框取出最大分数的框保存在刚才的列表中--再拿剩下其他的框和这个最大的框求IOU,留下IOU小于设定阈值的框(依次循环,直至结束)
NMS代码:
# 定义NMS,筛选符合标准的线框 def nms(boxes, thresh=0.3, isMin=False): # 如果照片里面没有框数据了,就返回空列表 if boxes.shape[0] == 0: return np.array([]) # 以计算出的iou从大到小排列 _boxes = boxes[(-boxes[:, 4]).argsort()] r_boxes = [] # 如果框的有1个以上就进行对比 while _boxes.shape[0] > 1: # 取出最大的框 a_box = _boxes[0] # 剩下的框分别和之前的进行比对 b_boxes = _boxes[1:] # 先将最大iou的框添加到保留框的列表中 r_boxes.append(a_box) # 保留iou 小于0.3的,说明这个框和目前比对的不是同一个框,去除交集较多的框 index = np.where(iou(a_box, b_boxes, isMin) < thresh) _boxes = b_boxes[index] # _boxes = b_boxes[iou(a_box, b_boxes, isMin) < thresh] # 如果保留的框数量大于0,则添加iou最大的那个框 if _boxes.shape[0] > 0: r_boxes.append(_boxes[0]) # 将这些框堆叠在一起 return np.stack(r_boxes)
转换框为正方形convert_to_square# 定义将图片框变为正方形的工具 def convert_to_square(bbox): # 先将数据copy square_bbox = bbox.copy() # 如果数据框内没有框,则返回空列表 if bbox.shape[0] == 0: return np.array([]) # 计算出框的长宽 h = bbox[:, 3] - bbox[:, 1] w = bbox[:, 2] - bbox[:, 0] # 找出最大的那个边 max_side = np.maximum(h, w) # 计算正方形的左上角的点 square_bbox[:, 0] = bbox[:, 0] w * 0.4 - max_side * 0.4 square_bbox[:, 1] = bbox[:, 1] h * 0.4 - max_side * 0.4 square_bbox[:, 2] = square_bbox[:, 0] max_side square_bbox[:, 3] = square_bbox[:, 1] max_side # 返回到正方形的列表 return square_bbox
这个的意思就是网络输入size限定的,工具的作用就是在每个网络输入的时候就是图片纠正,将图片改变成一个正方形的size,便于网络训练,作用其实很简单,因为在网络训练时一般输入的是12*12/24*24/48*48这种类型的,但是进行我们训练难免会变形为矩形什么的,所以我们就要进行矩形纠正。
好了今天的工具暂且讲到这里,有疑问可以在下面留言,这样大家可以一起交流解答~
有什么写的有问题的地方也欢迎大家指正~
喜欢就关注我,持续日更中~欢迎转发留言评论~
(发现一天写这么多的确很累吶,等我把恩达老师的笔记写完,就按照项目一个一个来写,那样大家就可以跟着实践了,谢谢大家的支持,欢迎转发留言~)
,