Caffe实战系列:实现自己Caffe网络层

由于之前介绍过一次关于实现自己的网络层的文章,但是那篇文章偏难,这次我以最简单的对图像进行缩放的层为例进行实现。

在进行讲解之前,有一些必要条件你需要掌握,那就是你已经很了解怎么安装caffe,并且知道caffe里头的各个目录。

首先我们设计我们层所拥有的参数

out_height,即输出的图像的高度

out_width,即输出图像的宽度

visualize,是否需要将图像显示出来

那么可以在src/caffe/proto/caffe.proto文件中加入如下代码:

message ImageScaleParameter {
  // Specify the output height and width
  optional uint32 out_height = 1;
  optional uint32 out_width = 2;

  // for debug you can see the source images and scaled images
  optional bool visualize = 3 [default = false];
}

这里就指定了参数的名称以及参数的类型,optional说明该参数是可选的可以出现也可以不出现,此外[default=false]表明该参数的默认值是false

每个参数都指定一个数字表明参数的标识。

接着,我们可以将我们设计好的参数放入LayerParameter里头:

optional HingeLossParameter hinge_loss_param = 114;
optional ImageDataParameter image_data_param = 115;
optional ImageScaleParameter image_scale_param = 147;
optional InfogainLossParameter infogain_loss_param = 116;
optional InnerProductParameter inner_product_param = 117;

注意加入的时候看一看LayerParameter的注释,当你修改完毕了也要注意加入这样提示,这样方便后人更加方便地添加自定义层

// LayerParameter next available layer-specific ID: 148 (last added: image_scale_param)

接下来我们实现我们自己的层的头文件:

(1)实现的首先需要设置不允许头文件重复加入的宏定义:

#ifndef CAFFE_IMAGE_SCALE_LAYER_HPP_
#define CAFFE_IMAGE_SCALE_LAYER_HPP_

(2)加入必要的头文件

#include "caffe/blob.hpp"
#include "caffe/layer.hpp"
#include "caffe/proto/caffe.pb.h"
#include "caffe/layer.hpp"

(3)加入返回的层的类型字符串

virtual inline const char* type() const { return "ImageScale"; }

(4)告诉caffe本层的输入有几个,输出有几个

virtual inline int ExactNumBottomBlobs() const { return 1; }
virtual inline int ExactNumTopBlobs() const { return 1; }

(5)由于本层实现是图像的缩放,所以不需要反传,因此直接写一个空的虚函数的实现

virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
  const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {};

(6)定义在使用过程中所使用的类中的成员变量,注意类的成员变量的命名最后是以下划线结束,这样能够保持与caffe的代码一致性

  int out_height_;
  int out_width_;
  int height_;
  int width_;
  bool visualize_;
  int num_images_;
  int num_channels_;

(7)最后别忘记加入endif这个宏,此外注意加入必要的注释,以表明这个endif所对应的开头是什么

#endif  // CAFFE_IMAGE_SCALE_LAYER_HPP_

下面给出详细的头文件代码:

#ifndef CAFFE_IMAGE_SCALE_LAYER_HPP_
#define CAFFE_IMAGE_SCALE_LAYER_HPP_
#include "caffe/blob.hpp"
#include "caffe/layer.hpp"
#include "caffe/proto/caffe.pb.h"
#include "caffe/layer.hpp"
namespace caffe {
// written by xizero00 2016/9/13
template <typename Dtype>
class ImageScaleLayer : public Layer<Dtype> {
 public:
  explicit ImageScaleLayer(const LayerParameter& param)
      : Layer<Dtype>(param) {}
  virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);
  virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);
  virtual inline const char* type() const { return "ImageScale"; }
  virtual inline int ExactNumBottomBlobs() const { return 1; }
  virtual inline int ExactNumTopBlobs() const { return 1; }
 protected:
  /// @copydoc ImageScaleLayer
  virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);
  virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {};
  int out_height_;
  int out_width_;
  int height_;
  int width_;
  bool visualize_;
  int num_images_;
  int num_channels_;
};
}  // namespace caffe
#endif  // CAFFE_IMAGE_SCALE_LAYER_HPP_

接下来写具体的层的设置以及层的前传的实现:

(8)加入必要的头文件

#include "caffe/layers/image_scale_layer.hpp"
#include "caffe/util/math_functions.hpp"
#include <opencv2/opencv.hpp>

(9)实现层的设置函数LayerSetUp,在该函数中将网络的配置参数读取到类中的成员变量中,便于前传的时候以及对层进行设置的时候使用,并且检查参数的合法性

template <typename Dtype>
void ImageScaleLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) {
  // get parameters
  const ImageScaleParameter& param = this->layer_param_.image_scale_param();
  // get the output size
  out_height_ = param.out_height();
  out_width_ = param.out_width();
  visualize_ = param.visualize();

  // get the input size
  num_images_ = bottom[0]->num();
  height_ = bottom[0]->height();
  width_ = bottom[0]->width();
  num_channels_ = bottom[0]->channels();
  // check the channels must be images
  // channel must be 1 or 3, gray image or color image
  CHECK_EQ( (num_channels_==3) || (num_channels_ == 1), true);
  // check the output size
  CHECK_GT(out_height_, 0);
  CHECK_GT(out_height_, 0);

}

(10)实现层的Reshape函数,来设定该层的输出的大小,我们使用从网络配置文件中的参数类设置输出的大小

template <typename Dtype>
void ImageScaleLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) {
  // reshape the outputs
  top[0]->Reshape(num_images_, num_channels_, out_height_, out_width_);
}

(11)实现前向传播函数Forward_cpu,我实现的就是将图像一幅一幅地进行缩放到配置文件中所给的大小。

template <typename Dtype>
void ImageScaleLayer<Dtype>::Forward_cpu(
    const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
  const Dtype* bottom_data = bottom[0]->cpu_data();
  Dtype * top_data = top[0]->mutable_cpu_data();
  cv::Mat srcimage, dstimage;

  // precompurte the index
  const int srcimagesize = width_ * height_;
  const int dstimagesize = out_width_ *  out_height_;
  const int srcchimagesize = srcimagesize * num_channels_;
  const int dstchimagesize = dstimagesize * num_channels_;
  for  ( int idx_img = 0; idx_img < num_images_; idx_img++ )
  {
        // zeros source images and scaled images
        srcimage = cv::Mat::zeros(height_, width_, CV_32FC1);
        dstimage = cv::Mat::zeros(out_height_, out_width_, CV_32FC1);
        // read from bottom[0]
        for  ( int idx_ch = 0; idx_ch < num_channels_; idx_ch++ )
        {
                for  (int i = 0; i < height_; i++)
                {
                        for ( int j=0; j < width_; j++ )
                        {
                                int image_idx = idx_img * srcchimagesize + srcimagesize * idx_ch + height_ *i + j;
                                srcimage.at<float>(i,j) = (float)bottom_data[image_idx];
                        }
                }
        }
        // resize to specified size
        // here we use linear interpolation
        cv::resize(srcimage, dstimage, dstimage.size());
        // store the resized image to top[0]
        for (int idx_ch = 0; idx_ch < num_channels_; idx_ch++)
        {
                for (int i = 0; i < out_height_; i++)
                {
                        for (int j = 0; j < out_width_; j++)
                        {
                                int image_idx = idx_img * dstchimagesize + dstimagesize * idx_ch + out_height_*i + j;
                                top_data[image_idx] = dstimage.at<float>(i,j);
                        }
                }
        }
        if (visualize_)
        {
                cv::namedWindow("src image", CV_WINDOW_AUTOSIZE);
                cv::namedWindow("dst image", CV_WINDOW_AUTOSIZE);
                cv::imshow("src image", srcimage);
                cv::imshow("dst image", dstimage);
                cv::waitKey(0);
        }
  }
}

最后给出完整的实现:

