【NVCaffe源码分析】PriorBoxLayer

    因为是着重于源码分析,所以假定读者对于 PriorBox(也有叫 Anchor、DefaultBox)已有基本的认识,此文只是从代码的角度来补充一些细节。且看以下我自己在训练某任务时关于 PriorBoxLayer的一段设置,虽并未覆盖全部参数,但作为后面代码分析时的参考已然够用。

layer {
  name: "conv5_cc_relu_mbox_priorbox"
  type: "PriorBox"
  bottom: "conv5_cc"
  bottom: "data"
  top: "conv5_cc_relu_mbox_priorbox"
  prior_box_param {
    min_size: 8
    min_size: 10
    min_size: 15
    min_size: 21
    min_size: 29
    min_size: 37
    min_size: 47
    min_size: 57
    min_size: 68
    min_size: 80
    aspect_ratio: 4 #宽高比
    flip: false #默认为 true
    clip: false #是否进行越界处理
    variance: 0.1
    variance: 0.1
    variance: 0.2
    variance: 0.2
    offset: 0.5 #中心
  }
}

LayerSetUp

template <typename Ftype, typename Btype>
void PriorBoxLayer<Ftype, Btype>::LayerSetUp(const vector<Blob*>& bottom,
      const vector<Blob*>& top) {
  const PriorBoxParameter& prior_box_param =
      this->layer_param_.prior_box_param();
  CHECK_GT(prior_box_param.min_size_size(), 0) << "must provide min_size.";
  for (int i = 0; i < prior_box_param.min_size_size(); ++i) {
    min_sizes_.push_back(prior_box_param.min_size(i));
    CHECK_GT(min_sizes_.back(), 0) << "min_size must be positive.";
  }
  aspect_ratios_.clear();
  aspect_ratios_.push_back(1.); //1是默认比例,即正方形
  flip_ = prior_box_param.flip(); //是否取倒数(e.g. 1/3->flip->3)
  //存放aspect_ratio数据至aspect_ratios_
  for (int i = 0; i < prior_box_param.aspect_ratio_size(); ++i) {
    float ar = prior_box_param.aspect_ratio(i);
    bool already_exist = false;
    for (int j = 0; j < aspect_ratios_.size(); ++j) {
      if (fabs(ar - aspect_ratios_[j]) < 1e-6) {
        already_exist = true; //大概意思就是不要重复定义aspect_ratio
        break;
      }
    }
    if (!already_exist) {
      aspect_ratios_.push_back(ar);
      if (flip_) {
        //这个参数不要轻意用,ratio会翻一倍
        aspect_ratios_.push_back(1./ar); 
      }
    }
  }
  //prior box的数量,注意还需要加上max_sizes的数量才是最终数量
  num_priors_ = aspect_ratios_.size() * min_sizes_.size();
  if (prior_box_param.max_size_size() > 0) {
    CHECK_EQ(prior_box_param.min_size_size(), prior_box_param.max_size_size());
    //max_size当然是要大于min_size才算进来
    for (int i = 0; i < prior_box_param.max_size_size(); ++i) {
      max_sizes_.push_back(prior_box_param.max_size(i));
      CHECK_GT(max_sizes_[i], min_sizes_[i])
          << "max_size must be greater than min_size.";
      num_priors_ += 1;
    }
  }
  clip_ = prior_box_param.clip();
  //variance与后期真实框计算有关,要么给个1值,要么就给4个值
  if (prior_box_param.variance_size() > 1) {
    // Must and only provide 4 variance.(大于1那就必需给4个值)
    CHECK_EQ(prior_box_param.variance_size(), 4);
    for (int i = 0; i < prior_box_param.variance_size(); ++i) {
      CHECK_GT(prior_box_param.variance(i), 0);
      variance_.push_back(prior_box_param.variance(i));
    }
  } else if (prior_box_param.variance_size() == 1) {
    CHECK_GT(prior_box_param.variance(0), 0);
    variance_.push_back(prior_box_param.variance(0));
  } else {
    // Set default to 0.1.
    variance_.push_back(0.1);
  }

  //干啥?为啥要有个img_h,img_w,img_size参数,有何意义?
  if (prior_box_param.has_img_h() || prior_box_param.has_img_w()) {
    CHECK(!prior_box_param.has_img_size())
        << "Either img_size or img_h/img_w should be specified; not both.";
    img_h_ = prior_box_param.img_h();
    CHECK_GT(img_h_, 0) << "img_h should be larger than 0.";
    img_w_ = prior_box_param.img_w();
    CHECK_GT(img_w_, 0) << "img_w should be larger than 0.";
  } else if (prior_box_param.has_img_size()) {
    const int img_size = prior_box_param.img_size();
    CHECK_GT(img_size, 0) << "img_size should be larger than 0.";
    img_h_ = img_size;
    img_w_ = img_size;
  } else {
    img_h_ = 0;
    img_w_ = 0;
  }

  if (prior_box_param.has_step_h() || prior_box_param.has_step_w()) {
    CHECK(!prior_box_param.has_step())
        << "Either step or step_h/step_w should be specified; not both.";
    step_h_ = prior_box_param.step_h();
    CHECK_GT(step_h_, 0.) << "step_h should be larger than 0.";
    step_w_ = prior_box_param.step_w();
    CHECK_GT(step_w_, 0.) << "step_w should be larger than 0.";
  } else if (prior_box_param.has_step()) {
    const float step = prior_box_param.step();
    CHECK_GT(step, 0) << "step should be larger than 0.";
    step_h_ = step;
    step_w_ = step;
  } else {
    step_h_ = 0;
    step_w_ = 0;
  }

  offset_ = prior_box_param.offset();
}

