MTCNN人脸及特征点检测---代码应用详解(基于ncnn架构)

2023-10-28

本博记录为卤煮理解,如有疏漏,请指正。转载请注明出处。

卤煮:非文艺小燕儿

本博地址:MTCNN人脸及特征点检测---代码应用详解


本文主要讲述当你拿到MTCNN的caffemodel后,如何使用它对一张图里的人脸进行检测和特征点标定。

相当于一个代码实现的解释。因为最近卤煮在用ncnn,所以该代码也是基于ncnn架构做的。 caffe架构同理。


如果你对MTCNN这篇论文还不熟悉,建议先去看原理。也可以用我之前写的相关博客做参考:

MTCNN解读:Joint Face Detection and Alignment using Multi-task Cascaded Convolutional Networks



1. MTCNN关键参数

nms_threshold:非极大值抑制nms筛选人脸框时的IOU阈值,三个网络可单独设定阈值,值设置的过小,nms合并的少,会产生较多冗余计算。示例nms_threshold[3] = { 0.5, 0.7, 0.7 };。

threshold:人脸框得分阈值,三个网络可单独设定阈值,值设置的太小,会有很多框通过,也就增加了计算量,还有可能导致最后不是人脸的框错认为人脸。示例threshold[3] = {0.8, 0.8, 0.8};

minsize :最小可检测图像,该值大小,可控制图像金字塔的阶层数的参数之一,越小,阶层越多,计算越多。示例minsize = 40;

factor :生成图像金字塔时候的缩放系数, 范围(0,1),可控制图像金字塔的阶层数的参数之一,越大,阶层越多,计算越多。示例factor = 0.709;


输入图片的尺寸,minsize和factor共同影响了图像金字塔的阶层数。用户可根据自己的精度需求进行调控。


MTCNN整体过程只管图示如下:


接下来对使用过程进行详细说明:


2. 生成图像金字塔

前面提到,输入图片的尺寸,minsize和factor共同影响了图像金字塔的阶层数。也就是说决定能够生成多少张图。


缩放后的尺寸minL=org_L*(12/minisize)*factor^(n),n={0,1,2,3,...,N},缩放尺寸最小不能小于12,也就是缩放到12为止。n的数量也就是能够缩放出图片的数量。


看到上面这个公式应该就明白为啥那三个参数能够影响阶层数了吧。



3. Pnet运算


一般Pnet只做检测和人脸框回归两个任务。忽略下图中的Facial landmark。



虽然网络定义的时候input的size是12*12*3,由于Pnet只有卷积层,我们可以直接将resize后的图像喂给网络进行前传,只是得到的结果就不是1*1*2和1*1*4,而是m*m*2和m*m*4了。这样就不用先从resize的图上截取各种12*12*3的图再送入网络了,而是一次性送入,再根据结果回推每个结果对应的12*12的图在输入图片的什么位置。



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

首先要根据得分进行筛选,得分低于阈值的滑框,排除。

然后利用nms非极大值抑制,对剩下的滑框进行合并。nms具体解释,可以参照我上一篇博客:NMS非极大值抑制:用擂台赛带你从原理到代码脑洞大开恍然大悟


当金字塔中所有图片处理完后,再利用nms对汇总的滑框进行合并,然后利用最后剩余的滑框对应的Bbox结果转换成原始图像中像素坐标,也就是得到了人脸框的坐标。


所以,Pnet最终能够得到了一批人脸框。


3. Rnet

Rnet仍然只做检测和人脸框回归两个任务。忽略下图中的Facial landmark。




Rnet的作用是对Pnet得到的人脸框进一步打分筛选,回归人脸框。


将Pnet运算出来的人脸框从原图上截取下来,并且resize到24*24*3,作为Rnet的输入。输出仍然是得分和BBox回归结果。

对得分低于阈值的候选框进行抛弃,剩下的候选框做nms进行合并,然后再将BBox回归结果映射到原始图像的像素坐标上。


所以,Rnet最终得到的是在Pnet结果中精选出来的人脸框。


4. Onet

Onet将检测,人脸框回归和特征点定位,一起做了。

Onet的作用是对Rnet得到的人脸框进一步打分筛选,回归人脸框。同时在每个框上都计算特征点位置。


将Rnet运算出来的人脸框从原图上截取下来,并且resize到48*48*3,作为Onet的输入。输出是得分,BBox回归结果以及landmark位置数据。

分数超过阈值的候选框对应的Bbox回归数据以及landmark数据进行保存。

Bbox回归数据以及landmark数据映射到原始图像坐标上。

再次实施nms对人脸框进行合并。


经过这层层筛选合并后,最终剩下的Bbox以及其对应的landmark就是我们苦苦追求的结果了。


