mtcnn算法实现基于ncnn版本原码讲解 附源码

mtcnn算法实现基于ncnn版本原码讲解 附源码

源码

https://github.com/wyrcode/mtcnn

原理

MTCNN由3个网络结构组成(P-Net,R-Net,O-Net)。

Proposal Network (P-Net):该网络结构主要获得了人脸区域的候选窗口和边界框的回归向量。并用该边界框做回归,对候选窗口进行校准,然后通过非极大值抑制(NMS)来合并高度重叠的候选框。

Refine Network (R-Net):该网络结构还是通过边界框回归和NMS来去掉那些false-positive区域。

只是由于该网络结构和P-Net网络结构有差异,多了一个全连接层,所以会取得更好的抑制false-positive的作用。

Output Network (O-Net):该层比R-Net层又多了一层卷基层,所以处理的结果会更加精细。作用和R-Net层作用一样。但是该层对人脸区域进行了更多的监督,同时还会输出5个坐标(landmark)。

为了保证5个特征点的精准,在最后面又加了一层Lnet,对人脸的5个特征点进一步的回归修正。

如同下面检测代码的流程

vector<FaceInfo> MtcnnDetector::Detect(ncnn::Mat img)
{
    int img_w = img.w;
    int img_h = img.h;

    vector<FaceInfo> pnet_results = Pnet_Detect(img);//将图片放入Pnet中。得到一批人脸候选框
    doNms(pnet_results, 0.7, "union");//通过非极大值抑制,清除一些正交比过大的候选框,0.7为正交比的阈值
    refine(pnet_results, img_h, img_w, true);//将框的坐标通过回归修正

    vector<FaceInfo> rnet_results = Rnet_Detect(img, pnet_results);//将人脸候选框信息输入Rnet,输出更加精细的人脸候选框信息
    doNms(rnet_results, 0.7, "union");
    refine(rnet_results, img_h, img_w, true);

    vector<FaceInfo> onet_results = Onet_Detect(img, rnet_results);//将人脸候选框信息输入Onet,输出的精细的5个人脸的特征点。
    refine(onet_results, img_h, img_w, false);
    doNms(onet_results, 0.7, "min");

    Lnet_Detect(img, onet_results);//进一步的对采集到的5个特征点坐标进行回归修正

    return onet_results;
}

上面代码中的回归修正函数的详细讲解可以参考我前面的博客:目标检测中的候选框的回归修正c++版

Pnet

Proposal Network (P-Net):该网络结构主要获得了人脸区域的候选窗口和边界框的回归向量。并用该边界框做回归,对候选窗口进行校准,然后通过非极大值抑制(NMS)来合并高度重叠的候选框。
1。首先生成图片金字塔,因为一张图片中的人脸大小不一定,所以将图片大小按比例缩小成多张图片,设置了最小的检测区域为12
这样缩小后的图片中的人脸,就可以被检测到了。
输入图片的尺寸,minsize和factor共同影响了图像金字塔的阶层数。也就是说决定能够生成多少张图。缩放后的尺寸minL=org_L*(12/minisize)*factor^(n),n={0,1,2,3,…,N},缩放尺寸最小不能小于12,也就是缩放到12为止。n的数量也就是能够缩放出图片的数量。

2.以前的是通过滑动窗口来截取图片然后送入网络检测。现在是用卷积代替了滑动窗口,并通过卷积之后的结果,定位原始的检测区域。
网络定义的nput的size是12 * 12* 3,由于Pnet只有卷积层,我们可以直接将resize后的图像给网络进行前传,只是得到的结果不是1* 1* 2和1* 1* 4,而是m* m* 2和m* m* 4。这样就不用先从resize的图上滑动截取各种12* 12* 3的图进入网络,而是一次性送入通过卷积,在根据结果回推每个结果对应的12* 12的图在输入图的什么位置。利用的就是卷积来代替原来的滑动窗口。

3.针对金字塔中每张图,网络forward计算后都得到了人脸得分以及人脸框回归的结果。人脸分类得分是两个通道的三维矩阵m* m* 2,其实对应在网络输入图片上m* m个12* 12的滑框,结合当前图片在金字塔图片中的缩放scale,可以推算出每个滑框在原始图像中的具体坐标。

4.首先要根据得分进行筛选,得分低于阈值的滑框,排除。
当金字塔中所有图片处理完后,然后利用nms非极大值抑制,对剩下的滑框进行合并。然后利用最后剩余的滑框对应的Bbox结果转换成原始图像中像素坐标,也就是得到了人脸框的坐标。
nms具体解释,可以参照我的博客:NMS非极大值抑制,
候选框的生成的详细过程解释,可以参照我的博客:MTCNN中 Pnet候选框生成算法
生成候选框的算法可参考我的前一篇博客。
通过代码来学习Pnet的工作原理

