MTCNN解读

1. 整体流程

  1. 将图像按照特定的比例resize成多个尺度下的图像

  2. P-Net(Proposal Net)

    [图片上传失败...(image-725dd8-1597038245948)]

    • 对于步骤1中的每一个尺度的图像都输入P-Net,输出一个降采样一倍的网格,网格中带有每个位置可能存在的bounding box proposal,包括是否有人脸和位置回归信息。原论文中还会输出关键点的proposal,但是在后续的实现中都将这一部分放在最后一个Net中实现。

    • 以原始图片为200x400为例,首先由缩放因子0.5缩放至输入图片为100x200,经过PNet之后输出网格大小为50x100,网格中每一个cell会输出该点位置对应的是否有人脸(onehot*2),以及该cell对应的回归框的偏移

    • 这里的偏移是相对于网格坐标映射到原图(200x400)上的偏移,每一个cell会自带一个框的尺度,这个尺度和图片的缩放尺度相关,比如设置成12/缩放因子。

    • 从偏移到原图框坐标的代码如下:

      <pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c++" cid="n118" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;"> void MTCNN::generateBbox(cv::Mat score, cv::Mat location, std::vector<Bbox>& boundingBox_, float scale)
      {
      const int stride = 2; // 表示PNet对输入图片的降采样
      const int cellsize = 12; // 预设的框尺度大小

      int sc_rows, sc_cols;
      if ( 4 == score.dims)
      {
      sc_rows = score.size[2]; // 网格行数
      sc_cols = score.size[3]; // 网格列数
      }

      float* p = (float *)score.data + sc_rows * sc_cols;
      float inv_scale = 1.0f / scale;
      for(int row = 0; row < sc_rows; row++)
      {
      for(int col = 0; col < sc_cols; col++)
      {
      Bbox bbox;
      if( *p > threshold[0] )
      {
      bbox.score = p;
      // 下面四行可以看作是anchor box
      bbox.x1 = round((stride * col + 1) * inv_scale);
      bbox.y1 = round((stride * row + 1) * inv_scale);
      bbox.x2 = round((stride * col + 1 + cellsize) * inv_scale);
      bbox.y2 = round((stride * row + 1 + cellsize) * inv_scale);
      const int index = row * sc_cols + col;
      for(int channel = 0;channel < 4; channel++)
      {
      float
      tmp = (float *)(location.data) + channel * sc_rows * sc_cols;
      bbox.regreCoord[channel] = tmp[index]; // anchor + offset
      }
      boundingBox_.push_back(bbox);
      }
      p++;
      }
      }

      return;
      }</pre>

    • 通过边界框回归对所有的BBox的坐标进行refine。集合得到第一阶段的Proposals。refine代码如下:

      <pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="cpp" cid="n123" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;"> void MTCNN::refine(std::vector<Bbox>& vecBbox, const int& height, const int& width, bool square)
      {
      if (vecBbox.empty())return;

      float bbw = 0, bbh = 0, max_side = 0;
      float h = 0, w = 0;
      float x1 = 0, x2 = 0, y1 = 0, y2 = 0;

      for (auto it = vecBbox.begin(); it != vecBbox.end(); it++)
      {
      bbw = it->x2 - it->x1 + 1;
      bbh = it->y2 - it->y1 + 1;

      x1 = it->x1 + bbw * it->regreCoord[1];
      y1 = it->y1 + bbh * it->regreCoord[0];
      x2 = it->x2 + bbw * it->regreCoord[3];
      y2 = it->y2 + bbh * it->regreCoord[2];

      if(square)
      {
      w = x2 - x1 + 1;
      h = y2 - y1 + 1;
      int maxSide = ( h > w ) ? h:w;
      x1 = x1 + w * 0.5 - maxSide * 0.5;
      y1 = y1 + h * 0.5 - maxSide * 0.5;
      x2 = round(x1 + maxSide - 1);
      y2 = round(y1 + maxSide - 1);
      x1 = round(x1);
      y1 = round(y1);
      }

      it->x1 = x1 < 0 ? 0 : x1;
      it->y1 = y1 < 0 ? 0 : y1;
      it->x2 = x2 >= width ? width - 1 : x2;
      it->y2 = y2 >= height ? height - 1 : y2;
      }
      }</pre>

    • 整体流程:

      <pre mdtype="fences" cid="n156" lang="cpp" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;"> void MTCNN::detectInternal(cv::Mat& img_, std::vector<Bbox>& finalBbox_)
      {
      const float nms_threshold[3] = {0.7f, 0.7f, 0.7f};

      img = img_;
      PNet();
      if ( !firstBbox_.empty())
      {
      nms(firstBbox_, nms_threshold[0]);
      refine(firstBbox_, img_.rows, img_.cols, true);

      RNet();
      if( !secondBbox_.empty())
      {
      nms(secondBbox_, nms_threshold[1]);
      refine(secondBbox_, img_.rows, img_.cols, true);

      ONet();
      if ( !thirdBbox_.empty())
      {
      refine(thirdBbox_, img_.rows, img_.cols, false);

      std::string ts = "Min";
      nms(thirdBbox_, nms_threshold[2], ts);
      }
      }
      }
      finalBbox_ = thirdBbox_;
      thirdBbox_.clear();
      }</pre>

    • O-Net(output net)

      [图片上传失败...(image-1246b1-1597038245942)]

      • 对于3中得到的粗BBox,再输入网络获得一个refine过的人脸分类、边界框回归和关键点坐标。通过该网络结果对bbox进行refine,再通过最终的NMS得到最终结果。
    • R-Net(Refine Net)

      [图片上传失败...(image-4aef89-1597038245942)]

      • 对于2中得到的每一个proposal,从原图中按照bbox将图像抠出来并resize成固定大小输入R-Net,输出人脸分类、边界框回归和5个关键点坐标。

      • 这里有一个疑惑,输入图片是一个patch,而边界框的坐标信息是一个全图的全局信息,这是怎么回归出来的呢?

      • NMS之后,通过该网络结果对bbox进行refine。

    • 将每个尺度下得到bounding boxes分别进行NMS之后,再对所有尺度下的结果进行NMS

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,295评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,928评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,682评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,209评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,237评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,965评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,586评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,487评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,016评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,136评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,271评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,948评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,619评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,139评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,252评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,598评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,267评论 2 358