Merge pull request #9305 from dkurt:public_dnn_importer_is_deprecated
authorVadim Pisarevsky <vadim.pisarevsky@gmail.com>
Mon, 18 Sep 2017 09:35:35 +0000 (09:35 +0000)
committerVadim Pisarevsky <vadim.pisarevsky@gmail.com>
Mon, 18 Sep 2017 09:35:35 +0000 (09:35 +0000)
1  2 
modules/dnn/include/opencv2/dnn/all_layers.hpp
modules/dnn/include/opencv2/dnn/dnn.hpp
modules/dnn/src/caffe/caffe_importer.cpp
modules/dnn/src/tensorflow/tf_importer.cpp
modules/dnn/test/test_caffe_importer.cpp
modules/dnn/test/test_tf_importer.cpp

@@@ -55,7 -55,7 +55,7 @@@ CV__DNN_EXPERIMENTAL_NS_BEGI
  
    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 &params);
      };
  
 +    class CV_EXPORTS ReLU6Layer : public ActivationLayer
 +    {
 +    public:
 +        static Ptr<ReLU6Layer> create(const LayerParams &params);
 +    };
 +
      class CV_EXPORTS ChannelsPReLULayer : public ActivationLayer
      {
      public:
@@@ -598,23 -598,27 +598,27 @@@ CV__DNN_EXPERIMENTAL_NS_BEGI
          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
  }
@@@ -225,28 -225,13 +225,28 @@@ public
          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)
@@@ -385,24 -370,15 +385,15 @@@ Ptr<Importer> createCaffeImporter(cons
      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
@@@ -85,38 -85,11 +85,38 @@@ static Mat getTensorContent(const tenso
      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;
@@@ -600,7 -573,7 +600,7 @@@ void TFImporter::populateNet(Net dstNet
          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);
@@@ -1098,10 -1045,9 +1098,9 @@@ Ptr<Importer> createTensorflowImporter(
  
  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;
  }
  
@@@ -57,22 -57,14 +57,14 @@@ static std::string _tf(TString filename
  
  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)
@@@ -81,9 -73,8 +73,8 @@@
      {
          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"));
@@@ -107,9 -98,8 +98,8 @@@ TEST(Reproducibility_FCN, Accuracy
      {
          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"));
@@@ -136,9 -126,8 +126,8 @@@ TEST(Reproducibility_SSD, Accuracy
      {
          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"));
@@@ -188,46 -177,4 +177,46 @@@ TEST(Reproducibility_SqueezeNet_v1_1, A
      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);
 +}
 +
  }
@@@ -29,9 -29,8 +29,8 @@@ TEST(Test_TensorFlow, read_inception
      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"));
@@@ -53,9 -52,8 +52,8 @@@ TEST(Test_TensorFlow, inception_accurac
      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"));
@@@ -93,12 -91,11 +91,12 @@@ static void runTensorFlowNet(const std:
      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)
@@@ -117,9 -114,8 +115,9 @@@ TEST(Test_TensorFlow, pad_and_concat
      runTensorFlowNet("pad_and_concat");
  }
  
 -TEST(Test_TensorFlow, fused_batch_norm)
 +TEST(Test_TensorFlow, batch_norm)
  {
 +    runTensorFlowNet("batch_norm");
      runTensorFlowNet("fused_batch_norm");
  }
  
@@@ -135,11 -131,6 +133,11 @@@ TEST(Test_TensorFlow, deconvolution
      runTensorFlowNet("deconvolution");
  }
  
 +TEST(Test_TensorFlow, matmul)
 +{
 +    runTensorFlowNet("matmul");
 +}
 +
  TEST(Test_TensorFlow, fp16)
  {
      const float l1 = 1e-3;