vector<FaceInfo> MtcnnDetector::Pnet_Detect(ncnn::Mat img)
{
    vector<FaceInfo> results;
    int img_w = img.w;
    int img_h = img.h;
    float minl = img_w < img_h ? img_w : img_h;
    double scale = 12.0 / this->minsize;//12为最小检测的大小
    minl *= scale;
    vector<double> scales;
    while (minl > 12)
    {
        scales.push_back(scale);
        minl *= this->factor;
        scale *= this->factor;
    }//得到一系列的缩小的比例,根据比例缩小的每一张图片输入Pnet网络中
    for (auto it = scales.begin(); it != scales.end(); it++)
    {
        scale = (double)(*it);
        int hs = (int) ceil(img_h * scale);
        int ws = (int) ceil(img_w * scale);//缩小宽高
        ncnn::Mat in = resize(img, ws, hs);
        in.substract_mean_normalize(this->mean_vals, this->norm_vals);
        ncnn::Extractor ex = Pnet.create_extractor();
        ex.set_light_mode(true);
        ex.input("data", in);
        ncnn::Mat score;
        ncnn::Mat location;
        ex.extract("prob1", score);//判断各区域是否有人脸的概率,输出维度为m*m*2
        ex.extract("conv4_2", location);//判断各回归框的修正信息,输出维度为m*m*4
        //输出信息可参考下面的网络结构
        vector<FaceInfo> bboxs = generateBbox(score, location, *it, this->threshold[0]);//根据阈值和Pnet网络的输出,生成候选框
        doNms(bboxs, 0.5, "union");//非极大值抑制
        results.insert(results.end(), bboxs.begin(), bboxs.end());
    }
    return results;
}

pnet网络结构
在这里插入图片描述

Rnet

该网络结构还是通过边界框回归和NMS来去掉那些false-positive区域。

只是由于该网络结构和P-Net网络结构有差异,多了一个全连接层,所以会取得更好的抑制false-positive的作用。
代码中基本同Pnet一样

vector<FaceInfo> MtcnnDetector::Rnet_Detect(ncnn::Mat img, vector<FaceInfo> bboxs)
{
    vector<FaceInfo> results;

    int img_w = img.w;
    int img_h = img.h;

    for (auto it = bboxs.begin(); it != bboxs.end(); it++)
    {
        ncnn::Mat img_t;
        copy_cut_border(img, img_t, it->y[0], img_h - it->y[1], it->x[0], img_w - it->x[1]);
        ncnn::Mat in = resize(img_t, 24, 24);
        in.substract_mean_normalize(this->mean_vals, this->norm_vals);
        ncnn::Extractor ex = Rnet.create_extractor();
        ex.set_light_mode(true);
        ex.input("data", in);
        ncnn::Mat score, bbox;
        ex.extract("prob1", score);
        ex.extract("conv5_2", bbox);
        if ((float)score[1] > threshold[1])//若人脸候选框的置信度大于阈值,则对候选框进修正
        {
            for (int c = 0; c < 4; c++)
            {
                it->regreCoord[c] = (float)bbox[c];
            }
            it->score = (float)score[1];
            results.push_back(*it);
        }
    }
    return results;
}

Rnet网络结构
在这里插入图片描述

Onet

该层比R-Net层又多了一层卷基层,所以处理的结果会更加精细。作用和R-Net层作用一样。但是该层对人脸区域进行了更多的监督,同时还会输出5个坐标(landmark)。

vector<FaceInfo> MtcnnDetector::Onet_Detect(ncnn::Mat img, vector<FaceInfo> bboxs)
{
    vector<FaceInfo> results;

    int img_w = img.w;
    int img_h = img.h;

    for (auto it = bboxs.begin(); it != bboxs.end(); it++)
    {
        ncnn::Mat img_t;
        copy_cut_border(img, img_t, it->y[0], img_h - it->y[1], it->x[0], img_w - it->x[1]);
        ncnn::Mat in = resize(img_t, 48, 48);
        in.substract_mean_normalize(this->mean_vals, this->norm_vals);
        ncnn::Extractor ex = Onet.create_extractor();
        ex.set_light_mode(true);
        ex.input("data", in);
        ncnn::Mat score, bbox, point;
        ex.extract("prob1", score);
        ex.extract("conv6_2", bbox);
        ex.extract("conv6_3", point);
        if ((float)score[1] > threshold[2])
        {
            for (int c = 0; c < 4; c++)
            {
                it->regreCoord[c] = (float)bbox[c];
            }
            for (int p = 0; p < 5; p++)
            {
                it->landmark[2 * p] =  it->x[0] + (it->x[1] - it->x[0]) * point[p];
                it->landmark[2 * p + 1] = it->y[0] + (it->y[1] - it->y[0]) * point[p + 5];
            }
            it->score = (float)score[1];
            results.push_back(*it);
        }
    }
    return results;
}

