欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-19 23:10:34
...

SSD的caffe源码解读 – 数据增强

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

用到了这几个数据的处理关系如下:
SSD的caffe源码解读 -- 数据增强
之所以要贴出来,是因为多种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