From d5b95632639b7203d174effc90cd2c909399a7c9 Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Wed, 25 Apr 2018 15:19:02 +0300 Subject: [PATCH] Custom deep learning layers in Python --- .../dnn_custom_layers/dnn_custom_layers.md | 34 ++++ doc/tutorials/dnn/images/lena_hed.jpg | Bin 0 -> 38925 bytes modules/dnn/misc/python/pyopencv_dnn.hpp | 180 +++++++++++++++++- modules/python/src2/cv2.cpp | 4 + samples/dnn/edge_detection.py | 69 +++++++ 5 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 doc/tutorials/dnn/images/lena_hed.jpg create mode 100644 samples/dnn/edge_detection.py diff --git a/doc/tutorials/dnn/dnn_custom_layers/dnn_custom_layers.md b/doc/tutorials/dnn/dnn_custom_layers/dnn_custom_layers.md index f0e419753a..5b3f3c7347 100644 --- a/doc/tutorials/dnn/dnn_custom_layers/dnn_custom_layers.md +++ b/doc/tutorials/dnn/dnn_custom_layers/dnn_custom_layers.md @@ -190,3 +190,37 @@ In our case resize's output shape will be stored in layer's `blobs[0]`. Next we register a layer and try to import the model. @snippet dnn/custom_layers.cpp Register ResizeBilinearLayer + +## 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. +You may find a full script in the [source code](https://github.com/opencv/opencv/tree/master/samples/dnn/edge_detection.py). + + + + + + +
![](js_tutorials/js_assets/lena.jpg)![](images/lena_hed.jpg)
diff --git a/doc/tutorials/dnn/images/lena_hed.jpg b/doc/tutorials/dnn/images/lena_hed.jpg new file mode 100644 index 0000000000000000000000000000000000000000..59d5a6c31ee8bb2d5bc01b0cab02025090f3c155 GIT binary patch literal 38925 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 &blobs, const char *name) //requi return pyopencvVecConverter::to(o, blobs, ArgInfo(name, false)); } -#endif +template +PyObject* pyopencv_from(const dnn::DictValue &dv) +{ + if (dv.size() > 1) + { + std::vector vec(dv.size()); + for (int i = 0; i < dv.size(); ++i) + vec[i] = dv.get(i); + return pyopencv_from_generic_vec(vec); + } + else + return pyopencv_from(dv.get()); +} + +template<> +PyObject* pyopencv_from(const dnn::DictValue &dv) +{ + if (dv.isInt()) return pyopencv_from(dv); + if (dv.isReal()) return pyopencv_from(dv); + if (dv.isString()) return pyopencv_from(dv); + CV_Error(Error::StsNotImplemented, "Unknown value type"); + return NULL; +} + +template<> +PyObject* pyopencv_from(const dnn::LayerParams& lp) +{ + PyObject* dict = PyDict_New(); + for (std::map::const_iterator it = lp.begin(); it != lp.end(); ++it) + { + CV_Assert(!PyDict_SetItemString(dict, it->first.c_str(), pyopencv_from(it->second))); + } + return dict; +} + +class pycvLayer CV_FINAL : public dnn::Layer +{ +public: + pycvLayer(const dnn::LayerParams ¶ms, PyObject* pyLayer) : Layer(params) + { + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + + PyObject* args = PyTuple_New(2); + CV_Assert(!PyTuple_SetItem(args, 0, pyopencv_from(params))); + CV_Assert(!PyTuple_SetItem(args, 1, pyopencv_from(params.blobs))); + o = PyObject_CallObject(pyLayer, args); + + Py_DECREF(args); + PyGILState_Release(gstate); + if (!o) + CV_Error(Error::StsError, "Failed to create an instance of custom layer"); + } + + static void registerLayer(const std::string& type, PyObject* o) + { + std::map >::iterator it = pyLayers.find(type); + if (it != pyLayers.end()) + it->second.push_back(o); + else + pyLayers[type] = std::vector(1, o); + } + + static void unregisterLayer(const std::string& type) + { + std::map >::iterator it = pyLayers.find(type); + if (it != pyLayers.end()) + { + if (it->second.size() > 1) + it->second.pop_back(); + else + pyLayers.erase(it); + } + } + + static Ptr create(dnn::LayerParams ¶ms) + { + std::map >::iterator it = pyLayers.find(params.type); + if (it == pyLayers.end()) + CV_Error(Error::StsNotImplemented, "Layer with a type \"" + params.type + + "\" is not implemented"); + CV_Assert(!it->second.empty()); + return Ptr(new pycvLayer(params, it->second.back())); + } + + virtual bool getMemoryShapes(const std::vector > &inputs, + const int, + std::vector > &outputs, + std::vector > &) const CV_OVERRIDE + { + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + + PyObject* args = PyList_New(inputs.size()); + for(size_t i = 0; i < inputs.size(); ++i) + PyList_SET_ITEM(args, i, pyopencv_from_generic_vec(inputs[i])); + + PyObject* res = PyObject_CallMethodObjArgs(o, PyString_FromString("getMemoryShapes"), args, NULL); + Py_DECREF(args); + PyGILState_Release(gstate); + if (!res) + CV_Error(Error::StsNotImplemented, "Failed to call \"getMemoryShapes\" method"); + pyopencv_to_generic_vec(res, outputs, ArgInfo("", 0)); + return false; + } + + virtual void forward(std::vector &inputs, std::vector &outputs, std::vector &) CV_OVERRIDE + { + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + + std::vector inps(inputs.size()); + for (size_t i = 0; i < inputs.size(); ++i) + inps[i] = *inputs[i]; + + PyObject* args = pyopencv_from(inps); + PyObject* res = PyObject_CallMethodObjArgs(o, PyString_FromString("forward"), args, NULL); + Py_DECREF(args); + PyGILState_Release(gstate); + if (!res) + CV_Error(Error::StsNotImplemented, "Failed to call \"forward\" method"); + + std::vector pyOutputs; + pyopencv_to(res, pyOutputs, ArgInfo("", 0)); + + CV_Assert(pyOutputs.size() == outputs.size()); + for (size_t i = 0; i < outputs.size(); ++i) + { + CV_Assert(pyOutputs[i].size == outputs[i].size); + CV_Assert(pyOutputs[i].type() == outputs[i].type()); + pyOutputs[i].copyTo(outputs[i]); + } + } + + virtual void forward(InputArrayOfArrays, OutputArrayOfArrays, OutputArrayOfArrays) CV_OVERRIDE + { + CV_Error(Error::StsNotImplemented, ""); + } + +private: + // Map layers types to python classes. + static std::map > pyLayers; + PyObject* o; // Instance of implemented python layer. +}; + +std::map > pycvLayer::pyLayers; + +static PyObject *pyopencv_cv_dnn_registerLayer(PyObject*, PyObject *args, PyObject *kw) +{ + const char *keywords[] = { "type", "class", NULL }; + char* layerType; + PyObject *classInstance; + + if (!PyArg_ParseTupleAndKeywords(args, kw, "sO", (char**)keywords, &layerType, &classInstance)) + return NULL; + if (!PyCallable_Check(classInstance)) { + PyErr_SetString(PyExc_TypeError, "class must be callable"); + return NULL; + } + + pycvLayer::registerLayer(layerType, classInstance); + dnn::LayerFactory::registerLayer(layerType, pycvLayer::create); + Py_RETURN_NONE; +} + +static PyObject *pyopencv_cv_dnn_unregisterLayer(PyObject*, PyObject *args, PyObject *kw) +{ + const char *keywords[] = { "type", NULL }; + char* layerType; + + if (!PyArg_ParseTupleAndKeywords(args, kw, "s", (char**)keywords, &layerType)) + return NULL; + + pycvLayer::unregisterLayer(layerType); + dnn::LayerFactory::unregisterLayer(layerType); + Py_RETURN_NONE; +} + +#endif // HAVE_OPENCV_DNN diff --git a/modules/python/src2/cv2.cpp b/modules/python/src2/cv2.cpp index 2c2e9ecf61..4101022ec6 100644 --- a/modules/python/src2/cv2.cpp +++ b/modules/python/src2/cv2.cpp @@ -1783,6 +1783,10 @@ static PyMethodDef special_methods[] = { {"createTrackbar", pycvCreateTrackbar, METH_VARARGS, "createTrackbar(trackbarName, windowName, value, count, onChange) -> None"}, {"createButton", (PyCFunction)pycvCreateButton, METH_VARARGS | METH_KEYWORDS, "createButton(buttonName, onChange [, userData, buttonType, initialButtonState]) -> None"}, {"setMouseCallback", (PyCFunction)pycvSetMouseCallback, METH_VARARGS | METH_KEYWORDS, "setMouseCallback(windowName, onMouse [, param]) -> None"}, +#endif +#ifdef HAVE_OPENCV_DNN + {"dnn_registerLayer", (PyCFunction)pyopencv_cv_dnn_registerLayer, METH_VARARGS | METH_KEYWORDS, "registerLayer(type, class) -> None"}, + {"dnn_unregisterLayer", (PyCFunction)pyopencv_cv_dnn_unregisterLayer, METH_VARARGS | METH_KEYWORDS, "unregisterLayer(type) -> None"}, #endif {NULL, NULL}, }; diff --git a/samples/dnn/edge_detection.py b/samples/dnn/edge_detection.py new file mode 100644 index 0000000000..26119782db --- /dev/null +++ b/samples/dnn/edge_detection.py @@ -0,0 +1,69 @@ +import cv2 as cv +import argparse + +parser = argparse.ArgumentParser( + description='This sample shows how to define custom OpenCV deep learning layers in Python. ' + 'Holistically-Nested Edge Detection (https://arxiv.org/abs/1504.06375) neural network ' + 'is used as an example model. Find a pre-trained model at https://github.com/s9xie/hed.') +parser.add_argument('--input', help='Path to image or video. Skip to capture frames from camera') +parser.add_argument('--prototxt', help='Path to deploy.prototxt', required=True) +parser.add_argument('--caffemodel', help='Path to hed_pretrained_bsds.caffemodel', required=True) +parser.add_argument('--width', help='Resize input image to a specific width', default=500, type=int) +parser.add_argument('--height', help='Resize input image to a specific height', default=500, type=int) +args = parser.parse_args() + +#! [CropLayer] +class CropLayer(object): + def __init__(self, params, blobs): + self.xstart = 0 + self.xend = 0 + self.ystart = 0 + self.yend = 0 + + # Our layer receives two inputs. We need to crop the first input blob + # to match a shape of the second one (keeping batch size and number of channels) + def getMemoryShapes(self, inputs): + inputShape, targetShape = inputs[0], inputs[1] + batchSize, numChannels = inputShape[0], inputShape[1] + height, width = targetShape[2], targetShape[3] + + self.ystart = (inputShape[2] - targetShape[2]) / 2 + self.xstart = (inputShape[3] - targetShape[3]) / 2 + self.yend = self.ystart + height + self.xend = self.xstart + width + + return [[batchSize, numChannels, height, width]] + + def forward(self, inputs): + return [inputs[0][:,:,self.ystart:self.yend,self.xstart:self.xend]] +#! [CropLayer] + +#! [Register] +cv.dnn_registerLayer('Crop', CropLayer) +#! [Register] + +# Load the model. +net = cv.dnn.readNet(args.prototxt, args.caffemodel) + +kWinName = 'Holistically-Nested Edge Detection' +cv.namedWindow('Input', cv.WINDOW_NORMAL) +cv.namedWindow(kWinName, cv.WINDOW_NORMAL) + +cap = cv.VideoCapture(args.input if args.input else 0) +while cv.waitKey(1) < 0: + hasFrame, frame = cap.read() + if not hasFrame: + cv.waitKey() + break + + cv.imshow('Input', frame) + + inp = cv.dnn.blobFromImage(frame, scalefactor=1.0, size=(args.width, args.height), + mean=(104.00698793, 116.66876762, 122.67891434), + swapRB=False, crop=False) + net.setInput(inp) + + out = net.forward() + out = out[0, 0] + out = cv.resize(out, (frame.shape[1], frame.shape[0])) + cv.imshow(kWinName, out)