#include "caffe/layers/image_scale_layer.hpp"
#include "caffe/util/math_functions.hpp"
#include <opencv2/opencv.hpp>
namespace caffe {
template <typename Dtype>
void ImageScaleLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) {
  // get parameters
  const ImageScaleParameter& param = this->layer_param_.image_scale_param();
  // get the output size
  out_height_ = param.out_height();
  out_width_ = param.out_width();
  visualize_ = param.visualize();

  // get the input size
  num_images_ = bottom[0]->num();
  height_ = bottom[0]->height();
  width_ = bottom[0]->width();
  num_channels_ = bottom[0]->channels();
  // check the channels must be images
  // channel must be 1 or 3, gray image or color image
  CHECK_EQ( (num_channels_==3) || (num_channels_ == 1), true);
  // check the output size
  CHECK_GT(out_height_, 0);
  CHECK_GT(out_height_, 0);

}
template <typename Dtype>
void ImageScaleLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) {
  // reshape the outputs
  top[0]->Reshape(num_images_, num_channels_, out_height_, out_width_);
}
template <typename Dtype>
void ImageScaleLayer<Dtype>::Forward_cpu(
    const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
  const Dtype* bottom_data = bottom[0]->cpu_data();
  Dtype * top_data = top[0]->mutable_cpu_data();
  cv::Mat srcimage, dstimage;

  // precompurte the index
  const int srcimagesize = width_ * height_;
  const int dstimagesize = out_width_ *  out_height_;
  const int srcchimagesize = srcimagesize * num_channels_;
  const int dstchimagesize = dstimagesize * num_channels_;
  for  ( int idx_img = 0; idx_img < num_images_; idx_img++ )
  {
        // zeros source images and scaled images
        srcimage = cv::Mat::zeros(height_, width_, CV_32FC1);
        dstimage = cv::Mat::zeros(out_height_, out_width_, CV_32FC1);
        // read from bottom[0]
        for  ( int idx_ch = 0; idx_ch < num_channels_; idx_ch++ )
        {
                for  (int i = 0; i < height_; i++)
                {
                        for ( int j=0; j < width_; j++ )
                        {
                                int image_idx = idx_img * srcchimagesize + srcimagesize * idx_ch + height_ *i + j;
                                srcimage.at<float>(i,j) = (float)bottom_data[image_idx];
                        }
                }
        }
        // resize to specified size
        // here we use linear interpolation
        cv::resize(srcimage, dstimage, dstimage.size());
        // store the resized image to top[0]
        for (int idx_ch = 0; idx_ch < num_channels_; idx_ch++)
        {
                for (int i = 0; i < out_height_; i++)
                {
                        for (int j = 0; j < out_width_; j++)
                        {
                                int image_idx = idx_img * dstchimagesize + dstimagesize * idx_ch + out_height_*i + j;
                                top_data[image_idx] = dstimage.at<float>(i,j);
                        }
                }
        }
        if (visualize_)
        {
                cv::namedWindow("src image", CV_WINDOW_AUTOSIZE);
                cv::namedWindow("dst image", CV_WINDOW_AUTOSIZE);
                cv::imshow("src image", srcimage);
                cv::imshow("dst image", dstimage);
                cv::waitKey(0);
        }
  }
}
#ifdef CPU_ONLY
STUB_GPU(ImageScaleLayer);
#endif
INSTANTIATE_CLASS(ImageScaleLayer);
REGISTER_LAYER_CLASS(ImageScale);
}  // namespace caffe

请把上述代码,保存为image_scale_layer.hpp和cpp。然后放入到对应的include和src/caffe/layers文件夹中。

那么在使用的时候可以进行如下配置

layer {
  name: "imagescaled"
  type: "ImageScale"
  bottom: "data"
  top: "imagescaled"
  image_scale_param {
    out_height: 128
    out_width: 128
    visualize: true
  }
}

上述配置中out_height和out_width就是经过缩放之后的图片的大小,而visualize表明是否显示的意思。

至此,我们就完成了一个很简单的caffe自定义层的实现,怎么样,很简单吧?

我测试的模型(我想你肯定知道怎么用caffe所听的工具将mnist数据集转换为lmdb吧)是:

# Simple single-layer network to showcase editing model parameters.
name: "sample"
layer {
  name: "data"
  type: "Data"
  top: "data"
  include {
    phase: TRAIN
  }
  transform_param {
    scale: 0.0039215684
  }
  data_param {
    source: "examples/mnist/mnist_train_lmdb"
    batch_size: 10
    backend: LMDB
  }
}

layer {
  name: "imagescaled"
  type: "ImageScale"
  bottom: "data"
  top: "imagescaled"
  image_scale_param {
    out_height: 128
    out_width: 128
    visualize: true
  }
}

测试所用的solver.prototxt

net: "examples/imagescale/sample.prototxt"

base_lr: 0.01
lr_policy: "step"
gamma: 0.1
stepsize: 10000
display: 1
max_iter: 1
weight_decay: 0.0005
snapshot: 1
snapshot_prefix: "examples/imagescale/sample"
momentum: 0.9
# solver mode: CPU or GPU
solver_mode: GPU

然后运行的时候仅仅需要写个bash文件到caffe的目录:

#!/usr/bin/env sh
set -e

snap_dir="examples/imagescale/snapshots"

mkdir -p $snap_dir

TOOLS=./build/tools

$TOOLS/caffe train --solver=examples/imagescale/solver.prototxt 2>&1 | tee -a $snap_dir/train.log

下面给出我的结果:

小的是输入的原始图像,大的是经过缩放之后的图像。

好了,到此结束。

