dnn_custom_layers.md 7.2 KB
Newer Older
1 2
# Custom deep learning layers support {#tutorial_dnn_custom_layers}

3 4
@prev_tutorial{tutorial_dnn_javascript}

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 34 35 36
## Introduction
Deep learning is a fast growing area. The new approaches to build neural networks
usually introduce new types of layers. They could be modifications of existing
ones or implement outstanding researching ideas.

OpenCV gives an opportunity to import and run networks from different deep learning
frameworks. There are a number of the most popular layers. However you can face
a problem that your network cannot be imported using OpenCV because of unimplemented layers.

The first solution is to create a feature request at https://github.com/opencv/opencv/issues
mentioning details such a source of model and type of new layer. A new layer could
be implemented if OpenCV community shares this need.

The second way is to define a **custom layer** so OpenCV's deep learning engine
will know how to use it. This tutorial is dedicated to show you a process of deep
learning models import customization.

## Define a custom layer in C++
Deep learning layer is a building block of network's pipeline.
It has connections to **input blobs** and produces results to **output blobs**.
There are trained **weights** and **hyper-parameters**.
Layers' names, types, weights and hyper-parameters are stored in files are generated by
native frameworks during training. If OpenCV mets unknown layer type it throws an
exception trying to read a model:

```
Unspecified error: Can't create layer "layer_name" of type "MyType" in function getLayerInstance
```

To import the model correctly you have to derive a class from cv::dnn::Layer with
the following methods:

37
@snippet dnn/custom_layers.hpp A custom layer interface
38 39 40

And register it before the import:

41
@snippet dnn/custom_layers.hpp Register a custom layer
42 43 44 45 46 47 48

@note `MyType` is a type of unimplemented layer from the thrown exception.

Let's see what all the methods do:

- Constructor

49
@snippet dnn/custom_layers.hpp MyLayer::MyLayer
50 51 52 53 54 55

Retrieves hyper-parameters from cv::dnn::LayerParams. If your layer has trainable
weights they will be already stored in the Layer's member cv::dnn::Layer::blobs.

- A static method `create`

56
@snippet dnn/custom_layers.hpp MyLayer::create
57 58 59 60 61

This method should create an instance of you layer and return cv::Ptr with it.

- Output blobs' shape computation

62
@snippet dnn/custom_layers.hpp MyLayer::getMemoryShapes
63 64 65 66 67 68

Returns layer's output shapes depends on input shapes. You may request an extra
memory using `internals`.

- Run a layer

69
@snippet dnn/custom_layers.hpp MyLayer::forward
70 71 72 73 74 75 76 77 78

Implement a layer's logic here. Compute outputs for given inputs.

@note OpenCV manages memory allocated for layers. In the most cases the same memory
can be reused between layers. So your `forward` implementation should not rely that
the second invocation of `forward` will has the same data at `outputs` and `internals`.

- Optional `finalize` method

79
@snippet dnn/custom_layers.hpp MyLayer::finalize
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112

The chain of methods are the following: OpenCV deep learning engine calls `create`
method once then it calls `getMemoryShapes` for an every created layer then you
can make some preparations depends on known input dimensions at cv::dnn::Layer::finalize.
After network was initialized only `forward` method is called for an every network's input.

@note Varying input blobs' sizes such height or width or batch size you make OpenCV
reallocate all the internal memory. That leads efficiency gaps. Try to initialize
and deploy models using a fixed batch size and image's dimensions.

## Example: custom layer from Caffe
Let's create a custom layer `Interp` from https://github.com/cdmh/deeplab-public.
It's just a simple resize that takes an input blob of size `N x C x Hi x Wi` and returns
an output blob of size `N x C x Ho x Wo` where `N` is a batch size, `C` is a number of channels,
`Hi x Wi` and `Ho x Wo` are input and output `height x width` correspondingly.
This layer has no trainable weights but it has hyper-parameters to specify an output size.

In example,
~~~~~~~~~~~~~
layer {
  name: "output"
  type: "Interp"
  bottom: "input"
  top: "output"
  interp_param {
    height: 9
    width: 8
  }
}
~~~~~~~~~~~~~

This way our implementation can look like:

113
@snippet dnn/custom_layers.hpp InterpLayer
114 115 116

Next we need to register a new layer type and try to import the model.

117
@snippet dnn/custom_layers.hpp Register InterpLayer
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189

## Example: custom layer from TensorFlow
This is an example of how to import a network with [tf.image.resize_bilinear](https://www.tensorflow.org/versions/master/api_docs/python/tf/image/resize_bilinear)
operation. This is also a resize but with an implementation different from OpenCV's or `Interp` above.

Let's create a single layer network:
~~~~~~~~~~~~~{.py}
inp = tf.placeholder(tf.float32, [2, 3, 4, 5], 'input')
resized = tf.image.resize_bilinear(inp, size=[9, 8], name='resize_bilinear')
~~~~~~~~~~~~~
OpenCV sees that TensorFlow's graph in the following way:

```
node {
  name: "input"
  op: "Placeholder"
  attr {
    key: "dtype"
    value {
      type: DT_FLOAT
    }
  }
}
node {
  name: "resize_bilinear/size"
  op: "Const"
  attr {
    key: "dtype"
    value {
      type: DT_INT32
    }
  }
  attr {
    key: "value"
    value {
      tensor {
        dtype: DT_INT32
        tensor_shape {
          dim {
            size: 2
          }
        }
        tensor_content: "\t\000\000\000\010\000\000\000"
      }
    }
  }
}
node {
  name: "resize_bilinear"
  op: "ResizeBilinear"
  input: "input:0"
  input: "resize_bilinear/size"
  attr {
    key: "T"
    value {
      type: DT_FLOAT
    }
  }
  attr {
    key: "align_corners"
    value {
      b: false
    }
  }
}
library {
}
```
Custom layers import from TensorFlow is designed to put all layer's `attr` into
cv::dnn::LayerParams but input `Const` blobs into cv::dnn::Layer::blobs.
In our case resize's output shape will be stored in layer's `blobs[0]`.

190
@snippet dnn/custom_layers.hpp ResizeBilinearLayer
191 192 193

Next we register a layer and try to import the model.

194
@snippet dnn/custom_layers.hpp Register ResizeBilinearLayer
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220

## Define a custom layer in Python
The following example shows how to customize OpenCV's layers in Python.

Let's consider [Holistically-Nested Edge Detection](https://arxiv.org/abs/1504.06375)
deep learning model. That was trained with one and only difference comparing to
a current version of [Caffe framework](http://caffe.berkeleyvision.org/). `Crop`
layers that receive two input blobs and crop the first one to match spatial dimensions
of the second one used to crop from the center. Nowadays Caffe's layer does it
from the top-left corner. So using the latest version of Caffe or OpenCV you'll
get shifted results with filled borders.

Next we're going to replace OpenCV's `Crop` layer that makes top-left cropping by
a centric one.

- Create a class with `getMemoryShapes` and `forward` methods

@snippet dnn/edge_detection.py CropLayer

@note Both methods should return lists.

- Register a new layer.

@snippet dnn/edge_detection.py Register

That's it! We've replaced an implemented OpenCV's layer to a custom one.
221
You may find a full script in the [source code](https://github.com/opencv/opencv/tree/3.4/samples/dnn/edge_detection.py).
222 223 224 225 226 227 228

<table border="0">
<tr>
<td>![](js_tutorials/js_assets/lena.jpg)</td>
<td>![](images/lena_hed.jpg)</td>
</tr>
</table>