【转】SSD的caffe源码解读 -- 数据增强

转自:https://blog.csdn.net/daniaokuye/article/details/78565817

SSD 的数据增强对ssd网络识别小物体效果明显(原文Fig6),而且他使用的方法有点特别,所以在此解析一下他的源码。python代码

补充一下data augment翻译:叫“数据增广”更好,中科院自动化所的师兄的翻译更准确

(一) ssd_pascal.py

/examples/ssd/ssd_pascal.py
在此源码中有几个点是涉及到数据预处理的,在此列举如下:

#第93行,变量batch_sampler
93 batch_sampler = [……
  • 1
  • 2

这是原文中所提到的(小节2.2)数据增强所用到的jaccard overlap和其他两个策略,在代码中的参数设置。

#第179行,train_transform_param
train_transform_param = {……
#第216行,test_transform_param
  • 1
  • 2
  • 3

这儿设置了一些图像变换的方法,比如resize转换大小,distort颜色变化,还有后面两个不太明白,先及下来。

//TODO:expand,emit
  • 1

这些参数都去哪儿了呢,在后面创建*.prototxt的时候用到了

#第435行,CreateAnnotatedDataLayer()函数
net.data, net.label = CreateAnnotatedDataLayer(
#此函数在/python/caffe/model_libs.py中。
#在第208行,batch_sampler被赋值给了annotated_data_param
#第196/201以及214,transform_param被赋值给了transform_param
  • 1
  • 2
  • 3
  • 4
  • 5

(二)train.prototxt

然后去找所创建的prototxt文件,在jobs/VGGNet/Voc0712/ssd_300/train.prototxt中,可以找到字典transform_param和annotated_data_param。
然后根据这两个参数去找调用他们的cpp代码

//对于batch_sampler的解释:(train.prototxt)
batch_sampler {
      sampler {
        min_scale: 0.3  #scale是patch随机框和原图的面积比
        max_scale: 1.0  
        min_aspect_ratio: 0.5 #长宽比
        max_aspect_ratio: 2.0
      }
      sample_constraint {
        min_jaccard_overlap: 0.3    #随机框和原ground truth的jaccard overlap
      }
      max_sample: 1
      max_trials: 50 #迭代次数
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

(三)annotated_data_layer.hpp/.cpp

1)transform

a.) 灰度/颜色扭曲DistortImage()函数(在第157行)

this->data_transformer_->DistortImage(anno_datum.datum(),
                                            distort_datum.mutable_datum());
  • 1
  • 2

b.) ExpandImage()(在第161/168行)

this->data_transformer_->ExpandImage(distort_datum, expand_datum);
  • 1

这两个函数都是调用了this指针,在该annotated.hpp头文件中可以看到没有这两个函数,但她继承了BasePrefetchingDataLayer文件,在里面可以找到data_transformer_是类DataTransformer的指针,至此可以找到DistortImage了,同样的ExpandImage。

这两个函数的功能
1. ExpandImage是缩小图片,达到zoom out的效果:先做一个比原图大的画布,然后随机找一个放原图的位置将原图镶嵌进去,像天安门上挂了一个画像。这在SSD原文中提到的zoom out缩小16倍(4×4,3.6节)来获得小对象的方法。
而放大图像必然会导致需要填充一些数据,原文(SSD,3.6节)提到是使用mean值填充,在data_transforme.cpp中,使用了如下的代码:

if (has_mean_values) {
   transformed_data[top_index] =
       (datum_element - mean_values_[c]) * scale;//c是channel,在prototxt中有三个值,分别对应三个波段104,117,123。
 } 
  • 1
  • 2
  • 3
  • 4
  1. distortImage使用了opencv的函数(DataTransformer.cpp),但我没找到该函数的实现。
    // Distort the image.
    cv::Mat distort_img = ApplyDistort(cv_img, param_.distort_param());
annotated_data_param/batch_samplers的处理
  • 1
  • 2
  • 3

c.) 随机裁剪
batch_samplers
是生产随机样本patch的方法,在该cpp的第178行调用了函数GenerateBatchSamples()函数原型定义为:

// Generate samples from AnnotatedDatum using the BatchSampler.
// All sampled bboxes which satisfy the constraints defined in BatchSampler
// is stored in sampled_bboxes.
void GenerateBatchSamples(const AnnotatedDatum& anno_datum,
                          const vector<BatchSampler>& batch_samplers,
                          vector<NormalizedBBox>* sampled_bboxes);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

然后调用到了函数GenerateSamples/SampleBBox
其方法是随机生产width和height,但是长宽的生成是相关的;然后在随机生成左上角坐标(之前见到的是先随机生成左上角坐标,然后用固定size去裁剪)

void SampleBBox(const Sampler& sampler, NormalizedBBox* sampled_bbox) {
  // Get random scale.
  CHECK_GE(sampler.max_scale(), sampler.min_scale());//scale是prototxt中参数
  CHECK_GT(sampler.min_scale(), 0.);//检查这些参数符合逻辑
  CHECK_LE(sampler.max_scale(), 1.);
  float scale;
  caffe_rng_uniform(1, sampler.min_scale(), sampler.max_scale(), &scale);//自定义的生成随机数的方法,在区间内生成float数,类似于random.random()

  // Get random aspect ratio.
  CHECK_GE(sampler.max_aspect_ratio(), sampler.min_aspect_ratio());
  CHECK_GT(sampler.min_aspect_ratio(), 0.);
  CHECK_LT(sampler.max_aspect_ratio(), FLT_MAX);//同样检查超参
  float aspect_ratio;
  caffe_rng_uniform(1, sampler.min_aspect_ratio(), sampler.max_aspect_ratio(),
      &aspect_ratio);//随机数

  aspect_ratio = std::max<float>(aspect_ratio, std::pow(scale, 2.));//做了些调整
  aspect_ratio = std::min<float>(aspect_ratio, 1 / std::pow(scale, 2.));

  // Figure out bbox dimension.
  float bbox_width = scale * sqrt(aspect_ratio);//长宽
  float bbox_height = scale / sqrt(aspect_ratio);

  // Figure out top left coordinates.
  float w_off, h_off;
  caffe_rng_uniform(1, 0.f, 1 - bbox_width, &w_off);//左上坐标
  caffe_rng_uniform(1, 0.f, 1 - bbox_height, &h_off);

  sampled_bbox->set_xmin(w_off);
  sampled_bbox->set_ymin(h_off);
  sampled_bbox->set_xmax(w_off + bbox_width);
  sampled_bbox->set_ymax(h_off + bbox_height);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

将随机产生的框(超参数给出迭代次数为max_trials: 50,会产生一些有效的符合要求的框boxes,不一定是50)存入一个容器中,然后再这这个容器中随机选择一个来裁剪图片和标注数据。

      // Generate sampled bboxes from expand_datum.
      vector<NormalizedBBox> sampled_bboxes;
      GenerateBatchSamples(*expand_datum, batch_samplers_, &sampled_bboxes);
      if (sampled_bboxes.size() > 0) {
        // Randomly pick a sampled bbox and crop the expand_datum.
        int rand_idx = caffe_rng_rand() % sampled_bboxes.size();
        sampled_datum = new AnnotatedDatum();
        this->data_transformer_->CropImage(*expand_datum,
                                           sampled_bboxes[rand_idx],
                                           sampled_datum);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

用到了这几个数据的处理关系如下:
这里写图片描述
之所以要贴出来,是因为多种transform方法组合在一起的时候,必然会带来排列组合的问题,源码中使用了一些组合方式,而不是生成一堆图像。(有人在blog中提到不同类别样本的数目问题,不均衡肯定是不好的,带来一些训练偏好)

上面并没有提及的是图片的长和宽,除了ExpandImage中会得到固定的size 的裁剪图片,其他的地方没有特别强调图片长宽不同,特别是sampled随机裁剪图片时,长和宽是用同样的随机数得到的,如果要被裁剪的图片长宽不同,那最终就不能保证aspect ratio(宽/长)了。
我们继续分析源码,annotateDataLayer层重新实现了DataLayerSetUp()函数,该函数是BaseDataLayer的一个虚函数,里面的。而BasePrefetchingDataLayer类的LayerSerup()调用了BaseDataLayer::LayerSetUp(bottom, top);函数将数据存入了prefetch_中,而所获取的数据被重新的reshape()了。

   // Reshape top[0] and prefetch_data according to the batch_size.
  for (int i = 0; i < this->PREFETCH_COUNT; ++i) {//AnnotatedDataLayer<Dtype>::DataLayerSetUp() ---line60
    this->prefetch_[i].data_.Reshape(top_shape);//其中,data_经过多层继承,最终是Blob的一个对象
  }
  • 1
  • 2
  • 3
  • 4

在这个层(annotateDataLayer)被调用的时候,它已经将输入数据resize了。

git代码(python)https://github.com/daniaokuye/SSD_data_augment.git


猜你喜欢

转载自blog.csdn.net/lanyuxuan100/article/details/79762142
今日推荐