}
}
+TYPED_TEST(ConvolutionLayerTest, TestSobelConvolution) {
+ // Test separable convolution by computing the Sobel operator
+ // as a single filter then comparing the result
+ // as the convolution of two rectangular filters.
+ typedef typename TypeParam::Dtype Dtype;
+ // Fill bottoms with identical Gaussian noise.
+ shared_ptr<GaussianFiller<Dtype> > filler;
+ FillerParameter filler_param;
+ filler_param.set_value(1.);
+ filler.reset(new GaussianFiller<Dtype>(filler_param));
+ filler->Fill(this->blob_bottom_);
+ this->blob_bottom_2_->CopyFrom(*this->blob_bottom_);
+ // Compute Sobel G_x operator as 3 x 3 convolution.
+ LayerParameter layer_param;
+ ConvolutionParameter* convolution_param =
+ layer_param.mutable_convolution_param();
+ convolution_param->set_kernel_size(3);
+ convolution_param->set_stride(2);
+ convolution_param->set_num_output(1);
+ convolution_param->set_bias_term(false);
+ shared_ptr<Layer<Dtype> > layer(
+ new ConvolutionLayer<Dtype>(layer_param));
+ layer->blobs().resize(1);
+ layer->blobs()[0].reset(new Blob<Dtype>(1, 3, 3, 3));
+ Dtype* weights = layer->blobs()[0]->mutable_cpu_data();
+ for (int c = 0; c < 3; ++c) {
+ int i = c * 9; // 3 x 3 filter
+ weights[i + 0] = -1;
+ weights[i + 1] = 0;
+ weights[i + 2] = 1;
+ weights[i + 3] = -2;
+ weights[i + 4] = 0;
+ weights[i + 5] = 2;
+ weights[i + 6] = -1;
+ weights[i + 7] = 0;
+ weights[i + 8] = 1;
+ }
+ layer->SetUp(this->blob_bottom_vec_, &(this->blob_top_vec_));
+ layer->Forward(this->blob_bottom_vec_, &(this->blob_top_vec_));
+ // Compute Sobel G_x operator as separable 3 x 1 and 1 x 3 convolutions.
+ // (1) the [1 2 1] column filter
+ vector<Blob<Dtype>*> sep_blob_bottom_vec;
+ vector<Blob<Dtype>*> sep_blob_top_vec;
+ shared_ptr<Blob<Dtype> > blob_sep(new Blob<Dtype>());
+ sep_blob_bottom_vec.push_back(this->blob_bottom_2_);
+ sep_blob_top_vec.push_back(this->blob_top_2_);
+ convolution_param->clear_kernel_size();
+ convolution_param->clear_stride();
+ convolution_param->set_kernel_h(3);
+ convolution_param->set_kernel_w(1);
+ convolution_param->set_stride_h(2);
+ convolution_param->set_stride_w(1);
+ convolution_param->set_num_output(1);
+ convolution_param->set_bias_term(false);
+ layer.reset(new ConvolutionLayer<Dtype>(layer_param));
+ layer->blobs().resize(1);
+ layer->blobs()[0].reset(new Blob<Dtype>(1, 3, 3, 1));
+ Dtype* weights_1 = layer->blobs()[0]->mutable_cpu_data();
+ for (int c = 0; c < 3; ++c) {
+ int i = c * 3; // 3 x 1 filter
+ weights_1[i + 0] = 1;
+ weights_1[i + 1] = 2;
+ weights_1[i + 2] = 1;
+ }
+ layer->SetUp(sep_blob_bottom_vec, &(sep_blob_top_vec));
+ layer->Forward(sep_blob_bottom_vec, &(sep_blob_top_vec));
+ // (2) the [-1 0 1] row filter
+ blob_sep->CopyFrom(*this->blob_top_2_, false, true);
+ sep_blob_bottom_vec.clear();
+ sep_blob_bottom_vec.push_back(blob_sep.get());
+ convolution_param->set_kernel_h(1);
+ convolution_param->set_kernel_w(3);
+ convolution_param->set_stride_h(1);
+ convolution_param->set_stride_w(2);
+ convolution_param->set_num_output(1);
+ convolution_param->set_bias_term(false);
+ layer.reset(new ConvolutionLayer<Dtype>(layer_param));
+ layer->blobs().resize(1);
+ layer->blobs()[0].reset(new Blob<Dtype>(1, 3, 1, 3));
+ Dtype* weights_2 = layer->blobs()[0]->mutable_cpu_data();
+ for (int c = 0; c < 3; ++c) {
+ int i = c * 3; // 1 x 3 filter
+ weights_2[i + 0] = -1;
+ weights_2[i + 1] = 0;
+ weights_2[i + 2] = 1;
+ }
+ layer->SetUp(sep_blob_bottom_vec, &(sep_blob_top_vec));
+ layer->Forward(sep_blob_bottom_vec, &(sep_blob_top_vec));
+ // Test equivalence of full and separable filters.
+ const Dtype* top_data = this->blob_top_->cpu_data();
+ const Dtype* sep_top_data = this->blob_top_2_->cpu_data();
+ for (int i = 0; i < this->blob_top_->count(); ++i) {
+ EXPECT_NEAR(top_data[i], sep_top_data[i], 1e-4);
+ }
+}
+
TYPED_TEST(ConvolutionLayerTest, TestGradient) {
typedef typename TypeParam::Dtype Dtype;
LayerParameter layer_param;