下面附上代码:(代码以https://github.com/ElegantGod/ncnn/tree/master/mtcnn为base,卤煮在上面做了很多注释以及稍许修改,以助于理解)

#include <stdio.h>
#include <algorithm>
#include <vector>
#include <math.h>
#include <iostream>
#include <time.h>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

#include "net.h"
#include"cpu.h"
using namespace std;
using namespace cv;

struct Bbox
{
    float score;
    int x1;
    int y1;
    int x2;
    int y2;
    float area;
    bool exist;
    float ppoint[10];
    float regreCoord[4];
};

struct orderScore
{
    float score;
    int oriOrder;
};

void resize_image(ncnn::Mat& srcImage, ncnn::Mat& dstImage)
{
	int src_width = srcImage.w;
	int src_height = srcImage.h;
	int src_channel = srcImage.c;
	int dst_width = dstImage.w;
	int dst_height = dstImage.h;
	int dst_channel = dstImage.c;

	if (src_width == dst_width && src_height == dst_height)
	{
		memcpy(dstImage.data, srcImage.data, src_width*src_height*src_channel*sizeof(float));
		return;
	}
	float lf_x_scl = static_cast<float>(src_width) / dst_width;
	float lf_y_Scl = static_cast<float>(src_height) / dst_height;
	const float* src_data = srcImage.data;

	float* dest_data = dstImage.data;
	int src_area = srcImage.cstep;
	int src_area2 = 2 * src_area;
	int dst_area = dstImage.cstep;
	int dst_area2 = 2 * dst_area;

	for (int y = 0; y < dst_height; y++) {
		for (int x = 0; x < dst_width; x++) {
			float lf_x_s = lf_x_scl * x;
			float lf_y_s = lf_y_Scl * y;

			int n_x_s = static_cast<int>(lf_x_s);
			n_x_s = (n_x_s <= (src_width - 2) ? n_x_s : (src_width - 2));
			int n_y_s = static_cast<int>(lf_y_s);
			n_y_s = (n_y_s <= (src_height - 2) ? n_y_s : (src_height - 2));

			float lf_weight_x = lf_x_s - n_x_s;
			float lf_weight_y = lf_y_s - n_y_s;

			float dest_val_b = (1 - lf_weight_y) * ((1 - lf_weight_x) *
				src_data[n_y_s * src_width + n_x_s] +
				lf_weight_x * src_data[n_y_s * src_width + n_x_s + 1]) +
				lf_weight_y * ((1 - lf_weight_x) * src_data[(n_y_s + 1) * src_width + n_x_s] +
				lf_weight_x * src_data[(n_y_s + 1) * src_width + n_x_s + 1]);
			float dest_val_g = (1 - lf_weight_y) * ((1 - lf_weight_x) *
				src_data[n_y_s * src_width + n_x_s + src_area] +
				lf_weight_x * src_data[n_y_s * src_width + n_x_s + 1 + src_area]) +
				lf_weight_y * ((1 - lf_weight_x) * src_data[(n_y_s + 1) * src_width + n_x_s + src_area] +
				lf_weight_x * src_data[(n_y_s + 1) * src_width + n_x_s + 1 + src_area]);
			float dest_val_r = (1 - lf_weight_y) * ((1 - lf_weight_x) *
				src_data[n_y_s * src_width + n_x_s + src_area2] +
				lf_weight_x * src_data[n_y_s * src_width + n_x_s + 1 + src_area2]) +
				lf_weight_y * ((1 - lf_weight_x) * src_data[(n_y_s + 1) * src_width + n_x_s + src_area2] +
				lf_weight_x * src_data[(n_y_s + 1) * src_width + n_x_s + 1 + src_area2]);

			dest_data[y * dst_width + x] = static_cast<float>(dest_val_b);
			dest_data[y * dst_width + x + dst_area] = static_cast<float>(dest_val_g);
			dest_data[y * dst_width + x + 2 * dst_area] = static_cast <float>(dest_val_r);
		}
	}
}

bool cmpScore(orderScore lsh, orderScore rsh){
    if(lsh.score<rsh.score)
        return true;
    else
        return false;
}

class mtcnn{
public:
    mtcnn();
    void detect(ncnn::Mat& img_, std::vector<Bbox>& finalBbox);
	cv::Mat cp_img;
private:
    void generateBbox(ncnn::Mat score, ncnn::Mat location, vector<Bbox>& boundingBox_, vector<orderScore>& bboxScore_, float scale);
    void nms(vector<Bbox> &boundingBox_, std::vector<orderScore> &bboxScore_, const float overlap_threshold, string modelname="Union");
    void refineAndSquareBbox(vector<Bbox> &vecBbox, const int &height, const int &width);

    ncnn::Net Pnet, Rnet, Onet;
    ncnn::Mat img;

	float nms_threshold[3];// = { 0.5, 0.7, 0.7 };
	float threshold[3];//  = {0.8, 0.8, 0.8};
	float mean_vals[3];//  = {127.5, 127.5, 127.5};
	float norm_vals[3];//  = {0.0078125, 0.0078125, 0.0078125};
    std::vector<Bbox> firstBbox_, secondBbox_,thirdBbox_;
    std::vector<orderScore> firstOrderScore_, secondBboxScore_, thirdBboxScore_;
    int img_w, img_h;
	
};

mtcnn::mtcnn(){
	for (int i = 0; i < 3; i++)
	{
		nms_threshold[i]=0.7;// = { 0.5, 0.7, 0.7 };
		threshold[i]=0.7;//  = {0.8, 0.8, 0.8};
		mean_vals[i]=127.5;//  = {127.5, 127.5, 127.5};
		norm_vals[i]=0.0078125;//  = {0.0078125, 0.0078125, 0.0078125};
	}
	nms_threshold[0] = 0.5;
    Pnet.load_param("E:/Algrithm/MTCNN/MTCNN-master/mtcnn_caffe/model/det1.param");
    Pnet.load_model("E:/Algrithm/MTCNN/MTCNN-master/mtcnn_caffe/model/det1.bin");
    Rnet.load_param("E:/Algrithm/MTCNN/MTCNN-master/mtcnn_caffe/model/det2.param");
    Rnet.load_model("E:/Algrithm/MTCNN/MTCNN-master/mtcnn_caffe/model/det2.bin");
    Onet.load_param("E:/Algrithm/MTCNN/MTCNN-master/mtcnn_caffe/model/det3.param");
    Onet.load_model("E:/Algrithm/MTCNN/MTCNN-master/mtcnn_caffe/model/det3.bin");
	//cp_img.create(295, 413, CV_8UC3);
	//const char* imagepath = "E:/Algrithm/ncnn/ncnn/x64/Release/test3.jpg";// argv[1];

	//cp_img = cv::imread(imagepath);
}

/******************generateBbox******************************/
//根据Pnet的输出结果,由滑框的得分,筛选可能是人脸的滑框,并记录该框的位置、人脸坐标信息、得分以及编号
void mtcnn::generateBbox(ncnn::Mat score, ncnn::Mat location, std::vector<Bbox>& boundingBox_, std::vector<orderScore>& bboxScore_, float scale){
    int stride = 2;//Pnet中有一次MP2*2,后续转换的时候相当于stride=2;
    int cellsize = 12;
    int count = 0;
    //score p
    float *p = score.channel(1);//score.data + score.cstep;//判定为人脸的概率
    //float *plocal = location.data;
    Bbox bbox;
    orderScore order;
//	float max_p = 0;
    for(int row=0;row<score.h;row++){
        for(int col=0;col<score.w;col++){
			//printf("Pnet prob: %f\n", *p);
			//if (*p>max_p)
			//{
			//	max_p = *p;
			//}
            if(*p>threshold[0]){
                bbox.score = *p;//记录得分
                order.score = *p;
                order.oriOrder = count;//记录有效滑框的编号
                bbox.x1 = round((stride*col+1)/scale);//12*12的滑框,换算到原始图像上的坐标
                bbox.y1 = round((stride*row+1)/scale);
                bbox.x2 = round((stride*col+1+cellsize)/scale);
                bbox.y2 = round((stride*row+1+cellsize)/scale);
                bbox.exist = true;
                bbox.area = (bbox.x2 - bbox.x1)*(bbox.y2 - bbox.y1);
                for(int channel=0;channel<4;channel++)
                    bbox.regreCoord[channel]=location.channel(channel)[0];//人脸框的坐标相关值
                boundingBox_.push_back(bbox);
                bboxScore_.push_back(order);
                count++;
            }
            p++;
            //plocal++;
        }
    }
	//printf("Pnet max prob: %f\n",max_p);
}

/**********************nms非极大值抑制****************************/
void mtcnn::nms(std::vector<Bbox> &boundingBox_, std::vector<orderScore> &bboxScore_, const float overlap_threshold, string modelname){
    if(boundingBox_.empty()){
        return;
    }
    std::vector<int> heros;
    //sort the score
    sort(bboxScore_.begin(), bboxScore_.end(), cmpScore);//cmpScore指定升序排列

    int order = 0;
    float IOU = 0;
    float maxX = 0;
    float maxY = 0;
    float minX = 0;
    float minY = 0;
	//规则,站上擂台的擂台主,永远都是胜利者。
    while(bboxScore_.size()>0){
        order = bboxScore_.back().oriOrder;//取得分最高勇士的编号ID。
        bboxScore_.pop_back();//勇士出列
        if(order<0)continue;//死的?下一个!(order在(*it).oriOrder = -1;改变)
        heros.push_back(order);//记录擂台主ID
        boundingBox_.at(order).exist = false;//当前这个Bbox为擂台主,签订生死簿。

        for(int num=0;num<boundingBox_.size();num++){
            if(boundingBox_.at(num).exist){//活着的勇士
                //the iou
                maxX = (boundingBox_.at(num).x1>boundingBox_.at(order).x1)?boundingBox_.at(num).x1:boundingBox_.at(order).x1;
                maxY = (boundingBox_.at(num).y1>boundingBox_.at(order).y1)?boundingBox_.at(num).y1:boundingBox_.at(order).y1;
                minX = (boundingBox_.at(num).x2<boundingBox_.at(order).x2)?boundingBox_.at(num).x2:boundingBox_.at(order).x2;
                minY = (boundingBox_.at(num).y2<boundingBox_.at(order).y2)?boundingBox_.at(num).y2:boundingBox_.at(order).y2;
                //maxX1 and maxY1 reuse 
                maxX = ((minX-maxX+1)>0)?(minX-maxX+1):0;
                maxY = ((minY-maxY+1)>0)?(minY-maxY+1):0;
                //IOU reuse for the area of two bbox
                IOU = maxX * maxY;
                if(!modelname.compare("Union"))
                    IOU = IOU/(boundingBox_.at(num).area + boundingBox_.at(order).area - IOU);
                else if(!modelname.compare("Min")){
                    IOU = IOU/((boundingBox_.at(num).area<boundingBox_.at(order).area)?boundingBox_.at(num).area:boundingBox_.at(order).area);
                }
                if(IOU>overlap_threshold){
                    boundingBox_.at(num).exist=false;//如果该对比框与擂台主的IOU够大,挑战者勇士战死
                    for(vector<orderScore>::iterator it=bboxScore_.begin(); it!=bboxScore_.end();it++){
                        if((*it).oriOrder == num) {
                            (*it).oriOrder = -1;//勇士战死标志
                            break;
                        }
                    }
                }//else 那些距离擂台主比较远迎战者幸免于难,将有机会作为擂台主出现
            }
        }
    }
    for(int i=0;i<heros.size();i++)
        boundingBox_.at(heros.at(i)).exist = true;//从生死簿上剔除,擂台主活下来了
}
void mtcnn::refineAndSquareBbox(vector<Bbox> &vecBbox, const int &height, const int &width){
    if(vecBbox.empty()){
        cout<<"Bbox is empty!!"<<endl;
        return;
    }
    float bbw=0, bbh=0, maxSide=0;
    float h = 0, w = 0;
    float x1=0, y1=0, x2=0, y2=0;
    for(vector<Bbox>::iterator it=vecBbox.begin(); it!=vecBbox.end();it++){
        if((*it).exist){
            bbw = (*it).x2 - (*it).x1 + 1;//滑框的宽高计算
            bbh = (*it).y2 - (*it).y1 + 1;
            x1 = (*it).x1 + (*it).regreCoord[0]*bbw;//人脸框的位置坐标计算
            y1 = (*it).y1 + (*it).regreCoord[1]*bbh;
            x2 = (*it).x2 + (*it).regreCoord[2]*bbw;
            y2 = (*it).y2 + (*it).regreCoord[3]*bbh;

            w = x2 - x1 + 1;//人脸框宽高
            h = y2 - y1 + 1;
          
            maxSide = (h>w)?h:w;
            x1 = x1 + w*0.5 - maxSide*0.5;
            y1 = y1 + h*0.5 - maxSide*0.5;
            (*it).x2 = round(x1 + maxSide - 1);
            (*it).y2 = round(y1 + maxSide - 1);
            (*it).x1 = round(x1);
            (*it).y1 = round(y1);

            //boundary check
            if((*it).x1<0)(*it).x1=0;
            if((*it).y1<0)(*it).y1=0;
            if((*it).x2>width)(*it).x2 = width - 1;
            if((*it).y2>height)(*it).y2 = height - 1;

            it->area = (it->x2 - it->x1)*(it->y2 - it->y1);
        }
    }
}
void mtcnn::detect(ncnn::Mat& img_, std::vector<Bbox>& finalBbox_){
    img = img_;
    img_w = img.w;
    img_h = img.h;
    img.substract_mean_normalize(mean_vals, norm_vals);//数据预处理,归一化至(-1,1)

    float minl = img_w<img_h?img_w:img_h;
    int MIN_DET_SIZE = 12;
    int minsize = 40;//最小可检测图像,该值大小,控制图像金字塔的阶层数,越小,阶层越多,计算越多。
    float m = (float)MIN_DET_SIZE/minsize;
    minl *= m;
    float factor = 0.409;
    int factor_count = 0;
    vector<float> scales_;
    while(minl>MIN_DET_SIZE){
		if (factor_count > 0){ m = m*factor; }
        scales_.push_back(m);
        minl *= factor;
        factor_count++;
    }
    orderScore order;
    int count = 0;

    for (size_t i = 0; i < scales_.size(); i++) {
        int hs = (int)ceil(img_h*scales_[i]);
        int ws = (int)ceil(img_w*scales_[i]);
        ncnn::Mat in(ws, hs, 3);
        resize_image(img, in);//一次次生成图像金字塔中的一层图
        ncnn::Extractor ex = Pnet.create_extractor();
        ex.set_light_mode(true);
		printf("Pnet input width:%d, height:%d, channel:%d\n",in.w,in.h,in.c);
        ex.input("data", in);//Pnet只有卷积层,所以可以接受不同size的input
        ncnn::Mat score_, location_;
        ex.extract("prob1", score_);
		printf("prob1 w:%d, h:%d, ch:%d, first data:%f\n", score_.w, score_.h, score_.c, score_.data[0]);
		//for (int t_w = 0; t_w < score_.w*score_.h*score_.c; t_w++)
		//{
		//	printf("%f, ", score_.data[t_w]);
		//}
        ex.extract("conv4-2", location_);
        std::vector<Bbox> boundingBox_;
        std::vector<orderScore> bboxScore_;
        generateBbox(score_, location_, boundingBox_, bboxScore_, scales_[i]);
        nms(boundingBox_, bboxScore_, nms_threshold[0]);//分会场擂台赛

        for(vector<Bbox>::iterator it=boundingBox_.begin(); it!=boundingBox_.end();it++){
            if((*it).exist){//获胜擂台主得到进入主会场的机会
                firstBbox_.push_back(*it);//主会场花名册
                order.score = (*it).score;
                order.oriOrder = count;
                firstOrderScore_.push_back(order);
                count++;
            }
        }
        bboxScore_.clear();
        boundingBox_.clear();
    }
    //the first stage's nms
    if(count<1)return;
    nms(firstBbox_, firstOrderScore_, nms_threshold[0]);//主会场擂台赛
    refineAndSquareBbox(firstBbox_, img_h, img_w);
    printf("firstBbox_.size()=%d\n", firstBbox_.size());
	//for (vector<Bbox>::iterator it = firstBbox_.begin(); it != firstBbox_.end(); it++)
	//{
	//	cout << "OK" << endl;
	//	//rectangle(cp_img, Point((*it).x1, (*it).y1), Point((*it).x2, (*it).y2), Scalar(0, 0, 255), 2, 8, 0);
	//}
	//imshow("Pnet.jpg", cp_img);
	//waitKey(1000);
    //second stage
    count = 0;
    for(vector<Bbox>::iterator it=firstBbox_.begin(); it!=firstBbox_.end();it++){
        if((*it).exist){
            ncnn::Mat tempIm;
            copy_cut_border(img, tempIm, (*it).y1, img_h-(*it).y2, (*it).x1, img_w-(*it).x2);
            ncnn::Mat in(24, 24, 3);		
            resize_image(tempIm, in);
            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(*(score.data+score.cstep)>threshold[1]){
                for(int channel=0;channel<4;channel++)
                    it->regreCoord[channel]=bbox.channel(channel)[0];//*(bbox.data+channel*bbox.cstep);
                it->area = (it->x2 - it->x1)*(it->y2 - it->y1);
                it->score = score.channel(1)[0];//*(score.data+score.cstep);
                secondBbox_.push_back(*it);
                order.score = it->score;
                order.oriOrder = count++;
                secondBboxScore_.push_back(order);
            }
            else{
                (*it).exist=false;
            }
        }
    }
    printf("secondBbox_.size()=%d\n", secondBbox_.size());
    if(count<1)return;
    nms(secondBbox_, secondBboxScore_, nms_threshold[1]);
    refineAndSquareBbox(secondBbox_, img_h, img_w);

    //third stage 
    count = 0;
    for(vector<Bbox>::iterator it=secondBbox_.begin(); it!=secondBbox_.end();it++){
        if((*it).exist){
            ncnn::Mat tempIm;
            copy_cut_border(img, tempIm, (*it).y1, img_h-(*it).y2, (*it).x1, img_w-(*it).x2);
            ncnn::Mat in(48, 48, 3);
            resize_image(tempIm, in);
            ncnn::Extractor ex = Onet.create_extractor();
            ex.set_light_mode(true);
            ex.input("data", in);
            ncnn::Mat score, bbox, keyPoint;
            ex.extract("prob1", score);
            ex.extract("conv6-2", bbox);
            ex.extract("conv6-3", keyPoint);
            if(score.channel(1)[0]>threshold[2]){
                for(int channel=0;channel<4;channel++)
                    it->regreCoord[channel]=bbox.channel(channel)[0];
                it->area = (it->x2 - it->x1)*(it->y2 - it->y1);
                it->score = score.channel(1)[0];
                for(int num=0;num<5;num++){
                    (it->ppoint)[num] = it->x1 + (it->x2 - it->x1)*keyPoint.channel(num)[0];
                    (it->ppoint)[num+5] = it->y1 + (it->y2 - it->y1)*keyPoint.channel(num+5)[0];
                }

                thirdBbox_.push_back(*it);
                order.score = it->score;
                order.oriOrder = count++;
                thirdBboxScore_.push_back(order);
            }
            else
                (*it).exist=false;
            }
        }

    printf("thirdBbox_.size()=%d\n", thirdBbox_.size());
    if(count<1)return;
    refineAndSquareBbox(thirdBbox_, img_h, img_w);
    nms(thirdBbox_, thirdBboxScore_, nms_threshold[2], "Min");
    finalBbox_ = thirdBbox_;

    firstBbox_.clear();
    firstOrderScore_.clear();
    secondBbox_.clear();
    secondBboxScore_.clear();
    thirdBbox_.clear();
    thirdBboxScore_.clear();
}

int main(int argc, char** argv)
{
	/******读图(start)*******/
	const char* imagepath ;// argv[1];
	if (argc == 2)
	{
		imagepath = argv[1];
	}
	else{
		imagepath = "E:/Algrithm/ncnn/ncnn/x64/Release/test2.jpg";
	}
	cout << imagepath << endl;
    cv::Mat cv_img = cv::imread(imagepath);
    if (cv_img.data==NULL)
    {
        fprintf(stderr, "cv::imread %s failed\n", imagepath);
		system("pause");
        return -1;
    }
	printf("img w: %d  h:%d  ch:%d\n",cv_img.cols,cv_img.rows,cv_img.channels());
	imshow("img",cv_img);
	waitKey(10);
	/***************读图(end)********************/

	/***********MTCNN运算(start)************/
	float start = clock();
	int times = 1;
	ncnn::set_omp_num_threads(4);
	for (int cnt = 0; cnt < times; cnt++)
	{
		std::vector<Bbox> finalBbox;
		mtcnn Net;
		//OpenCV读出的图片是BGR格式的,需要转为RGB格式,否则检出率会很低。
		ncnn::Mat ncnn_img = ncnn::Mat::from_pixels(cv_img.data, ncnn::Mat::PIXEL_BGR2RGB, cv_img.cols, cv_img.rows);
		Net.detect(ncnn_img, finalBbox);
		for (vector<Bbox>::iterator it = finalBbox.begin(); it != finalBbox.end(); it++){
			if ((*it).exist)
			{
				printf("Bbox [x1,y1], [x2,y2]:[%d,%d], [%d,%d] \n", (*it).x1, (*it).x2, (*it).y1, (*it).y2);
				rectangle(cv_img, Point((*it).x1, (*it).y1), Point((*it).x2, (*it).y2), Scalar(0, 0, 255), 2, 8, 0);
				for (int num = 0; num < 5; num++)
				{
					printf("Landmark [x1,y1]: [%d,%d] \n", (int)*(it->ppoint + num), (int)*(it->ppoint + num + 5));
					circle(cv_img, Point((int)*(it->ppoint + num), (int)*(it->ppoint + num + 5)), 3, Scalar(0, 255, 255), -1);
				}
			}
		}
	}
	/***********MTCNN运算(end)************/
	printf("MTCNN mean time comsuming: %f ms\n",(clock()-start)/times);
    imshow("result.jpg",cv_img);
	waitKey(100);
	system("pause");
    return 0;
}
#endif


终于将mtcnn缕清楚了,神清气爽~~~





本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MTCNN人脸及特征点检测---代码应用详解(基于ncnn架构) 的相关文章

  • AI,v3,百度人脸识别库上传---node

    config有必要的grant type client id client secret var https require https var request require request var qs require querystr
  • 基于LBPH算法进行人脸识别的毕设项目(一)

    摘要 在完成本科毕业设计时期 选题使关于人脸识别的一个题目 主要内容是通过海康威视的网络摄像头让视频数据在上传到笔记本电脑端进行实时的人脸检测与识别 识别出人脸则显示信息 应用场景为针对斑马线行人闯红灯的行为 通过对下位机实时视频数据的处理
  • 模式识别学习初感悟

    学习模式识别已经快一个月了 从开始的混沌迷茫到现在的小有感觉 不能说自己进步了很多 只能说自己没有虚度这一个月的时光 下面相对自己这一个月的学习做一个总结 希望各位老师同学能多提宝贵意见 毕竟学术要在交流中才能发展 考研的生活结束后 顺利进
  • python 图像处理(5):图像的批量处理

    有些时候 我们不仅要对一张图片进行处理 可能还会对一批图片处理 这时候 我们可以通过循环来执行处理 也可以调用程序自带的图片集合来处理 图片集合函数为 skimage io ImageCollection load pattern load
  • 人脸识别研究

    转载自 https www jianshu com p 639e3f8b7253 本篇文章十分的长 大概有2万7千字左右 一 发展史 1 人脸识别的理解 人脸识别 Face Recognition 是一种依据人的面部特征 如统计或几何特征等
  • 手把手教你:人脸识别考勤系统

    系列文章 手把手教你 人脸识别考勤系统 本文为系列第一篇 文章目录 系列文章 项目简介 一 项目展示 二 环境需求 环境安装实例 三 功能模块介绍 1 人脸库图像 2 构建人脸库 3 启动人脸识别功能 完整代码地址 项目简介 本文主要介绍如
  • 【毕业设计】stm32机器视觉的人脸识别系统 - 单片机 物联网 嵌入式

    文章目录 0 前言 1 简介 2 主要器件 3 实现效果 4 设计原理 K210实现人脸识别 5 部分核心代码 6 最后 0 前言 这两年开始毕业设计和毕业答辩的要求和难度不断提升 传统的毕设题目缺少创新和亮点 往往达不到毕业答辩的要求 这
  • 基于主成分分析(PCA)的人脸识别算法

    本次实战的所有数据 文献 代码及输出均在以下百度云链接 链接 https pan baidu com s 1tR3VCyHxL7EZx su 3mxNg 提取码 siso 1 要求 实战的数据及要求如下 用到的数据库为XM2VTS中的前20
  • 基于百度PaddleHub实现人像美颜V1.0

    AI美颜核心技术之一就是人脸关键点检测 PaddleHub已经开源了人脸关键点检测模型 face landmark localization 人脸关键点检测是人脸识别和分析领域中的关键一步 它是诸如自动人脸识别 表情分析 三维人脸重建及三维
  • 疯壳AI语音及人脸识别教程2-5中断

    目录 1 1寄存器 1 1 2实验现象 4 视频地址 https fengke club GeekMart su f9cTSxNsp jsp 官方QQ群 457586268 中断 接口数据传送控制方式有查询 中断和DMA等 中断是重要的接口
  • 基于PaddleGAN项目人脸表情动作迁移学习(二)单人表情迁移

    学习目标 学习基于PaddleGAN实现的动作迁移模型 First order motion model First order motion model原理 First order motion model的任务是image animat
  • 【OpenCV图像处理入门学习教程六】基于Python的网络爬虫与OpenCV扩展库中的人脸识别算法比较

    OpenCV图像处理入门学习教程系列 上一篇第五篇 基于背景差分法的视频目标运动侦测 一 网络爬虫简介 Python3 网络爬虫 大家应该不陌生了 接下来援引一些Jack Cui在专栏 Python3网络爬虫入门 中的内容来帮助初学者理解
  • Face_Recognition 人脸识别函数详解

    加载人脸图片文件 load image file file mode RGB 通过 PIL image open 加载图片文件 mode 有两种模式 RGB 3通道 和 L 单通道 返回 numpy array 查找人脸位置 人脸分割 fa
  • 计算机视觉之人脸识别(Yale数据集)--HOG和ResNet两种方法实现

    1 问题描述 在给定Yale数据集上完成以下工作 在给定的人脸库中 通过算法完成人脸识别 算法需要做到能判断出测试的人脸是否属于给定的数据集 如果属于 需要判断出测试的人脸属于数据集中的哪一位 否则 需要声明测试的人脸不属于数据集 这是一个
  • 调用百度API实现人脸识别

    人脸识别 听着很高级 但实际上它确实很高级 不过对于我们开发人员来说 我们大部分人都是拿来主义 这次展示的是调用百度人脸识别API进行人脸信息分析 笔者试了下 发现还是挺准确的 而且代码量很少才8行 用的python 如果用java铁定不止
  • TensorFlow框架做实时人脸识别小项目(一)

    人脸识别是深度学习最有价值也是最成熟的的应用之一 在研究环境下 人脸识别已经赶上甚至超过了人工识别的精度 一般来说 一个完整的人脸识别项目会包括两大部分 人脸检测与人脸识别 下面就我近期自己练习写的一个 粗糙 的人脸识别小项目讲起 也算是做
  • 深度学习——图像增强 小组代码

    TJU暑期的深度学习训练营 这是人脸识别运用图像增强后的一段代码 import os shutil unzip tjudataset zip base dir tjudataset read data train dir os path j
  • 基于Pytorch版本的T2T-ViT+ArcFace的人脸识别训练及效果

    目录 一 前言 二 训练准备 1 T2T ViT的Pytorch版本 2 人脸识别数据和代码架构 3 完整训练代码 三 训练和结果 1 训练 2 结果 一 前言 最近 将transformer在CV领域中新出现的T2T ViT模型修改 再加
  • 机器学习系列(8):人脸识别基本原理及Python实现

    众所周知 人脸识别和人脸验证已经得到大量应用 那么它们之间有什么异同呢 又是如何实现的呢 这里是机器学习系列第八篇 带你揭开它们神秘的面纱 若图片挂了 可移步 https mp weixin qq com s biz MzU4NTY1NDM
  • 用MATLAB实现人脸识别

    1 人脸识别技术的细节 一般来说 人脸识别系统包括图像提取 人脸定位 图形预处理 以及人脸识别 身份确认或者身份查找 系统输入一般是一张或者一系列含有未确定身份的人脸图像 以及人脸数据库中的若干已知身份的人脸图像或者相应的编码 而其输出则是

随机推荐

  • 底量超顶量超级大黑马指标源码_底量超顶量超级大黑马指标源码

    主力买力度 LARGEINTRDVOL 100 VOL COLORRED 主力卖力度 LARGEOUTTRDVOL 100 VOL COLORGREEN 超B L2 VOL 0 0 VOL CAPITAL 大B L2 VOL 1 0 VOL
  • linux服务器高并发的极限和瓶颈

    最大并发数探究 Fancylee 2022 03 30 并发数 QPS 并发数 系统中同时存在的请求 同时处理中 QPS query per second 每秒的访问数 如何理解 将整个系统比喻成一个超市 QPS在超市门口测得的每秒钟有多少
  • U盘重装系统后可能遇到的问题

    一 重装系统 具体流程安装参考百度盘的使用优启通进行安装 安装完系统后可能会出现如下现象 一般台式机比笔记本简单 因为台式机不存在外围设备 例如触控板等 1 自己的优启通的万能驱动可能不具有相应的硬件驱动 在安装完系统后会提示 未找到相应的
  • 使用Hyperledger Composer将业务网络部署到单个组织的Hyperledger Fabric区块链上

    转载请标明出处 http blog csdn net qq 27818541 article details 78727076 本文出自 BigManing的博客 前言 先前准备 1先满足下列环境要求 2安装Hyperledger Comp
  • 让你的代码变的更加健壮(Making your C++ code robust)

    Making your C code robust Introduction 在实际的项目中 当项目的代码量不断增加的时候 你会发现越来越难管理和跟踪其各个组件 如其不善 很容易就引入BUG 因此 我们应该掌握一些能让我们程序更加健壮的方法
  • Unity AVPro Video使用和WebGL播放视频流

    1 创建Media Player对象 在Hierarchy视图右击 Video gt Media Player 或者选择菜单栏的GameObject菜单 然后选择 Video gt Media Player 2 创建Display uGui
  • C++工厂方法模式:Factory Method Pattern

    工厂 Factory 处理创建对象的细节 只负责创建对象 方便不同的对象需要时通过工厂来获取对象 简单工厂其实不是一种设计模式 反而比较像是一种编程习惯 但是由于经常被使用 有些开发人员把这个编程习惯误认为是 工厂模式 简单工厂模式 实例化
  • 【Java基础篇

    个人主页 兜里有颗棉花糖 欢迎 点赞 收藏 留言 加关注 本文由 兜里有颗棉花糖 原创 收录于专栏 JavaSE primary 本专栏旨在分享学习JavaSE的一点学习心得 欢迎大家在评论区讨论 目录 一 什么是多态 二 多态的实现条件
  • Maven settings.xml配置指定本地仓库

    1 本机新建一个文件夹当做本地仓库 我建的文件夹路径为 G Maven Mavenrepository ps 别命名为Maven repository 系统可能读不出来 2 在settings xml中添加localRepository标签
  • 前端布局 Flex(弹性)布局

    1 flex布局优点 操作方便 布局极为简单 移动端应用很广泛 pc端浏览器支持情况较差 IE11或者更低版本 不支持或仅部分支持 2 flex布局原理 flex意为 弹性布局 用来为盒状模型提供最大的灵活性 任何一个容器都可以指定为fle
  • Flutter实时动态UI刷新、数据交互

    setState 简介 setState 函数的作用是标记 StatefulWidget 中的 State 发生变化 需要重新构建 UI 即让Flutter架构自动实时刷新UI 当 StatefulWidget 的 State 发生变化时
  • 乔戈里推荐的新版Java学习路线,开源!

    Java 学习路线一条龙版 by 程序员鱼皮 所以我又抽空做了新版的 Java 学习路线一条龙 补充了很多内容 比如面试题 常用 Java 类库 常用软件等 让整个路线 字数翻倍 同时区分了各知识点的学习必要性 使得无论是急着找工作还是想花
  • vue3中setup语法糖下父子组件之间如何传递数据

    vue3中setup语法糖下父子组件之间如何传递数据 先弄明白什么是父子组件 父传子 子传父 组件间通信都有哪些方式 父子组件通信和兄弟组件通信的区别 先弄明白什么是父子组件 父子组件 分为父组件和子组件 Vue3中 父组件指的是包含一个或
  • 前端实现动态导航栏样式

  • 如何在电脑中安装虚拟机?

    第一步 先下载vmware软件 新手小白第一次下载软件会特别麻烦 自己也有可能在官网找不到相对应的软件 比如现在刚接触的我 内心无数的懵逼烦躁 还有很多很多的负面情绪 也懒得一个一个去摸索 下面是压缩包以及后续所需的激活码 加我百度网盘 别
  • 51单片机定时器/计数器T0

    选择方式0 方式1 方式2时 T0 T1的工作情况相同 选择方式3时 T0 T1的工作情况不同 方式0 13位定时器 计数器 TH0的高8位 TL0的低5位 方式1 16位定时器 计数器 TH0的高8位 TL0的低8位 方式2 自动重装的8
  • git出现error: invalid object for ‘xxxxx‘

    该问题说明git本地仓库 git objects里丢失了部分文件 执行git hash object w xxxxx 即可修复 xxxxx是invalid object for后面的文件路径
  • 向上管理(中高层核心能力的表现)

    向上管理 即在工作中为了让公司或上级以及自己取得更好的结果而下意识地配合上级一起工作的过程 在职业生涯中 向上管理其实也是工作能力的一部分 一项重要的职业技能 管理者不仅要做好向下管理 他们还要学会向上管理 1 向下管理 主要涉及的是管理者
  • 基于51单片机霍尔传感器测速(仿真+源程序)

    资料编号 196 下面是该资料仿真演示视频 196 基于51单片机霍尔传感器测速 仿真 源程序 全套资料 功能简介 51单片机计数测速转速测量 在仿真中等价于测量外部脉冲频率 如果修改输入脉冲的频率 在数码管上可实时显示当前频率 功能 霍尔
  • MTCNN人脸及特征点检测---代码应用详解(基于ncnn架构)

    本博记录为卤煮理解 如有疏漏 请指正 转载请注明出处 卤煮 非文艺小燕儿 本博地址 MTCNN人脸及特征点检测 代码应用详解 本文主要讲述当你拿到MTCNN的caffemodel后 如何使用它对一张图里的人脸进行检测和特征点标定 相当于一个