代码打包下载,请戳这里

http://download.csdn.net/detail/xizero00/9629898

时间: 2024-07-28 19:58:30

Caffe实战系列:实现自己Caffe网络层的相关文章

Caffe学习系列(16):caffe的整体流程

在某社区看到的回答,觉得不错就转过来了:http://caffecn.cn/?/question/123 Caffe从四个层次来理解:Blob,Layer,Net,Solver. 1.Blob Caffe的基本数据结构,用四维矩阵Batch*Channel*Height*Width表示,存储了包括神经元的 激活值.参数.以及相应的梯度(dW,db).其中包含有cpu_data.gpu_data.cpu_diff.gpu_diff. mutable_cpu_data.mutable_gpu_dat

Caffe学习系列(14):Caffe代码阅读

知乎上这位博主画的caffe的整体结构:https://zhuanlan.zhihu.com/p/21796890?refer=hsmyy Caffe 做train时的流程图,来自http://caffecn.cn/?/question/242

Caffe 学习系列

学习列表: Google protocol buffer在windows下的编译 caffe windows 学习第一步:编译和安装(vs2012+win 64) caffe windows学习:第一个测试程序 Caffe学习系列(1):安装配置ubuntu14.04+cuda7.5+caffe+cudnn Caffe学习系列(2):数据层及参数 Caffe学习系列(3):视觉层(Vision Layers)及参数 Caffe学习系列(4):激活层(Activiation Layers)及参数

Caffe学习系列——工具篇:神经网络模型结构可视化

Caffe学习系列--工具篇:神经网络模型结构可视化 在Caffe中,目前有两种可视化prototxt格式网络结构的方法: 使用Netscope在线可视化 使用Caffe提供的draw_net.py 本文将就这两种方法加以介绍 1. Netscope:支持Caffe的神经网络结构在线可视化工具 Netscope是个支持prototxt格式描述的神经网络结构的在线可视工具,网址:  http://ethereon.github.io/netscope/quickstart.html  它可以用来可

转 Caffe学习系列(3):视觉层(Vision Layers)及参数

所有的层都具有的参数,如name, type, bottom, top和transform_param请参看我的前一篇文章:Caffe学习系列(2):数据层及参数 本文只讲解视觉层(Vision Layers)的参数,视觉层包括Convolution, Pooling, Local Response Normalization (LRN), im2col等层. 1.Convolution层: 就是卷积层,是卷积神经网络(CNN)的核心层. 层类型:Convolution lr_mult: 学习率

深度学习Caffe实战笔记(19)Windows平台 Faster-RCNN 制作自己的数据集

"-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 深度学习Caffe实战笔记(19)Windows平台 Faster-RCNN 制作自己的数据集 - gybheroin的博客 - 博客频道 - CSDN.NET gybheroin的博客 目录视图 摘要视图 订阅 [活动]2017 CSDN博客专栏评选 &nbsp [

Caffe学习系列(一)Ubuntu16.04下搭建编译Caffe环境,并运行MNIST示例(仅CPU)

前言: 正文: 1.安装必要依赖包: sudo apt-get install libprotobuf-dev libleveldb-dev libsnappy-dev libopencv-dev libhdf5-serial-dev protobuf-compiler sudo apt-get install --no-install-recommends libboost-all-dev sudo apt-get install libatlas-base-dev sudo apt-get

Caffe学习系列(10):命令行解析

caffe的运行提供三种接口:c++接口(命令行).python接口和matlab接口.本文先对命令行进行解析,后续会依次介绍其它两个接口. caffe的c++主程序(caffe.cpp)放在根目录下的tools文件夹内, 当然还有一些其它的功能文件,如:convert_imageset.cpp, train_net.cpp, test_net.cpp等也放在这个文件夹内.经过编译后,这些文件都被编译成了可执行文件,放在了 ./build/tools/ 文件夹内.因此我们要执行caffe程序,都

caffe实战笔记

Caffe简要介绍: Caffe还没有windows版本,所以我需要远程登录linux服务器 Caffe主要处理图片/图片序列 Caffe读取的数据格式 从专用的数据库中读取(lmdb.leveldb) 直接读取图片 从内存中读取(会占很多内存) 从HDF5文件中读取 从滑动窗口中读取(在大图中滑动一次作为一张小图) 最常用的是前面两种方式.默认是从lmdb数据库格式中读取,因此需要先把图片文件转换成lmdb格式文件.直接读取图片会导致无法减均值.如果不考虑减均值的情况,可直接读取图片. Caf