这篇博客主要写prior_box_layer 
这一层完成的是给定一系列feature map后如何在上面生成prior box。SSD的做法很有意思,对于输入大小是W×H的feature map,生成的prior box中心就是W×H个,均匀分布在整张图上,像下图中演示的一样。在每个中心上,可以生成多个不同长宽比的prior box,如[1/3, 1/2, 1, 2, 3]。所以在一个feature map上可以生成的prior box总数是W×H×length_of_aspect_ratio,对于比较大的feature map,如VGG的conv4_3,生成的prior box可以达到数千个。当然对于边界上的box,还要做一些处理保证其不超出图片范围,这都是细节了。

这里需要注意的是,虽然prior box的位置是在W×H的格子上,但prior box的大小并不是跟格子一样大,而是人工指定的,原论文中随着feature map从底层到高层,prior box的大小在0.2到0.9之间均匀变化。

一开始看SSD的时候很困扰我的一点就是形状的匹配问题:SSD用卷积层做bbox的拟合,输出的不应该是feature map吗,怎么能正好输出4个坐标呢?这里的做法有点暴力,比如需要输出W×H×length_of_aspect_ratio×4个坐标,就直接用length_of_aspect_ratio×4个channel的卷积层做拟合,这样就得到length_of_aspect_ratio×4个大小为W×H的feature map,然后把feature map拉成一个长度为W×H×length_of_aspect_ratio×4的向量,用SmoothL1之类的loss去拟合,效果还意外地不错……

代码解读:

#include <algorithm>
#include <functional>
#include <utility>
#include <vector>#include "caffe/layers/prior_box_layer.hpp"namespace caffe {template <typename Dtype>
void PriorBoxLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,    // 参数解析const vector<Blob<Dtype>*>& 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_size_size()=1min_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,在ProtoTXT只设置了2,3或者2flip_ = prior_box_param.flip();  // 默认truefor (int i = 0; i < prior_box_param.aspect_ratio_size(); ++i) {    // aspect_ratio_size=2float ar = prior_box_param.aspect_ratio(i);bool already_exist = false;for (int j = 0; j < aspect_ratios_.size(); ++j) {  // 这里判断是不是已近把ratio压入栈,保证每个ratios都只有一个1/ratiosif (fabs(ar - aspect_ratios_[j]) < 1e-6) {    // 这里aspect_ratios_只有1一个值already_exist = true;break;   // 跳出for循环}}if (!already_exist) {aspect_ratios_.push_back(ar);if (flip_) {     //  翻转,改变长宽比aspect_ratios_.push_back(1./ar);  // 得到1,2,3,1/2,1/3}}    // 到这里,共有5个ratios,分别为1,2,1/2,3,1/3}num_priors_ = aspect_ratios_.size() * min_sizes_.size();  // min_sizes_.size()=1   5*1if (prior_box_param.max_size_size() > 0) {CHECK_EQ(prior_box_param.min_size_size(), prior_box_param.max_size_size());  // 最大和最小不能相等for (int i = 0; i < prior_box_param.max_size_size(); ++i) {  // max_size_size=1max_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;    // num_priors_ = 6;这里很重要,不然就只有5个,和论文中的6个就不相符了}}clip_ = prior_box_param.clip();           // true 默认falseif (prior_box_param.variance_size() > 1) {   // variance_size = 4// Must and only provide 4 variance.CHECK_EQ(prior_box_param.variance_size(), 4);   // 必须有4个variancefor (int i = 0; i < prior_box_param.variance_size(); ++i) {   // variance:0.1 0.1 0.2 0.2CHECK_GT(prior_box_param.variance(i), 0);variance_.push_back(prior_box_param.variance(i));}} else if (prior_box_param.variance_size() == 1) {   // 或者只设置一个,设为0.1CHECK_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);}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()) {  // step,tesp_h,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();   // 偏移量,默认0.5
}  // layersetup 结束template <typename Dtype>
void PriorBoxLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,const vector<Blob<Dtype>*>& top) {const int layer_width = bottom[0]->width();    // 输入feature map的大小const int layer_height = bottom[0]->height();vector<int> top_shape(3, 1);// 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.top_shape[0] = 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;  // 输出坐标,就是需要这么多个map,类似faster rcnn,注意:这里,如果没有在ptototxt中没有设置max_size,num_priors_的值就要减1CHECK_GT(top_shape[2], 0);top[0]->Reshape(top_shape);// 在mbox_priorbox层中,concat是选的axis: 2,就是说是concat的map数。
}template <typename Dtype>
void PriorBoxLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,const vector<Blob<Dtype>*>& top) {const int layer_width = bottom[0]->width();    // 上一层feature mapconst int layer_height = bottom[0]->height();int img_width, img_height;if (img_h_ == 0 || img_w_ == 0) {img_width = bottom[1]->width();             // data layer出来的结果,原始图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) {   // 得到缩放比例,相当于faster的feat_stride,这里处理的稍好些,长宽都有相应参数step_w = static_cast<float>(img_width) / layer_width;  // 这里都用的float,不像faster直接暴力int型step_h = static_cast<float>(img_height) / layer_height;} else {step_w = step_w_;step_h = step_h_;}Dtype* top_data = top[0]->mutable_cpu_data();int dim = layer_height * layer_width * num_priors_ * 4;  // 一般情况下w*h*6*4,conv4_3除外,详细参考笔记上的框架图int idx = 0;for (int h = 0; h < layer_height; ++h) {   // 对于feature map上的每个点逐一映射for (int w = 0; w < layer_width; ++w) {// 这里和Faster RCNN 一样,就是把feature map上的点映射回原图,这里加上0.5也是为了四舍五入,和faster rcnn python代码类似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) {  // min_sizes_.size()=1int min_size_ = min_sizes_[s]; // 这里的min_size从fc7_mbox_priorbox的60到最后的276,就是s_k从0.2到0.92的过程// first prior: aspect_ratio = 1, size = min_sizebox_width = box_height = min_size_;  // xmintop_data[idx++] = (center_x - box_width / 2.) / img_width;    // // ymintop_data[idx++] = (center_y - box_height / 2.) / img_height;// xmaxtop_data[idx++] = (center_x + box_width / 2.) / img_width;// ymaxtop_data[idx++] = (center_y + box_height / 2.) / img_height;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)  // 这里就和论文中一致,s_k的选法,每个都不同box_width = box_height = sqrt(min_size_ * max_size_);// xmintop_data[idx++] = (center_x - box_width / 2.) / img_width;// ymintop_data[idx++] = (center_y - box_height / 2.) / img_height;// xmaxtop_data[idx++] = (center_x + box_width / 2.) / img_width;// ymaxtop_data[idx++] = (center_y + box_height / 2.) / img_height;}// rest of priorsfor (int r = 0; r < aspect_ratios_.size(); ++r) {  // 其他几个比例计算float ar = aspect_ratios_[r];if (fabs(ar - 1.) < 1e-6) {continue;}box_width = min_size_ * sqrt(ar);box_height = min_size_ / sqrt(ar);// xmintop_data[idx++] = (center_x - box_width / 2.) / img_width;// ymintop_data[idx++] = (center_y - box_height / 2.) / img_height;// xmaxtop_data[idx++] = (center_x + box_width / 2.) / img_width;// ymaxtop_data[idx++] = (center_y + box_height / 2.) / img_height;}}  // end for min_size=1}  // end for w}  // end for h// 到这里,所有的prior_box选取完成,共6个比例,和论文中相符合,同时在每一层中算一个s_k,就是每一层都会设置一个min_size// clip the prior's coordidate such that it is within [0, 1]if (clip_) {                        // 裁剪到[0,1]for (int d = 0; d < dim; ++d) {top_data[d] = std::min<Dtype>(std::max<Dtype>(top_data[d], 0.), 1.);}}// set the variance.// 解答: https://github.com/weiliu89/caffe/issues/75// 除以variance是对预测box和真实box的误差进行放大,从而增加loss,增大梯度,加快收敛。// 另外,top_data += top[0]->offset(0, 1);已经使指针指向新的地址,所以variance不会覆盖前面的结果。// offse一般都是4个参数的offset(n,c,w,h),设置相应的参数就可以指到下一张图(以四位张量为例)top_data += top[0]->offset(0, 1); // 这里我猜是指向了下一个chanelif (variance_.size() == 1) {caffe_set<Dtype>(dim, Dtype(variance_[0]), top_data);// 用常数variance_[0]对top_data进行初始化} else {int count = 0;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;}}}}}
}INSTANTIATE_CLASS(PriorBoxLayer);
REGISTER_LAYER_CLASS(PriorBox);}  // namespace caffe