这里有几个要注意的地方:

1. 代码中默认会设置一个 aspect_ratio=1,也就是正方形。在计算生成的 PriorBox 数量的时候不要忘记这个默认比例。

2.参数 flip 相当于将你自定义的所有 aspect_ratio 取倒数后加入到总的 aspect_ratio 中去,这样你定义的 aspect_ratio 相当于就翻了一倍了。这个参数默认是 TRUE,总感觉这个有点坑。毕竟 aspect_ratio 和 1/aspect_ratio 比例上完全不是一回事。

3.参数 clip 表示对于生成那些超出边界的 PriorBox 要不要做坐标截图,例如坐标小于则截断为0,大于图像的宽/高,则截断成图像的宽/高。

4. 在生成的 priorbox 的个数(num_priors_)是个有意思的地方,归结为两部分:1).aspect_ratio的数量(自定义的 aspect_ratio加上默认为1的 aspect_ratio) 乘以定义的 min_size的数量;2).如果还定义了 max_size,则要加入 max_size 的数量。

Reshape

template <typename Ftype, typename Btype>
void PriorBoxLayer<Ftype, Btype>::Reshape(const vector<Blob*>& bottom,
      const vector<Blob*>& top) {
  //取feature map的大小
  const int layer_width = bottom[0]->width();
  const int layer_height = bottom[0]->height();
  vector<int> top_shape(3, 1); //定义了一个初始容量为3,默认值为1的vector
  // Since all images in a batch has same height and width, we only need to
  // generate one set of priors which can be shared across all images.
  // 因为一个batch里面的所有图片的宽和高都一样,这里在生成anchor(实际上就是4个坐标值)
  // 的时候只要生成一组就够了!
  top_shape[0] = 1; //所以这里的第一维就只是1
  // 2 channels. First channel stores the mean of each prior coordinate.
  // Second channel stores the variance of each prior coordinate.
  top_shape[1] = 2; //
  top_shape[2] = layer_width * layer_height * num_priors_ * 4; //前面是anchor的数量,4是表示一个anchor要用4个坐标来表示
  CHECK_GT(top_shape[2], 0);
  top[0]->Reshape(top_shape);
}

这段代码用于定义PriorBox 层输出(top[0])的 shape,输出 shape 为(1,2,N)。top_shape[1]等于2,其中第一个通道用于存储 prior box 的坐标,第2个通道存储坐标对应的 variance 值。那这里的 N 是什么呢?它是 PriorBox 层要生成的总的 prior box 的坐标,所以它的计算公式为:layer_width * layer_height * num_priors_ * 4。

Forward

