MTCNN,Multi-task convolutional neural network(多任务卷积神经网络),将人脸区域检测与人脸关键点检测放在了一起。总体可分为P-Net、R-Net、和O-Net三层网络结构,三个网络并行训练,串行使用,这正好体现了MTCNN级联的思想。

对于MTCNN整套流程来说,样本数据集制作及模型训练没有太大难度(毕竟论文已经把网络模型给出,照着写下来就行,当然,对于原论文的模型还是有优化空间的),MTCNN的精髓主要体现在侦测上,里面用到了目标检测常用的IOU,NMS以及图像金字塔,当然还有最难的坐标反算

下面我们来梳理一下整个流程(本文未做人脸关键点检测):

             图片(图像金字塔第一层)---->PNET---->输出形状(1,5,H,W)---->筛选置信度达标的输出--->坐标反算--->原图尺寸*factor

            (图像金字塔第二层)--->缩放后图片再次传入PNET(重复以上步骤)---->直到min(w,h)<12--->拿到所有人脸建议框并做

            NMS--->建议框转正方形后裁剪--->resize成24*24--->输入RNET--->输出形状(N,5)--->筛选置信度达标的输出--->坐标反算

          -->拿到所有人脸框做NMS--->ONET与RNET就是大同小异了(无非就是把resize成48*48,在最后NMS的时候有一点点区别)

下面附上PNET侦测的代码:


def p_net(self, image):
    scale = 1
    p_net_boxes = []
    image_w, image_h = image.size
    min_side = min(image_w, image_h)
    while min_side >= 12:
        image_data = self.trans(image)
        image_data = image_data.unsqueeze(0).to(self.device)
        p_cls_out, p_offset_out = self.pnet(image_data)
        # p_cls_out=p_cls_out.cpu().data()#(1,1,h,w)
        # p_offset_out=p_offset_out.cpu().data()#(1,4,h,w)
        p_cls = p_cls_out.squeeze()#[h,w]
        p_offset = p_offset_out.squeeze()  # [4,h,w]
        index = torch.nonzero(torch.gt(p_cls, 0.5))#[n,2]
        for _index in index:
            box = self._boxes(_index, p_offset, p_cls, scale, strides=2, side_len=12)
            p_net_boxes.append(box)
        scale *= 0.709
        _image_w = image_w * scale
        _image_h = image_h * scale
        min_side = min(_image_w, _image_h)
        image = image.resize((int(_image_w), int(_image_h)))

    P_net_boxes = tool.nms(np.array(p_net_boxes), 0.3, isMin=False)
    return P_net_boxes


def _boxes(self, idx, offset, cls, scale, strides=2, side_len=12):
    x1 = (int(idx[1]) * strides) / scale
    y1 = (int(idx[0]) * strides) / scale
    x2 = (int(idx[1]) * strides + side_len) / scale
    y2 = (int(idx[0]) * strides + side_len) / scale
    w, h = (x2 - x1), (y2 - y1)
    _x1 = x1 + offset[0, idx[0], idx[1]] * w
    _y1 = y1 + offset[1, idx[0], idx[1]] * h
    _x2 = x2 + offset[2, idx[0], idx[1]] * w
    _y2 = y2 + offset[3, idx[0], idx[1]] * h
    _cls = cls[idx[0], idx[1]]
    return [_x1, _y1, _x2, _y2, _cls]


是不是感觉过程听起来很麻烦,代码却不多!

其实就是这样,只要理解了其中的原理,代码还是挺简单的。

下面来详细解释下PNET坐标反算这块:

神经网络级联方法 级联神经网络模型_神经网络级联方法

神经网络级联方法 级联神经网络模型_pytorch_02

其实明白了PNET的坐标反算,那RNET和ONET的反算就简单了,PNET的反算分为两步:先反算到建议框(右图上),再反算回真实框(右图下),那对于RNET和ONET来说,传入的就是建议框,所以只需要反算真实框这一步。

index = torch.nonzero(torch.gt(p_cls, 0.5))#[95,2],这段代码就是找到示意图中的h,w值,经过x1 = (int(idx[1]) * strides) / scale的反算就能找到建议框是在原图哪个大概位置,再经过_x1 = x1 + offset[0, idx[0], idx[1]] * w就能得到精确的回归,值得注意的是,计算x的坐标时h对应index的索引为0,对应的是1,因此计算x时取的是idx[1],计算y时idx[0]

下面解释一下右图中各参数的意义:

_x1,_y1,_x2,_y2:建议框的坐标值

index[:,1],index[:,0]:建议框在特征图上位置

strides:步长,卷积时池化步长为2,因此反算时要乘回去

scale:图像金字塔对应的缩放比例

x1,y1,x2,y2:真实框坐标值

w'=_x2-_x1

h'=_y2-_y1

*offset_x1' ,offset_y1' ,offset_x2',offset_y2':输出的偏移率

MTCNN的坐标反算和YOLO V3的坐标反算有异曲同工之妙,建议深刻理解,里面体现了作者深邃的思想。