Classes listed here, in fact, provides C++ API for creating intances of bult-in layers.
In addition to this way of layers instantiation, there is a more common factory API (see @ref dnnLayerFactory), it allows to create layers dynamically (by name) and register new ones.
- You can use both API, but factory API is less convinient for native C++ programming and basically designed for use inside importers (see @ref Importer, @ref createCaffeImporter(), @ref createTorchImporter()).
+ You can use both API, but factory API is less convinient for native C++ programming and basically designed for use inside importers (see @ref readNetFromCaffe(), @ref readNetFromTorch(), @ref readNetFromTensorflow()).
Bult-in layers partially reproduce functionality of corresponding Caffe and Torch7 layers.
In partuclar, the following layers and Caffe @ref Importer were tested to reproduce <a href="http://caffe.berkeleyvision.org/tutorial/layers.html">Caffe</a> functionality:
static Ptr<ReLULayer> create(const LayerParams ¶ms);
};
+ class CV_EXPORTS ReLU6Layer : public ActivationLayer
+ {
+ public:
+ static Ptr<ReLU6Layer> create(const LayerParams ¶ms);
+ };
+
class CV_EXPORTS ChannelsPReLULayer : public ActivationLayer
{
public:
Ptr<Impl> impl;
};
- /** @brief Small interface class for loading trained serialized models of different dnn-frameworks. */
+ /**
+ * @deprecated Deprecated as external interface. Will be for internal needs only.
+ * @brief Small interface class for loading trained serialized models of different dnn-frameworks. */
class CV_EXPORTS_W Importer : public Algorithm
{
public:
/** @brief Adds loaded layers into the @p net and sets connections between them. */
- CV_WRAP virtual void populateNet(Net net) = 0;
+ CV_DEPRECATED CV_WRAP virtual void populateNet(Net net) = 0;
virtual ~Importer();
};
- /** @brief Creates the importer of <a href="http://caffe.berkeleyvision.org">Caffe</a> framework network.
+ /**
+ * @deprecated Use @ref readNetFromCaffe instead.
+ * @brief Creates the importer of <a href="http://caffe.berkeleyvision.org">Caffe</a> framework network.
* @param prototxt path to the .prototxt file with text description of the network architecture.
* @param caffeModel path to the .caffemodel file with learned network.
* @returns Pointer to the created importer, NULL in failure cases.
*/
- CV_EXPORTS_W Ptr<Importer> createCaffeImporter(const String &prototxt, const String &caffeModel = String());
+ CV_DEPRECATED CV_EXPORTS_W Ptr<Importer> createCaffeImporter(const String &prototxt, const String &caffeModel = String());
/** @brief Reads a network model stored in Caffe model files.
* @details This is shortcut consisting from createCaffeImporter and Net::populateNet calls.
*/
CV_EXPORTS_W Net readNetFromTorch(const String &model, bool isBinary = true);
- /** @brief Creates the importer of <a href="http://www.tensorflow.org">TensorFlow</a> framework network.
+ /**
+ * @deprecated Use @ref readNetFromTensorflow instead.
+ * @brief Creates the importer of <a href="http://www.tensorflow.org">TensorFlow</a> framework network.
* @param model path to the .pb file with binary protobuf description of the network architecture.
* @returns Pointer to the created importer, NULL in failure cases.
*/
- CV_EXPORTS_W Ptr<Importer> createTensorflowImporter(const String &model);
+ CV_DEPRECATED CV_EXPORTS_W Ptr<Importer> createTensorflowImporter(const String &model);
- /** @brief Creates the importer of <a href="http://torch.ch">Torch7</a> framework network.
+ /**
+ * @deprecated Use @ref readNetFromTorch instead.
+ * @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.
* @param isBinary specifies whether the network was serialized in ascii mode or binary.
* @returns Pointer to the created importer, NULL in failure cases.
*
* Also some equivalents of these classes from cunn, cudnn, and fbcunn may be successfully imported.
*/
- CV_EXPORTS_W Ptr<Importer> createTorchImporter(const String &filename, bool isBinary = true);
+ CV_DEPRECATED 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_W Mat blobFromImages(const std::vector<Mat>& images, double scalefactor=1.0,
Size size = Size(), const Scalar& mean = Scalar(), bool swapRB=true);
+ /** @brief Convert all weights of Caffe network to half precision floating point.
+ * @param src Path to origin model from Caffe framework contains single
+ * precision floating point weights (usually has `.caffemodel` extension).
+ * @param dst Path to destination model with updated weights.
+ *
+ * @note Shrinked model has no origin float32 weights so it can't be used
+ * in origin Caffe framework anymore. However the structure of data
+ * is taken from NVidia's Caffe fork: https://github.com/NVIDIA/caffe.
+ * So the resulting model may be used there.
+ */
+ CV_EXPORTS_W void shrinkCaffeModel(const String& src, const String& dst);
+
+
//! @}
CV__DNN_EXPERIMENTAL_NS_END
}
blobShapeFromProto(pbBlob, shape);
dstBlob.create((int)shape.size(), &shape[0], CV_32F);
- CV_Assert(pbBlob.data_size() == (int)dstBlob.total());
-
- CV_DbgAssert(pbBlob.GetDescriptor()->FindFieldByLowercaseName("data")->cpp_type() == FieldDescriptor::CPPTYPE_FLOAT);
float *dstData = dstBlob.ptr<float>();
+ if (pbBlob.data_size())
+ {
+ // Single precision floats.
+ CV_Assert(pbBlob.data_size() == (int)dstBlob.total());
+
+ CV_DbgAssert(pbBlob.GetDescriptor()->FindFieldByLowercaseName("data")->cpp_type() == FieldDescriptor::CPPTYPE_FLOAT);
- for (int i = 0; i < pbBlob.data_size(); i++)
- dstData[i] = pbBlob.data(i);
+ for (int i = 0; i < pbBlob.data_size(); i++)
+ dstData[i] = pbBlob.data(i);
+ }
+ else
+ {
+ // Half precision floats.
+ CV_Assert(pbBlob.raw_data_type() == caffe::FLOAT16);
+ std::string raw_data = pbBlob.raw_data();
+
+ CV_Assert(raw_data.size() / 2 == (int)dstBlob.total());
+
+ Mat halfs((int)shape.size(), &shape[0], CV_16SC1, (void*)raw_data.c_str());
+ convertFp16(halfs, dstBlob);
+ }
}
void extractBinaryLayerParms(const caffe::LayerParameter& layer, LayerParams& layerParams)
return Ptr<Importer>(new CaffeImporter(prototxt.c_str(), caffeModel.c_str()));
}
- #else //HAVE_PROTOBUF
-
- Ptr<Importer> createCaffeImporter(const String&, const String&)
- {
- CV_Error(cv::Error::StsNotImplemented, "libprotobuf required to import data from Caffe models");
- return Ptr<Importer>();
- }
-
- #endif //HAVE_PROTOBUF
-
Net readNetFromCaffe(const String &prototxt, const String &caffeModel /*= String()*/)
{
- Ptr<Importer> caffeImporter = createCaffeImporter(prototxt, caffeModel);
+ CaffeImporter caffeImporter(prototxt.c_str(), caffeModel.c_str());
Net net;
- if (caffeImporter)
- caffeImporter->populateNet(net);
+ caffeImporter.populateNet(net);
return net;
}
+ #endif //HAVE_PROTOBUF
+
CV__DNN_EXPERIMENTAL_NS_END
}} // namespace
switch (tensor.dtype())
{
case tensorflow::DT_FLOAT:
- return Mat(1, content.size() / sizeof(float), CV_32FC1, (void*)content.c_str()).clone();
+ {
+ if (!content.empty())
+ return Mat(1, content.size() / sizeof(float), CV_32FC1, (void*)content.c_str()).clone();
+ else
+ {
+ const RepeatedField<float>& field = tensor.float_val();
+ CV_Assert(!field.empty());
+ return Mat(1, field.size(), CV_32FC1, (void*)field.data()).clone();
+ }
+ }
case tensorflow::DT_DOUBLE:
- return Mat(1, content.size() / sizeof(double), CV_64FC1, (void*)content.c_str()).clone();
+ {
+ if (!content.empty())
+ return Mat(1, content.size() / sizeof(double), CV_64FC1, (void*)content.c_str()).clone();
+ else
+ {
+ const RepeatedField<double>& field = tensor.double_val();
+ CV_Assert(!field.empty());
+ return Mat(1, field.size(), CV_64FC1, (void*)field.data()).clone();
+ }
+ }
case tensorflow::DT_INT32:
- return Mat(1, content.size() / sizeof(int32_t), CV_32SC1, (void*)content.c_str()).clone();
+ {
+ if (!content.empty())
+ return Mat(1, content.size() / sizeof(int32_t), CV_32SC1, (void*)content.c_str()).clone();
+ else
+ {
+ const RepeatedField<int32_t>& field = tensor.int_val();
+ CV_Assert(!field.empty());
+ return Mat(1, field.size(), CV_32SC1, (void*)field.data()).clone();
+ }
+ }
case tensorflow::DT_HALF:
{
Mat halfs;
if(layers_to_ignore.find(li) != layers_to_ignore.end())
continue;
- if (type == "Conv2D" || type == "SpaceToBatchND")
+ if (type == "Conv2D" || type == "SpaceToBatchND" || type == "DepthwiseConv2dNative")
{
// The first node of dilated convolution subgraph.
// Extract input node, dilation rate and paddings.
}
kernelFromTensor(getConstBlob(layer, value_id), layerParams.blobs[0]);
- const int* kshape = layerParams.blobs[0].size.p;
+ int* kshape = layerParams.blobs[0].size.p;
+ if (type == "DepthwiseConv2dNative")
+ {
+ const int chMultiplier = kshape[0];
+ const int inCh = kshape[1];
+ const int height = kshape[2];
+ const int width = kshape[3];
+
+ Mat copy = layerParams.blobs[0].clone();
+ float* src = (float*)copy.data;
+ float* dst = (float*)layerParams.blobs[0].data;
+ for (int i = 0; i < chMultiplier; ++i)
+ for (int j = 0; j < inCh; ++j)
+ for (int s = 0; s < height * width; ++s)
+ {
+ int src_i = (i * inCh + j) * height * width + s;
+ int dst_i = (j * chMultiplier + i) * height* width + s;
+ dst[dst_i] = src[src_i];
+ }
+ kshape[0] = inCh * chMultiplier;
+ kshape[1] = 1;
+ }
layerParams.set("kernel_h", kshape[2]);
layerParams.set("kernel_w", kshape[3]);
layerParams.set("num_output", kshape[0]);
layerParams.blobs.resize(1);
StrIntVector next_layers = getNextLayers(net, name, "BiasAdd");
+ if (next_layers.empty())
+ {
+ next_layers = getNextLayers(net, name, "Add");
+ }
if (next_layers.size() == 1) {
layerParams.set("bias_term", true);
layerParams.blobs.resize(2);
{
// Multiplication by constant.
CV_Assert(layer.input_size() == 2);
+ Mat scaleMat = getTensorContent(getConstBlob(layer, value_id));
+ CV_Assert(scaleMat.type() == CV_32FC1);
- float scale;
- if (!getConstBlob(layer, value_id).float_val().empty())
- scale = getConstBlob(layer, value_id).float_val()[0];
- else
+ int id;
+ if (scaleMat.total() == 1) // is a scalar.
{
- Mat scaleMat;
- blobFromTensor(getConstBlob(layer, value_id), scaleMat);
- CV_Assert(scaleMat.total() == 1 && scaleMat.type() == CV_32FC1);
- scale = scaleMat.at<float>(0, 0);
+ layerParams.set("scale", scaleMat.at<float>(0));
+ id = dstNet.addLayer(name, "Power", layerParams);
+ }
+ else // is a vector
+ {
+ layerParams.blobs.resize(1, scaleMat);
+ id = dstNet.addLayer(name, "Scale", layerParams);
}
- layerParams.set("scale", scale);
-
- int id = dstNet.addLayer(name, "Power", layerParams);
layer_id[name] = id;
Pin inp0 = parsePin(layer.input(0));
}
else if (type == "Abs" || type == "Tanh" || type == "Sigmoid" ||
type == "Relu" || type == "Elu" || type == "Softmax" ||
- type == "Identity")
+ type == "Identity" || type == "Relu6")
{
std::string dnnType = type;
if (type == "Abs") dnnType = "AbsVal";
else if (type == "Tanh") dnnType = "TanH";
else if (type == "Relu") dnnType = "ReLU";
+ else if (type == "Relu6") dnnType = "ReLU6";
else if (type == "Elu") dnnType = "ELU";
int id = dstNet.addLayer(name, dnnType, layerParams);
Net readNetFromTensorflow(const String &model)
{
- Ptr<Importer> importer = createTensorflowImporter(model);
+ TFImporter importer(model.c_str());
Net net;
- if (importer)
- importer->populateNet(net);
+ importer.populateNet(net);
return net;
}
TEST(Test_Caffe, read_gtsrb)
{
- Net net;
- {
- Ptr<Importer> importer = createCaffeImporter(_tf("gtsrb.prototxt"), "");
- ASSERT_TRUE(importer != NULL);
- importer->populateNet(net);
- }
+ Net net = readNetFromCaffe(_tf("gtsrb.prototxt"));
+ ASSERT_FALSE(net.empty());
}
TEST(Test_Caffe, read_googlenet)
{
- Net net;
- {
- Ptr<Importer> importer = createCaffeImporter(_tf("bvlc_googlenet.prototxt"), "");
- ASSERT_TRUE(importer != NULL);
- importer->populateNet(net);
- }
+ Net net = readNetFromCaffe(_tf("bvlc_googlenet.prototxt"));
+ ASSERT_FALSE(net.empty());
}
TEST(Reproducibility_AlexNet, Accuracy)
{
const string proto = findDataFile("dnn/bvlc_alexnet.prototxt", false);
const string model = findDataFile("dnn/bvlc_alexnet.caffemodel", false);
- Ptr<Importer> importer = createCaffeImporter(proto, model);
- ASSERT_TRUE(importer != NULL);
- importer->populateNet(net);
+ net = readNetFromCaffe(proto, model);
+ ASSERT_FALSE(net.empty());
}
Mat sample = imread(_tf("grace_hopper_227.png"));
{
const string proto = findDataFile("dnn/fcn8s-heavy-pascal.prototxt", false);
const string model = findDataFile("dnn/fcn8s-heavy-pascal.caffemodel", false);
- Ptr<Importer> importer = createCaffeImporter(proto, model);
- ASSERT_TRUE(importer != NULL);
- importer->populateNet(net);
+ net = readNetFromCaffe(proto, model);
+ ASSERT_FALSE(net.empty());
}
Mat sample = imread(_tf("street.png"));
{
const string proto = findDataFile("dnn/ssd_vgg16.prototxt", false);
const string model = findDataFile("dnn/VGG_ILSVRC2016_SSD_300x300_iter_440000.caffemodel", false);
- Ptr<Importer> importer = createCaffeImporter(proto, model);
- ASSERT_TRUE(importer != NULL);
- importer->populateNet(net);
+ net = readNetFromCaffe(proto, model);
+ ASSERT_FALSE(net.empty());
}
Mat sample = imread(_tf("street.png"));
normAssert(ref, out);
}
+TEST(Reproducibility_AlexNet_fp16, Accuracy)
+{
+ const float l1 = 1e-5;
+ const float lInf = 2e-4;
+
+ const string proto = findDataFile("dnn/bvlc_alexnet.prototxt", false);
+ const string model = findDataFile("dnn/bvlc_alexnet.caffemodel", false);
+
+ shrinkCaffeModel(model, "bvlc_alexnet.caffemodel_fp16");
+ Net net = readNetFromCaffe(proto, "bvlc_alexnet.caffemodel_fp16");
+
+ Mat sample = imread(findDataFile("dnn/grace_hopper_227.png", false));
+
+ net.setInput(blobFromImage(sample, 1, Size(227, 227)));
+ Mat out = net.forward();
+ Mat ref = blobFromNPY(findDataFile("dnn/caffe_alexnet_prob.npy", false));
+ normAssert(ref, out, "", l1, lInf);
+}
+
+TEST(Reproducibility_GoogLeNet_fp16, Accuracy)
+{
+ const float l1 = 1e-5;
+ const float lInf = 3e-3;
+
+ const string proto = findDataFile("dnn/bvlc_googlenet.prototxt", false);
+ const string model = findDataFile("dnn/bvlc_googlenet.caffemodel", false);
+
+ shrinkCaffeModel(model, "bvlc_googlenet.caffemodel_fp16");
+ Net net = readNetFromCaffe(proto, "bvlc_googlenet.caffemodel_fp16");
+
+ std::vector<Mat> inpMats;
+ inpMats.push_back( imread(_tf("googlenet_0.png")) );
+ inpMats.push_back( imread(_tf("googlenet_1.png")) );
+ ASSERT_TRUE(!inpMats[0].empty() && !inpMats[1].empty());
+
+ net.setInput(blobFromImages(inpMats), "data");
+ Mat out = net.forward("prob");
+
+ Mat ref = blobFromNPY(_tf("googlenet_prob.npy"));
+ normAssert(out, ref, "", l1, lInf);
+}
+
}
Net net;
{
const string model = findDataFile("dnn/tensorflow_inception_graph.pb", false);
- Ptr<Importer> importer = createTensorflowImporter(model);
- ASSERT_TRUE(importer != NULL);
- importer->populateNet(net);
+ net = readNetFromTensorflow(model);
+ ASSERT_FALSE(net.empty());
}
Mat sample = imread(_tf("grace_hopper_227.png"));
Net net;
{
const string model = findDataFile("dnn/tensorflow_inception_graph.pb", false);
- Ptr<Importer> importer = createTensorflowImporter(model);
- ASSERT_TRUE(importer != NULL);
- importer->populateNet(net);
+ net = readNetFromTensorflow(model);
+ ASSERT_FALSE(net.empty());
}
Mat sample = imread(_tf("grace_hopper_227.png"));
normAssert(target, output, "", l1, lInf);
}
-TEST(Test_TensorFlow, single_conv)
+TEST(Test_TensorFlow, conv)
{
runTensorFlowNet("single_conv");
runTensorFlowNet("atrous_conv2d_valid");
runTensorFlowNet("atrous_conv2d_same");
+ runTensorFlowNet("depthwise_conv2d");
}
TEST(Test_TensorFlow, padding)
runTensorFlowNet("pad_and_concat");
}
-TEST(Test_TensorFlow, fused_batch_norm)
+TEST(Test_TensorFlow, batch_norm)
{
+ runTensorFlowNet("batch_norm");
runTensorFlowNet("fused_batch_norm");
}
runTensorFlowNet("deconvolution");
}
+TEST(Test_TensorFlow, matmul)
+{
+ runTensorFlowNet("matmul");
+}
+
TEST(Test_TensorFlow, fp16)
{
const float l1 = 1e-3;