template <typename Ftype, typename Btype>
void PriorBoxLayer<Ftype, Btype>::Forward_cpu(const vector<Blob*>& bottom,
    const vector<Blob*>& top) {
  const int layer_width = bottom[0]->width();
  const int layer_height = bottom[0]->height();
  int img_width, img_height;
  //img_h 和 img_w 可以不用给定,直接从 data 里面取
  if (img_h_ == 0 || img_w_ == 0) {
    //bottom[1]是data即训练数据
    img_width = bottom[1]->width();
    img_height = bottom[1]->height();
  } else {
    img_width = img_w_;
    img_height = img_h_;
  }
  float step_w, step_h; 
  if (step_w_ == 0 || step_h_ == 0) {
    //网络输入图像相对于featuremap的缩放比例
    step_w = static_cast<float>(img_width) / layer_width; 
    step_h = static_cast<float>(img_height) / layer_height;
  } else {
    step_w = step_w_;
    step_h = step_h_;
  }
  Ftype* top_data = top[0]->mutable_cpu_data<Dtype>();
  int dim = layer_height * layer_width * num_priors_ * 4; //
  int idx = 0;
  for (int h = 0; h < layer_height; ++h) {
    for (int w = 0; w < layer_width; ++w) {
      //offset默认值是0.5,因为要寻求的是中心点
      //featuremap上每个点都映射回原图中对应感受野的一个中心点
      //所以,这里 center_x,center_y都是像素坐标
      float center_x = (w + offset_) * step_w;
      float center_y = (h + offset_) * step_h;
      float box_width, box_height;
      for (int s = 0; s < min_sizes_.size(); ++s) {
        //在prototxt中设的min_size参数都是像素级的大小
        int min_size_ = min_sizes_[s];
        //首先计算默认比例为1的anchor,你如果什么其它aspect_ratio参数都不设置的话,
        //默认会有一个aspect_ratio为1,也就是出正方形的anchor
        // first prior: aspect_ratio = 1, size = min_size
        box_width = box_height = min_size_;
        //坐标按原图的height,width进行归一化
        //这里相当于是aspect_ratio为1的情形,作者默认就加入了!
        // xmin
        top_data[idx++] = (center_x - box_width / 2.) / img_width;
        // ymin
        top_data[idx++] = (center_y - box_height / 2.) / img_height;
        // xmax
        top_data[idx++] = (center_x + box_width / 2.) / img_width;
        // ymax
        top_data[idx++] = (center_y + box_height / 2.) / img_height;

        //如果定义了 max_size则与 min_size是一一对应
        if (max_sizes_.size() > 0) {
          CHECK_EQ(min_sizes_.size(), max_sizes_.size()); //必须相等
          int max_size_ = max_sizes_[s]; 
          // second prior: aspect_ratio = 1, size = sqrt(min_size * max_size)
          // 正方形最小边是min_size,但是即使设定了max_size,这可不是说最长边就是max_size
          //哟,而是sqrt(min_size * max_size)
          //这里还是相对于 aspectio 为1的情形....
          box_width = box_height = sqrt(min_size_ * max_size_);
          // xmin
          top_data[idx++] = (center_x - box_width / 2.) / img_width;
          // ymin
          top_data[idx++] = (center_y - box_height / 2.) / img_height;
          // xmax
          top_data[idx++] = (center_x + box_width / 2.) / img_width;
          // ymax
          top_data[idx++] = (center_y + box_height / 2.) / img_height;
        }
        //注意,上面两步min_sizes和max_sizes求出来的都是正方形的框,
        //因为目前为止还没有用到aspect_ratio,这正是下面要做的生成非正方形的框
        //min_size是设定的一个正方形的边长大小,ar是w/h的值,具体到计算anchor的h,w
        //的时候要根据简单的面积公式来求解.
        // rest of priors
        for (int r = 0; r < aspect_ratios_.size(); ++r) {
          float ar = aspect_ratios_[r];
          //aspect_ratio 为1的情形已经计算过了,所以要 skip
          if (fabs(ar - 1.) < 1e-6) {
            continue;
          }
          box_width = min_size_ * sqrt(ar);
          box_height = min_size_ / sqrt(ar);
          // xmin
          top_data[idx++] = (center_x - box_width / 2.) / img_width;
          // ymin
          top_data[idx++] = (center_y - box_height / 2.) / img_height;
          // xmax
          top_data[idx++] = (center_x + box_width / 2.) / img_width;
          // ymax
          top_data[idx++] = (center_y + box_height / 2.) / img_height;
        }
      }
    }
  }
  // clip表示是否进行越界处理,因为按照默认逻辑,边界附近必然有些框的边框会超出图像自身的大小
  // clip the prior's coordidate such that it is within [0, 1]
  if (clip_) {
    for (int d = 0; d < dim; ++d) {
      //如果top_data[d]小于0,则std::max<Dtype>(top_data[d], 0.)为0,
      //所以std::min<Dtype>(0,1)为0,哈哈,小于0则clip成0,就是超出边界的
      //坐标值直接设定成边界值
      top_data[d] = std::min<Dtype>(std::max<Dtype>(top_data[d], 0.), 1.);
    }
  }
  // set the variance.
  // 将每个点生成的坐标值加上variance的值
  // 调用了blob的offset函数,计算出第2个channel初始位置的偏移量,使后面使用
  // top_data[0]就可以设置variance_
  /*
  参数reshape函数来看,top的shape是1x2xN
  First channel stores the mean of each prior coordinate. 为什么叫mean?
  Second channel stores the variance of each prior coordinate.
  */
  top_data += top[0]->offset(0, 1);
  if (variance_.size() == 1) {
    caffe_set(dim, Dtype(variance_[0]), top_data);
  } else {
    int count = 0;
    // feature map每个位置上都有4个variance值,当然,对于每个位置来说是
    // 一组同样的值,它们在prototxt文件中给定
    for (int h = 0; h < layer_height; ++h) {
      for (int w = 0; w < layer_width; ++w) {
        for (int i = 0; i < num_priors_; ++i) {
          for (int j = 0; j < 4; ++j) {
            top_data[count] = variance_[j];
            ++count;
          }
        }
      }
    }
  }
}

参考文献:

https://zhuanlan.zhihu.com/p/33544892

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值