mtcnn人脸检测demo wuhao2019-11-06 09:41:49 回复 1 查看 资源共享 经验交流
mtcnn人脸检测demo

1.前言

MTCNN,Multi-task convolutional neural network(多任务卷积神经网络),在网络推理过程同时进行人脸边框检测与人脸关键点检测.该模型用到了图像金字塔、边框回归、非最大值抑制等技术.

网络模型采用三级网络级联的结构,三级网络分别为P-Net、R-Net、和O-Net三层网络结构.P-Net、R-Net、和O-Net三层网络各自的作用如下:

  1. P-Net快速生成候选窗口.

  2. R-Net高精度候选窗口过滤.

  3. O-Net生成最终边界框与人脸关键点.

 

 

2.pipeline结构

pipeline.png

 

DataSource : 1

 对输入图片/视频解码


Inferencer : 2.1~2.10 

Inferencer插件执行Pnet网络模型推理.Pnet网络的输入为下采样为不同size的图片,输出为socre特征图和boundingBox的回归系数(dx1,dy1,dx2,dy2)特征图.

  1. 上图为什么在datasource插件后设置10个并行的inferencer插件?

    由于P-Net的输出的特征图中,每个像素点的感受野固定为12x12,为了检测到各种size的人脸,mtcnn使用图像金字塔技术,以scale=0.709(面积比例接近1/2)将输入图像下采样,以产生不同size的人脸.而cambricon离线模型暂时只支持固定size的输入,因此需要手工将模型分别转换为不同size的cambricon离线模型.每个离线模型输入层size对应金字塔下采样的size.上图中,每个inferencer插件对1个size的P-Net进行推理.


  2. 10个inferencer插件输出结果的汇总.

    DataSource插件将同1帧数据发送给10个并行的inferencer插件,每个inference插件将输入的frame resize到其网络要求的size,并进行推理,在网络后处理中将推理结果保存到输入的frame对象中.因为frame对象为临界资源,因此在对frame对象的objs进行写操作时,要加上线程互斥锁.

     

  3. 10个并行的inference的输出frame怎样和下游插件同步?

      10个并行的inference将向下游插件inferenceEx总共发送10次frame对象.cnstream中使用掩码机制处理插件间的同步,在插件进行连接时,会保存该插件的所有上游和下游插件.下游插件只会对已经被其所有上游插件处理标记后的frame进行处理.因此,上图中的inferenceEx插件会等到10个inferencer插件处理完后,只对最后1个inferencer插件输出的frame进行处理.

 

InferenceEx : 3 ~ 4 

InferenceEx插件为自定义插件,主要在inferencer插件的基础上添加了对同1帧数据多次推理的功能.其中3对应R-Net,4对应O-Net.

InferenceEx插件的数据处理函数处理步骤如下:

  1. 对frame中保存的推理结果中的boudingbox进行非极大值抑制

  2. 对剩下的boudingbox进行边框回归

  3. 遍历所有boudingbox,根据boudingbox截取图像,送入到网络模型进行推理,将推理结果保存在frame对象.


Osd : 5

 根据frame对象中保存的结果,绘制人脸边框和关键点.


Encode : 6

将frame对象编码.

 

 

3.网络预处理和后处理步骤

P-Net ,R-Net,O-Net的预处理相同,其步骤如下:

  //将输入图片颜色空间转换为BGR
  package->objs_mutex.lock();
  cv::Mat img = *package->frame.ImageBGR();
  package->objs_mutex.unlock();
 
  // 根据网络输入size,将图片进行resize.
  int dst_w = input_shapes[0].w();
  int dst_h = input_shapes[0].h();
  cv::Mat outImg;
  cv::resize(img, outImg, cv::Size(dst_w, dst_h));
 
 //.将dataType转换为float32,并规范化.
  cv::Mat dst(dst_h, dst_w, CV_32FC3, net_inputs[0]);
  outImg.convertTo(dst, CV_32F, VAR, - MEAN * VAR)



P-Net后处理:

因为P-Net为全卷积网络,输出为特征图,需要从P-Net的输出中还原出boundingBox的坐标信息.主要代码如下:

 //遍历输出特征图
 for(int y = 0; y < h; ++y)
        for(int x = 0; x < w; ++x) {
            float tempScore = scores[(y * w + x) * 2 + 1];   
            //根据置信度筛选可能为人脸的区域.
            if(tempScore > threshold) {
                auto tempBox = std::make_shared<BoundingBox>(); 
                //根据特征值的坐标信息计算人脸区域的坐标.
                tempBox->x1 = (stride * x + 1) / scale;  
                tempBox->y1 = (stride * y + 1) / scale;
                tempBox->x2 = (stride * x + cellSize) / scale;
                tempBox->y2 = (stride * y + cellSize) / scale;
                boxWidth = tempBox->x2 - tempBox->x1;
                boxHeight = tempBox->y2 - tempBox->y1;
                dx1 = regs[(y * w + x) * 4 + 0];
                dy1 = regs[(y * w + x) * 4 + 1];
                dx2 = regs[(y * w + x) * 4 + 2];
                dy2 = regs[(y * w + x) * 4 + 3];
                //修正boundingbox
                tempBox->dx1 = floor(boxWidth * dx1);
                tempBox->dy1 = floor(boxHeight * dy1);
                tempBox->dx2 = floor(boxWidth * dx2);
                tempBox->dy2 = floor(boxHeight * dy2);
                tempBox->score = tempScore;
                //保存结果
                outBox.push_back(tempBox);


R-Net和O-Nnet后处理

R-Net和O-Net网络的后处理结果类似,主要代码如下:

      //保存结果
      auto getBox = std::make_shared<mtcnn::BoundingBox>();    
      float w = package->currentBox.x2 - package->currentBox.x1;
      float h = package->currentBox.y2 - package->currentBox.y1;
      getBox->dx1 = regsOutput[0] * w;
      getBox->dy1 = regsOutput[1] * h;
      getBox->dx2 = regsOutput[2] * w;
      getBox->dy2 = regsOutput[3] * h;
      getBox->x1 = package->currentBox.x1;
      getBox->y1 = package->currentBox.y1;
      getBox->x2 = package->currentBox.x2;
      getBox->y2 = package->currentBox.y2;
      getBox->score = tempScore;
      package->mtcnnObjs.push_back(getBox);



4.demo复现

下载源代码

1

git clone git@172.10.80.76:video/cnstream.git

切换分支

1

git checkout mtcnn_wuhao

进入代码目录

1

cd cnstream

编译代码

1

2

3

4

mkdir build

cd build

cmake ..

make -j8

 运行代码

1

cd cnstream/samples/mtcnn

./run.sh


5.运行结果:

可以看到结果还是比较精确.

mtcnnResult.png


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


官方微博 官方微信
版权所有 © 2019 寒武纪 Cambricon 备案/许可证号:京ICP备17003415
关闭