目标检测:SSD目标检测中PriorBox代码解读相关推荐

  1. linux kernel中cache代码解读

    1. 在kernel中调用__dma_flush_range,底层是如何操作的呢? /* remove any dirty cache lines on the kernel alias */__dm ...

  2. 目标检测 SSD: Single Shot MultiBox Detector - SSD在MMDetection中的实现

    目标检测 SSD: Single Shot MultiBox Detector - SSD在MMDetection中的实现 flyfish 目标检测 SSD: Single Shot MultiBox ...

  3. Pytorch搭建SSD目标检测平台

    学习前言 什么是SSD目标检测算法 源码下载 SSD实现思路 一.预测部分 1.主干网络介绍 2.从特征获取预测结果 3.预测结果的解码 4.在原图上进行绘制 二.训练部分 1.真实框的处理 2.利用 ...

  4. 目标检测-SSD算法详细总结

    文章与视频资源多平台更新 微信公众号|知乎|B站|头条:AI研习图书馆 深度学习.大数据.IT编程知识与资源分享,欢迎关注,共同进步~ 一. 引言 文章:SSD: Single Shot MultiB ...

  5. 说说早期目标检测-----------ssd那些事

    说说早期目标检测---------ssd那些事 前言 论文地址 github ssd的优点 学习前言 a[::-1] ssd---anchor SSD代码讲解 1.预测部分 获得预测结果 预测框解码 ...

  6. 再读目标检测--ssd深度解析

    [目标检测 – R-CNN,Fast R-CNN,Faster R-CNN] https://www.cnblogs.com/yanghailin/p/14767995.html [目标检测-SSD] ...

  7. 目标检测 | 盘点目标检测中的特征融合技巧(根据YOLO v4总结)

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 特征融合分类 在深度学习的很多工作中(例如目标检测.图像分割),融 ...

  8. 【目标检测系列】CNN中的目标多尺度处理方法

    关注上方"深度学习技术前沿",选择"星标公众号", 技术干货,第一时间送达! [导读]本篇博文我们一起来讨论总结一下目标检测任务中用来处理目标多尺度的一些算法. ...

  9. 检测到目标服务器启用了trace方法_综述:目标检测中的多尺度检测方法

    ↑ 点击蓝字 关注极市平台作者丨SFXiang来源丨AI算法修炼营编辑丨极市平台 极市导读 本文从降低下采样率与空洞卷积.多尺度训练.优化Anchor尺寸设计.深层和浅层特征融合等多个方面入手,对目标 ...

  10. Mxnet (33): 多盒目标检测(SSD)检测香蕉

    1. 香蕉检测数据集 对象检测没有像MNIST或Fashion-MNIST这样的小型数据集.为了快速测试模型,可以自己组装数据集.首先使用香蕉生成1000个角度和大小不同的香蕉图像.然后收集一些背景图 ...

最新文章

  1. 数据分析利器Jupyter Notebook!
  2. IntelliLock托管代码保护和许可授权管理系统软件详细介绍及下载
  3. [排错]运行cocos2d自带的cocos2d-test-ios工程出现错误:找不到libcocos2d.a
  4. 拼接字符串的优雅方式
  5. vue 简介 vue 项目
  6. java泛型 算法_非常具体的Java泛型问题 – 如何返回传递给方法的相同类型?
  7. Joe一款个人博客typecho主题(扩展版)
  8. JS获取页面中Url的某个参数
  9. 80后的十三种最深寂寞
  10. JSTL和EL的使用
  11. MySQL卸载不干净问题
  12. python批量新建文件夹_python批量创建文件夹
  13. 浅谈 - 技术人员为什么更喜欢进行人身攻击?
  14. 最新的 CocoaPods 的使用教程 上传podspec
  15. php 过滤字符 b,php过滤所有中英文标点符号
  16. element ui中table合并相同内容单元格
  17. 市场营销行业拓客的10个经典方法
  18. Three.js加载.obj和.mtl文件(无法加载材质、路径错误问题)
  19. amd锐龙笔记本cpu怎么样_AMD锐龙R5怎么样 AMD锐龙R5配置参数
  20. CASIA数据集格式转化代码

热门文章

  1. python爬虫菜鸟驿站_爬30层楼、扛40斤……5万菜鸟驿站春节快递不打烊
  2. 安装tcpreplay时报错:configure: error: libdnet not found
  3. c语言c11标准 下载,【整理】C语言的各种版本:C89,AMD1,C99,C11
  4. 磁共振成像(MRI)影像心脏组织分割
  5. matlab sa函数的傅里叶变换,通信第三章常见函数的傅里叶变换.ppt
  6. 投影仪是计算机的基本配置吗,投影仪如何设置
  7. 机械制图计算机识图,机械制图基础知识
  8. ipv6 dns服务器修改,ipv6服务器dns怎么设置
  9. 网吧无盘服务器进u盘启动,利用U盘启动在网吧免费上网
  10. 计算机考试67,注册电气工程师基础考试计算机基础知识试题答案(67)