Onet的网络结构
在这里插入图片描述

Lnet

Lnet,对人脸的5个特征点进一步的回归修正。

void MtcnnDetector::Lnet_Detect(ncnn::Mat img, vector<FaceInfo> &bboxes)
{
    int img_w = img.w;
    int img_h = img.h;

    for (auto it = bboxes.begin(); it != bboxes.end(); it++)
    {
        int w = it->x[1] - it->x[0] + 1;
        int h = it->y[1] - it->y[0] + 1;
        int m = w > h ? w : h;
        m = (int)round(m * 0.25);
        if (m % 2 == 1) m++;
        m /= 2;

        ncnn::Mat in(24, 24, 15);

        for (int i = 0; i < 5; i++)
        {
            int px = it->landmark[2 * i];
            int py = it->landmark[2 * i + 1];
            ncnn::Mat cut;
            copy_cut_border(img, cut, py - m, img_h - py - m, px - m, img_w - px - m);
            ncnn::Mat resized = resize(cut, 24, 24);
            resized.substract_mean_normalize(this->mean_vals, this->norm_vals);
            for (int j = 0; j < 3; j++)
                memcpy(in.channel(3 * i + j), resized.channel(j), 24 * 24 * sizeof(float));
        }

        ncnn::Extractor ex = Lnet.create_extractor();
        ex.set_light_mode(true);
        ex.input("data", in);
        ncnn::Mat out1, out2, out3, out4, out5;

        ex.extract("fc5_1", out1);
        ex.extract("fc5_2", out2);
        ex.extract("fc5_3", out3);
        ex.extract("fc5_4", out4);
        ex.extract("fc5_5", out5);

        if (abs(out1[0] - 0.5) > 0.35) out1[0] = 0.5f;
        if (abs(out1[1] - 0.5) > 0.35) out1[1] = 0.5f;
        if (abs(out2[0] - 0.5) > 0.35) out2[0] = 0.5f;
        if (abs(out2[1] - 0.5) > 0.35) out2[1] = 0.5f;
        if (abs(out3[0] - 0.5) > 0.35) out3[0] = 0.5f;
        if (abs(out3[1] - 0.5) > 0.35) out3[1] = 0.5f;
        if (abs(out4[0] - 0.5) > 0.35) out4[0] = 0.5f;
        if (abs(out4[1] - 0.5) > 0.35) out4[1] = 0.5f;
        if (abs(out5[0] - 0.5) > 0.35) out5[0] = 0.5f;
        if (abs(out5[1] - 0.5) > 0.35) out5[1] = 0.5f;

        it->landmark[0] += (int)round((out1[0] - 0.5) * m * 2);
        it->landmark[1] += (int)round((out1[1] - 0.5) * m * 2);
        it->landmark[2] += (int)round((out2[0] - 0.5) * m * 2);
        it->landmark[3] += (int)round((out2[1] - 0.5) * m * 2);
        it->landmark[4] += (int)round((out3[0] - 0.5) * m * 2);
        it->landmark[5] += (int)round((out3[1] - 0.5) * m * 2);
        it->landmark[6] += (int)round((out4[0] - 0.5) * m * 2);
        it->landmark[7] += (int)round((out4[1] - 0.5) * m * 2);
        it->landmark[8] += (int)round((out5[0] - 0.5) * m * 2);
        it->landmark[9] += (int)round((out5[1] - 0.5) * m * 2);
    }
}

其他函数

包括回归框的生成,非极大值抑制nms,框的回归修正refin。具体的函数解释请参考我前面的博客。

各网络的损失函数

MTCNN特征描述子主要包含3个部分,人脸/非人脸分类器,边界框回归,地标定位。

人脸分类:
在这里插入图片描述
上式为人脸分类的交叉熵损失函数,其中,pi为是人脸的概率,yidet为背景的真实标签。

边界框回归:
在这里插入图片描述

上式为通过欧氏距离计算的回归损失。其中,带尖的y为通过网络预测得到,不带尖的y为实际的真实的背景坐标。其中,y为一个(左上角x,左上角y,长,宽)组成的四元组。

地标定位:
在这里插入图片描述

和边界回归一样,还是计算网络预测的地标位置和实际真实地标的欧式距离,并最小化该距离。其中,,带尖的y为通过网络预测得到,不带尖的y为实际的真实的地标坐标。由于一共5个点,每个点2个坐标,所以,y属于十元组。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值