Commit 589286bb authored by Vitaliy Lyudvichenko's avatar Vitaliy Lyudvichenko

Adding of pyhon bindings for dnn module

parent a62f7e1d
......@@ -54,7 +54,7 @@ namespace dnn
//! @{
/** @brief Lightweight class for storing and processing a shape of blob (or anything else). */
struct BlobShape
struct CV_EXPORTS_W BlobShape
{
BlobShape(); //!< Creates [1, 1, 1, 1] shape @todo Make more clearer behavior.
explicit BlobShape(int s0); //!< Creates 1-dim shape [@p s0]
......@@ -154,7 +154,7 @@ namespace dnn
/** @brief Constructs Blob from existing Mat or UMat. */
Blob(InputArray data);
/** @brief Constucts 4-dimensional blob (so-called batch) from image or array of images.
/** @brief Constructs 4-dimensional blob (so-called batch) from image or array of images.
* @param image 2-dimensional multi-channel or 3-dimensional single-channel image (or array of such images)
* @param dstCn specifies size of second axis of ouptut blob
*/
......@@ -312,17 +312,17 @@ namespace dnn
public:
enum DataState
{
UNINITIALIZED,
HEAD_AT_MAT,
HEAD_AT_UMAT,
SYNCED
UNINITIALIZED = 0,
HEAD_AT_MAT = 1 << 0,
HEAD_AT_UMAT = 1 << 1,
SYNCED = HEAD_AT_MAT | HEAD_AT_UMAT
};
enum AllocFlag
{
ALLOC_MAT = 1,
ALLOC_UMAT = 2,
ALLOC_BOTH = 3
ALLOC_MAT = HEAD_AT_MAT,
ALLOC_UMAT = HEAD_AT_UMAT,
ALLOC_BOTH = SYNCED
};
};
......
......@@ -62,7 +62,8 @@ struct DictValue
DictValue(int p = 0) : type(Param::INT), pi(new AutoBuffer<int64,1>) { (*pi)[0] = p; } //!< Constructs integer scalar
DictValue(unsigned p) : type(Param::INT), pi(new AutoBuffer<int64,1>) { (*pi)[0] = p; } //!< Constructs integer scalar
DictValue(double p) : type(Param::REAL), pd(new AutoBuffer<double,1>) { (*pd)[0] = p; } //!< Constructs floating point scalar
DictValue(const String &p) : type(Param::STRING), ps(new AutoBuffer<String,1>) { (*ps)[0] = p; } //!< Constructs string scalar
DictValue(const String &s) : type(Param::STRING), ps(new AutoBuffer<String,1>) { (*ps)[0] = s; } //!< Constructs string scalar
DictValue(const char *s) : type(Param::STRING), ps(new AutoBuffer<String,1>) { (*ps)[0] = s; } //!< @overlaod
template<typename TypeIter>
static DictValue arrayInt(TypeIter begin, int size); //!< Constructs integer array
......
......@@ -86,7 +86,7 @@ namespace dnn //! This namespace is used for dnn module functionlaity.
public:
//! List of learned parameters must be stored here to allow read them by using Net::getParam().
std::vector<Blob> blobs;
CV_PROP_RW std::vector<Blob> blobs;
/** @brief Allocates internal buffers and output blobs with respect to the shape of inputs.
* @param[in] input vector of already allocated input blobs
......@@ -104,6 +104,18 @@ namespace dnn //! This namespace is used for dnn module functionlaity.
*/
virtual void forward(std::vector<Blob*> &input, std::vector<Blob> &output) = 0;
/** @brief @overload */
CV_WRAP void allocate(const std::vector<Blob> &inputs, CV_OUT std::vector<Blob> &outputs);
/** @brief @overload */
CV_WRAP std::vector<Blob> allocate(const std::vector<Blob> &inputs);
/** @brief @overload */
CV_WRAP void forward(const std::vector<Blob> &inputs, CV_IN_OUT std::vector<Blob> &outputs);
/** @brief Allocates layer and computes output. */
CV_WRAP void run(const std::vector<Blob> &inputs, CV_OUT std::vector<Blob> &outputs);
/** @brief Returns index of input blob into the input array.
* @param inputName label of input blob
*
......@@ -116,8 +128,8 @@ namespace dnn //! This namespace is used for dnn module functionlaity.
*/
virtual int outputNameToIndex(String outputName);
String name; //!< Name of the layer instance, can be used for logging or other internal purposes.
String type; //!< Type name which was used for creating layer by layer factory.
CV_PROP String name; //!< Name of the layer instance, can be used for logging or other internal purposes.
CV_PROP String type; //!< Type name which was used for creating layer by layer factory.
Layer();
explicit Layer(const LayerParams &params); //!< Initializes only #name, #type and #blobs fields.
......@@ -135,12 +147,15 @@ namespace dnn //! This namespace is used for dnn module functionlaity.
*
* This class supports reference counting of its instances, i. e. copies point to the same instance.
*/
class CV_EXPORTS_W Net
class CV_EXPORTS_W_SIMPLE Net
{
public:
Net(); //!< Default constructor.
~Net(); //!< Destructor frees the net only if there aren't references to the net anymore.
CV_WRAP Net(); //!< Default constructor.
CV_WRAP ~Net(); //!< Destructor frees the net only if there aren't references to the net anymore.
/** Returns true if there are no layers in the network. */
CV_WRAP bool empty() const;
/** @brief Adds new layer to the net.
* @param name unique name of the adding layer.
......@@ -157,13 +172,18 @@ namespace dnn //! This namespace is used for dnn module functionlaity.
/** @brief Converts string name of the layer to the integer identifier.
* @returns id of the layer, or -1 if the layer wasn't found.
*/
int getLayerId(const String &layer);
CV_WRAP int getLayerId(const String &layer);
CV_WRAP std::vector<String> getLayerNames() const;
/** @brief Container for strings and integers. */
typedef DictValue LayerId;
/** @brief Returns pointer to layer with specified name which the network use. */
CV_WRAP Ptr<Layer> getLayer(LayerId layerId);
/** @brief Delete layer for the network (not implemented yet) */
void deleteLayer(LayerId layer);
CV_WRAP void deleteLayer(LayerId layer);
/** @brief Connects output of the first layer to input of the second layer.
* @param outPin descriptor of the first layer output.
......@@ -178,7 +198,7 @@ namespace dnn //! This namespace is used for dnn module functionlaity.
*
* @see setNetInputs(), Layer::inputNameToIndex(), Layer::outputNameToIndex()
*/
void connect(String outPin, String inpPin);
CV_WRAP void connect(String outPin, String inpPin);
/** @brief Connects #@p outNum output of the first layer to #@p inNum input of the second layer.
* @param outLayerId identifier of the first layer
......@@ -188,19 +208,22 @@ namespace dnn //! This namespace is used for dnn module functionlaity.
*/
void connect(int outLayerId, int outNum, int inpLayerId, int inpNum);
/** @brief Sets ouputs names of the network input pseudo layer.
/** @brief Sets outputs names of the network input pseudo layer.
*
* Each net always has special own the network input pseudo layer with id=0.
* This layer stores the user blobs only and don't make any computations.
* In fact, this layer provides the only way to pass user data into the network.
* As any other layer, this layer can label its outputs and this function provides an easy way to do this.
*/
void setNetInputs(const std::vector<String> &inputBlobNames);
CV_WRAP void setNetInputs(const std::vector<String> &inputBlobNames);
/** @brief Initializes and allocates all layers. */
CV_WRAP void allocate();
/** @brief Runs forward pass for the whole network */
void forward();
/** @brief Runs forward pass to compute output of layer @p toLayer */
void forward(LayerId toLayer);
/** @brief Runs forward pass to compute output of layer @p toLayer.
* @detail By default runs forward pass for the whole network.
*/
CV_WRAP void forward(LayerId toLayer = String());
/** @brief Runs forward pass to compute output of layer @p toLayer, but computations start from @p startLayer */
void forward(LayerId startLayer, LayerId toLayer);
/** @overload */
......@@ -222,12 +245,13 @@ namespace dnn //! This namespace is used for dnn module functionlaity.
* @note If updating blob is not empty then @p blob must have the same shape,
* because network reshaping is not implemented yet.
*/
void setBlob(String outputName, const Blob &blob);
CV_WRAP void setBlob(String outputName, const Blob &blob);
/** @brief Returns the layer output blob.
* @param outputName the descriptor of the returning layer output blob.
* @see connect(String, String)
*/
Blob getBlob(String outputName);
CV_WRAP Blob getBlob(String outputName);
/** @brief Sets the new value for the learned param of the layer.
* @param layer name or id of the layer.
......@@ -237,13 +261,14 @@ namespace dnn //! This namespace is used for dnn module functionlaity.
* @note If shape of the new blob differs from the previous shape,
* then the following forward pass may fail.
*/
void setParam(LayerId layer, int numParam, const Blob &blob);
CV_WRAP void setParam(LayerId layer, int numParam, const Blob &blob);
/** @brief Returns parameter blob of the layer.
* @param layer name or id of the layer.
* @param numParam index of the layer parameter in the Layer::blobs array.
* @see Layer::blobs
*/
Blob getParam(LayerId layer, int numParam = 0);
CV_WRAP Blob getParam(LayerId layer, int numParam = 0);
private:
......@@ -252,12 +277,12 @@ namespace dnn //! This namespace is used for dnn module functionlaity.
};
/** @brief Small interface class for loading trained serialized models of different dnn-frameworks. */
class Importer
class CV_EXPORTS_W Importer
{
public:
/** @brief Adds loaded layers into the @p net and sets connetions between them. */
virtual void populateNet(Net net) = 0;
/** @brief Adds loaded layers into the @p net and sets connections between them. */
CV_WRAP virtual void populateNet(Net net) = 0;
virtual ~Importer();
};
......@@ -267,7 +292,12 @@ namespace dnn //! This namespace is used for dnn module functionlaity.
* @param caffeModel path to the .caffemodel file with learned network.
* @returns Pointer to the created importer, NULL in failure cases.
*/
CV_EXPORTS Ptr<Importer> createCaffeImporter(const String &prototxt, const String &caffeModel = String());
CV_EXPORTS_W Ptr<Importer> createCaffeImporter(const String &prototxt, const String &caffeModel = String());
/** @brief Reads a network model stored in Caffe model files.
* @detail This is shortcut consisting from createCaffeImporter and Net::populateNet calls.
*/
CV_EXPORTS_W Net readNetFromCaffe(const String &prototxt, const String &caffeModel = String());
/** @brief Creates the importer of <a href="http://torch.ch">Torch7</a> framework network.
* @param filename path to the file, dumped from Torch by using torch.save() function.
......@@ -294,12 +324,12 @@ namespace dnn //! This namespace is used for dnn module functionlaity.
*
* Also some equivalents of these classes from cunn, cudnn, and fbcunn may be successfully imported.
*/
CV_EXPORTS Ptr<Importer> createTorchImporter(const String &filename, bool isBinary = true);
CV_EXPORTS_W Ptr<Importer> createTorchImporter(const String &filename, bool isBinary = true);
/** @brief Loads blob which was serialized as torch.Tensor object of Torch7 framework.
* @warning This function has the same limitations as createTorchImporter().
*/
CV_EXPORTS Blob readTorchBlob(const String &filename, bool isBinary = true);
CV_EXPORTS_W Blob readTorchBlob(const String &filename, bool isBinary = true);
//! @}
}
......
from __future__ import print_function
import numpy as np
import cv2
from cv2 import dnn
import timeit
def prepare_image(img):
img = cv2.resize(img, (224, 224))
#convert interleaved image (RGBRGB) to planar(RRGGBB)
blob = np.moveaxis(img, 2, 0)
blob = np.reshape(blob.astype(np.float32), (-1, 3, 224, 224))
return blob
def timeit_forward(net):
print("OpenCL:", cv2.ocl.useOpenCL())
print("Runtime:", timeit.timeit(lambda: net.forward(), number=10))
def get_class_list():
with open('synset_words.txt', 'rt') as f:
return [ x[x.find(" ") + 1 :] for x in f ]
blob = prepare_image(cv2.imread('space_shuttle.jpg'))
print("Input:", blob.shape, blob.dtype)
cv2.ocl.setUseOpenCL(True) #Disable OCL if you want
net = dnn.readNetFromCaffe('bvlc_googlenet.prototxt', 'bvlc_googlenet.caffemodel')
net.setBlob(".data", blob)
net.forward()
timeit_forward(net) #Uncomment to check performance
prob = net.getBlob("prob")
print("Output:", prob.shape, prob.dtype)
classes = get_class_list()
print("Best match", classes[prob.argmax()])
\ No newline at end of file
#ifdef HAVE_OPENCV_DNN
typedef dnn::DictValue LayerId;
typedef std::vector<cv::dnn::Blob> vector_Blob;
template<>
bool pyopencv_to(PyObject *o, dnn::Blob &blob, const char *name);
template<> struct pyopencvVecConverter<dnn::Blob>
{
static bool to(PyObject* obj, std::vector<dnn::Blob>& value, const ArgInfo info)
{
if (PyArray_Check(obj))
{
value.resize(1);
return pyopencv_to(obj, value[0], info.name);
}
return pyopencv_to_generic_vec(obj, value, info);
}
static PyObject* from(const std::vector<dnn::Blob>& value)
{
return pyopencv_from_generic_vec(value);
}
};
template<>
bool pyopencv_to(PyObject *o, std::vector<dnn::Blob> &blobs, const char *name) //required for Layer::blobs RW
{
return pyopencvVecConverter<dnn::Blob>::to(o, blobs, ArgInfo(name, false));
}
template<>
bool pyopencv_to(PyObject *o, dnn::Blob &blob, const char *name)
{
Mat &dst = blob.matRef();
if (!pyopencv_to(o, dst, name))
return false;
if (PyArray_Check(o)) //try fix channels
{
PyArrayObject* oarr = (PyArrayObject*) o;
if (PyArray_NDIM(oarr) == dst.dims)
return true;
int ndims = PyArray_NDIM(oarr);
std::vector<int> shape(ndims);
const npy_intp* _sizes = PyArray_DIMS(oarr);
for (int i = 0; i < ndims; i++)
shape[i] = (int)_sizes[i];
dst = dst.reshape(1, ndims, &shape[0]);
}
return true;
}
template<>
PyObject *pyopencv_from(const dnn::Blob &blob)
{
return pyopencv_from(blob.matRefConst());
}
template<>
bool pyopencv_to(PyObject *o, dnn::DictValue &dv, const char *name)
{
(void)name;
if (!o || o == Py_None)
return true; //Current state will be used
else if (PyLong_Check(o))
{
dv = dnn::DictValue(PyLong_AsLong(o));
return true;
}
else if (PyFloat_Check(o))
{
dv = dnn::DictValue(PyFloat_AS_DOUBLE(o));
return true;
}
else if (PyString_Check(o))
{
dv = dnn::DictValue(String(PyString_AsString(o)));
return true;
}
else
return false;
}
template<>
bool pyopencv_to(PyObject *o, dnn::BlobShape &shape, const char *name)
{
std::vector<int> data;
if (!pyopencv_to_generic_vec(o, data, ArgInfo(name, false)))
return false;
shape = data.size() ? dnn::BlobShape((int)data.size(), &data[0]) : dnn::BlobShape::empty();
return true;
}
template<>
PyObject *pyopencv_from(const dnn::BlobShape &shape)
{
std::vector<int> data(shape.ptr(), shape.ptr() + shape.dims());
return pyopencv_from_generic_vec(data);
}
#endif
\ No newline at end of file
......@@ -63,16 +63,15 @@ Blob::Blob(InputArray data)
#ifndef CV_DNN_UMAT
m = data.getMat();
#else
CV_Assert(data.isMat() || data.isUMat());
if (data.isMat())
if (data.isUMat())
{
m = data.getMat();
state = HEAD_AT_MAT;
um = data.getUMat();
state = HEAD_AT_UMAT;
}
else
{
um = data.getUMat();
state = HEAD_AT_UMAT;
m = data.getMat();
state = HEAD_AT_MAT;
}
#endif
}
......
......@@ -353,3 +353,12 @@ Ptr<Importer> cv::dnn::createCaffeImporter(const String&, const String&)
}
#endif //HAVE_PROTOBUF
Net cv::dnn::readNetFromCaffe(const String &prototxt, const String &caffeModel /*= String()*/)
{
Ptr<Importer> caffeImporter = createCaffeImporter(prototxt, caffeModel);
Net net;
if (caffeImporter)
caffeImporter->populateNet(net);
return net;
}
......@@ -44,6 +44,7 @@
#include <algorithm>
#include <iostream>
#include <sstream>
#include <iterator>
using namespace cv;
using namespace cv::dnn;
......@@ -127,7 +128,7 @@ struct LayerData
};
//fake layer containing network input blobs
struct NetInputLayer : public Layer
struct DataLayer : public Layer
{
void allocate(const std::vector<Blob*>&, std::vector<Blob>&) {}
void forward(std::vector<Blob*>&, std::vector<Blob>&) {}
......@@ -152,7 +153,7 @@ struct Net::Impl
Impl()
{
//allocate fake net input layer
netInputLayer = Ptr<NetInputLayer>(new NetInputLayer());
netInputLayer = Ptr<DataLayer>(new DataLayer());
LayerData &inpl = layers.insert( make_pair(0, LayerData()) ).first->second;
inpl.id = 0;
inpl.name = "_input";
......@@ -163,7 +164,7 @@ struct Net::Impl
netWasAllocated = false;
}
Ptr<NetInputLayer> netInputLayer;
Ptr<DataLayer> netInputLayer;
std::vector<int> netOutputs;
typedef std::map<int, LayerData> MapIdToLayerData;
......@@ -328,11 +329,16 @@ struct Net::Impl
netOutputs.push_back(lid);
}
#ifndef NDEBUG
std::cout << "\nNet Outputs(" << netOutputs.size() << "):\n";
for (size_t i = 0; i < netOutputs.size(); i++)
std::cout << layers[netOutputs[i]].name << std::endl;
std::cout << layers[netOutputs[i]].name << "\n";
#endif
}
#define CV_RETHROW_ERROR(err, newmsg)\
cv::error(err.code, newmsg, err.func.c_str(), err.file.c_str(), err.line)
void allocateLayer(int lid)
{
LayerData &ld = layers[lid];
......@@ -361,7 +367,15 @@ struct Net::Impl
//allocate layer
ld.outputBlobs.resize(std::max((size_t)1, ld.requiredOutputs.size())); //layer produce at least one output blob
ld.getLayerInstance()->allocate(ld.inputBlobs, ld.outputBlobs);
Ptr<Layer> layerPtr = ld.getLayerInstance();
try
{
layerPtr->allocate(ld.inputBlobs, ld.outputBlobs);
}
catch (const cv::Exception &err)
{
CV_RETHROW_ERROR(err, format("The following error occured while making allocate() for layer \"%s\": %s", ld.name.c_str(), err.err.c_str()));
}
ld.flag = 1;
}
......@@ -399,7 +413,14 @@ struct Net::Impl
}
//forward itself
ld.layerInstance->forward(ld.inputBlobs, ld.outputBlobs);
try
{
ld.layerInstance->forward(ld.inputBlobs, ld.outputBlobs);
}
catch (const cv::Exception &err)
{
CV_RETHROW_ERROR(err, format("The following error occured while making forward() for layer \"%s\": %s", ld.name.c_str(), err.err.c_str()));
}
ld.flag = 1;
}
......@@ -417,12 +438,10 @@ struct Net::Impl
Net::Net() : impl(new Net::Impl)
{
}
Net::~Net()
{
}
int Net::addLayer(const String &name, const String &type, LayerParams &params)
......@@ -469,16 +488,19 @@ void Net::connect(String _outPin, String _inPin)
impl->connect(outPin.lid, outPin.oid, inpPin.lid, inpPin.oid);
}
void Net::forward()
void Net::allocate()
{
impl->setUpNet();
impl->forwardAll();
}
void Net::forward(LayerId toLayer)
{
impl->setUpNet();
impl->forwardLayer(impl->getLayerData(toLayer));
if (toLayer.isString() && toLayer.get<String>().empty())
impl->forwardAll();
else
impl->forwardLayer(impl->getLayerData(toLayer));
}
void Net::setNetInputs(const std::vector<String> &inputBlobNames)
......@@ -521,6 +543,16 @@ Blob Net::getParam(LayerId layer, int numParam)
return layerBlobs[numParam];
}
void Net::setParam(LayerId layer, int numParam, const Blob &blob)
{
LayerData &ld = impl->getLayerData(layer);
std::vector<Blob> &layerBlobs = ld.layerInstance->blobs;
CV_Assert(numParam < (int)layerBlobs.size());
//we don't make strong checks, use this function carefully
layerBlobs[numParam] = blob;
}
int Net::getLayerId(const String &layer)
{
return impl->getLayerId(layer);
......@@ -531,6 +563,34 @@ void Net::deleteLayer(LayerId)
CV_Error(Error::StsNotImplemented, "");
}
Ptr<Layer> Net::getLayer(LayerId layerId)
{
LayerData &ld = impl->getLayerData(layerId);
if (!ld.layerInstance)
CV_Error(Error::StsNullPtr, format("Requseted layer \"%s\" was not initialized", ld.name.c_str()));
return ld.layerInstance;
}
std::vector<String> Net::getLayerNames() const
{
std::vector<String> res;
res.reserve(impl->layers.size());
Impl::MapIdToLayerData::iterator it;
for (it = impl->layers.begin(); it != impl->layers.end(); it++)
{
if (it->second.id) //skip Data layer
res.push_back(it->second.name);
}
return res;
}
bool Net::empty() const
{
return impl->layers.size() <= 1; //first layer is default Data layer
}
//////////////////////////////////////////////////////////////////////////
Importer::~Importer() {}
......@@ -560,6 +620,43 @@ int Layer::outputNameToIndex(String)
return -1;
}
template <typename T>
static void vecToPVec(const std::vector<T> &v, std::vector<T*> &pv)
{
pv.resize(v.size());
for (size_t i = 0; i < v.size(); i++)
pv[i] = const_cast<T*>(&v[i]);
}
void Layer::allocate(const std::vector<Blob> &inputs, std::vector<Blob> &outputs)
{
std::vector<Blob*> inputsp;
vecToPVec(inputs, inputsp);
this->allocate(inputsp, outputs);
}
std::vector<Blob> Layer::allocate(const std::vector<Blob> &inputs)
{
std::vector<Blob> outputs;
this->allocate(inputs, outputs);
return outputs;
}
void Layer::forward(const std::vector<Blob> &inputs, std::vector<Blob> &outputs)
{
std::vector<Blob*> inputsp;
vecToPVec(inputs, inputsp);
this->forward(inputsp, outputs);
}
void Layer::run(const std::vector<Blob> &inputs, std::vector<Blob> &outputs)
{
std::vector<Blob*> inputsp;
vecToPVec(inputs, inputsp);
this->allocate(inputsp, outputs);
this->forward(inputsp, outputs);
}
Layer::~Layer() {}
//////////////////////////////////////////////////////////////////////////
......
......@@ -692,13 +692,13 @@ struct TorchImporter : public ::cv::dnn::Importer
}
};
CV_EXPORTS Ptr<Importer> createTorchImporter(const String &filename, bool isBinary)
Ptr<Importer> createTorchImporter(const String &filename, bool isBinary)
{
return Ptr<Importer>(new TorchImporter(filename, isBinary));
}
CV_EXPORTS Blob readTorchBlob(const String &filename, bool isBinary)
Blob readTorchBlob(const String &filename, bool isBinary)
{
Ptr<TorchImporter> importer(new TorchImporter(filename, isBinary));
importer->readObject();
......@@ -709,13 +709,13 @@ CV_EXPORTS Blob readTorchBlob(const String &filename, bool isBinary)
#else //ENABLE_TORCH_IMPORTER
CV_EXPORTS Ptr<Importer> createTorchImporter(const String&, bool)
Ptr<Importer> createTorchImporter(const String&, bool)
{
CV_Error(Error::StsNotImplemented, "Module was build without Torch importer");
return Ptr<Importer>();
}
CV_EXPORTS Blob readTorchMat(const String&, bool)
Blob readTorchBlob(const String&, bool)
{
CV_Error(Error::StsNotImplemented, "Module was build without Torch importer");
return Blob();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment