CRF As RNN的原理及Caffe实现

CRF(Conditional Random Field)是图像分割中很常用的后处理算法。在《全卷积网络(FCN)与图像分割 》这篇博文中提到,FCN可以得到较好的分割结果,Chen, Liang-Chieh, et al. 2014在其基础上使用fully connected CRF得到了更好的效果,但是FCN的步骤和CRF的步骤是分开的。Zheng et al 2015将fully connected CRF表示成RNN的结构,与FCN连接在一起,可以同时训练FCN和CRF,使分割的准确率有了更多提高。

 

CRF as RNN的原理

 

  CRF的能量函数包括一个数据项和平滑项。数据项是基于每个像素属于各个类别的概率,平滑项是基于像素之间的灰度值差异和空间距离。传统的CRF的平滑项只考虑相邻像素间的关联,而Fully connected CRF考虑了图像中任意两个像素之间的关联性。

公式(1)

  其中是数据项, 是能量项, 即像素对之间的能量,其定义为若干个高斯函数的和:

公式(2)

  高斯函数的定义为:

公式(3)

  由两部分组成,第一部分是Bilateral Filter, 其想法是空间上离得近并且灰度值接近的像素很有可能是属于同一个物体。第二部分是Spatial Kernel,起到空间平滑作用,可以去除掉分割结果中一些孤立的小区域。

 

  条件随机场的概率函数为。对公式(1)中的E(x)最小化对应着对后验概率P(X=x | I)的最大化,从而得到最优分割结果。

 

  由于直接计算概率函数P(X)比较麻烦,可以通过一个比较方便计算的概率函数Q(X)来近似得到P(X)。 。为了让Q(X)最大限度接近P(X),可通过对它们的KL-divergence最小化得到。这个最小化过程的迭代步骤如下: 

公式(4)

  将该步骤中的各个操作拆分,可以得到如下的算法: 

  该算法的输入为: 初始势能,对应公式(1)的第一项; m个滤波器; 各滤波器的权重, 不同类别之间的兼容性矩阵。输出为Q_i即更新后的概率分布。

 

  该算法的每一次迭代分为5个步骤:

1, 信息传递。即使用m个滤波器分别对每一个类别l的概率图进行滤波的过程。 

2, 滤波结果加权相加。对每一个类别l的m个滤波结果根据权重相加。 

3,类别兼容性转换。对每一个类别l的概率图根据不同类别之间的兼容性矩阵进行更新。 

4, 加上数据项(一元项 Unary Potential)。 

5, 归一化。对各像素所属不同类别l的概率归一化,这实际上是一个Softmax的过程。

 

Caffe实现

 

  GitHub上可以找到CRF as RNN的源代码。主要有两个类:MultiStageMeanfieldLayer和MeanfieldIteration。其中MultiStageMeanfieldLayer将CRF的所有迭代步骤组装在一起形成一个Caffe层,迭代的每一步作为一个子层在MeanfieldIteration中实现。目前是基于cpu代码的实现,还没有在cuda上实现。

 

  该实现中考虑了Bilateral filter 和Spatial filter两种滤波器,分别对应公式(3)中的两项。由于Fully connected CRF中的滤波操作要考虑整个图像的信息, 滤波器核的大小为整个图像的尺寸,因此滤波过程比较耗时,为了提高效率,Zheng et al使用了Andrew Adams et al所提出的基于Perutohedral Lttice的滤波实现: 实现Bilateral filter 的bilateral_lattices_和实现Spatial filter的spatial_lattice。

MeanfieldIteration的前向运算为:

  1. /**

  2.  * 在推论期间向前传.

  3.  */

  4. template <typename Dtype>

  5. void MeanfieldIteration<Dtype>::Forward_cpu() {

  6.  

  7.  

  8.   //------------------------------- Softmax规范化--------------------

  9.   softmax_layer_->Forward(softmax_bottom_vec_, softmax_top_vec_);

  10.  

  11.   //-----------------------------------消息传递-----------------------

  12.   for (int n = 0; n < num_ /*number of images in the batch */; ++n) {

  13.   //空间过滤

  14.     Dtype* spatial_out_data = spatial_out_blob_.mutable_cpu_data() + spatial_out_blob_.offset(n);

  15.     const Dtype* prob_input_data = prob_.cpu_data() + prob_.offset(n);

  16.  

  17.     spatial_lattice_->compute(spatial_out_data, prob_input_data, channels_, false);

  18.  

  19.     // 空间滤波,像素标准化.

  20.     for (int channel_id = 0; channel_id < channels_; ++channel_id) {

  21.       caffe_mul(num_pixels_, spatial_norm_->cpu_data(),

  22.           spatial_out_data + channel_id * num_pixels_,

  23.           spatial_out_data + channel_id * num_pixels_);

  24.     }

  25.     // 双边过滤

  26.     Dtype* bilateral_out_data = bilateral_out_blob_.mutable_cpu_data() + bilateral_out_blob_.offset(n);

  27.  

  28.     (*bilateral_lattices_)[n]->compute(bilateral_out_data, prob_input_data, channels_, false);

  29.     // 双边滤波,像素标准化.

  30.     for (int channel_id = 0; channel_id < channels_; ++channel_id) {

  31.       caffe_mul(num_pixels_, bilateral_norms_->cpu_data() + bilateral_norms_->offset(n),

  32.           bilateral_out_data + channel_id * num_pixels_,

  33.           bilateral_out_data + channel_id * num_pixels_);

  34.     }

  35.   }

  36.  

  37.   caffe_set(count_, Dtype(0.), message_passing_.mutable_cpu_data());

  38.   // 添加空间滤波的加权输出

  39.   for (int n = 0; n < num_; ++n) {

  40.     caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, channels_, num_pixels_, channels_, (Dtype) 1.,

  41.         this->blobs_[0]->cpu_data(), spatial_out_blob_.cpu_data() + spatial_out_blob_.offset(n), (Dtype) 0.,

  42.         message_passing_.mutable_cpu_data() + message_passing_.offset(n));

  43.   }

  44.   // 添加双边过滤的加权输出

  45.   for (int n = 0; n < num_; ++n) {

  46.     caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, channels_, num_pixels_, channels_, (Dtype) 1.,

  47.         this->blobs_[1]->cpu_data(), bilateral_out_blob_.cpu_data() + bilateral_out_blob_.offset(n), (Dtype) 1.,

  48.         message_passing_.mutable_cpu_data() + message_passing_.offset(n));

  49.   }

  50.  

  51.   //--------------------------- 兼容性乘法 ----------------

  52.   //消息传递的结果需要与兼容性值相乘.

  53.   for (int n = 0; n < num_; ++n) {

  54.     caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, channels_, num_pixels_,

  55.         channels_, (Dtype) 1., this->blobs_[2]->cpu_data(),

  56.         message_passing_.cpu_data() + message_passing_.offset(n), (Dtype) 0.,

  57.         pairwise_.mutable_cpu_data() + pairwise_.offset(n));

  58.   }

  59.  

  60.   //------------------------- 添加一元,归一化到下一次迭代 --------------

  61.   // 添加一元

  62.   sum_layer_->Forward(sum_bottom_vec_, sum_top_vec_);

  63. }

   其中caffe_cpu_gemm是实现C=αAB+βC的运算函数。在Forward_cpu()中依次实现了上面提到的5个步骤。 

  void MeanfieldIteration::Backward_cpu()中倒序实现了上述个步骤的反向传播。

MultiStageMeanfieldLayer中为每一个迭代步骤分别创建了一个MeanfieldIteration层,其前向传播的代码为:

  1. template <typename Dtype>

  2. void MultiStageMeanfieldLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,

  3.       const vector<Blob<Dtype>*>& top) {

  4.  

  5.   split_layer_bottom_vec_[0] = bottom[0];

  6.   split_layer_->Forward(split_layer_bottom_vec_, split_layer_top_vec_);

  7.  

  8.   // 初始化双边格子.

  9.   bilateral_lattices_.resize(num_);

  10.   for (int n = 0; n < num_; ++n) {

  11.  

  12.     compute_bilateral_kernel(bottom[2], n, bilateral_kernel_buffer_.get());

  13.     bilateral_lattices_[n].reset(new ModifiedPermutohedral());

  14.     bilateral_lattices_[n]->init(bilateral_kernel_buffer_.get(), 5, num_pixels_);

  15.  

  16.     // 计算双边滤波器归一化因子.

  17.     Dtype* norm_output_data = bilateral_norms_.mutable_cpu_data() + bilateral_norms_.offset(n);

  18.     bilateral_lattices_[n]->compute(norm_output_data, norm_feed_.get(), 1);

  19.     for (int i = 0; i < num_pixels_; ++i) {

  20.       norm_output_data[i] = 1.f / (norm_output_data[i] + 1e-20f);

  21.     }

  22.   }

  23.  

  24.   for (int i = 0; i < num_iterations_; ++i) {

  25.  

  26.     meanfield_iterations_[i]->PrePass(this->blobs_, &bilateral_lattices_, &bilateral_norms_);

  27.  

  28.     meanfield_iterations_[i]->Forward_cpu();

  29.   }

  30. }

相关文章

相关标签/搜索