Merge pull request #19546 from LupusSanctus:am/slice_steps
authorAnastasia M <anastasia.murzova@xperience.ai>
Fri, 26 Mar 2021 11:04:57 +0000 (14:04 +0300)
committerGitHub <noreply@github.com>
Fri, 26 Mar 2021 11:04:57 +0000 (11:04 +0000)
* Added Steps support in DNN Slice layer

* Added code corrections

* dnn(slice): fix OCL and OCL_FP16 processing

modules/dnn/include/opencv2/dnn/all_layers.hpp
modules/dnn/src/layers/slice_layer.cpp
modules/dnn/src/onnx/onnx_importer.cpp
modules/dnn/test/test_onnx_importer.cpp

index 98d7671fdf701794b95c96324739804b4b202989..e92ce2f565ef3791ab3ac96ca5fd5dd5bb2f6504 100644 (file)
@@ -364,6 +364,7 @@ CV__DNN_EXPERIMENTAL_NS_BEGIN
          * Inner vector has slice ranges for the first number of input dimensions.
          */
         std::vector<std::vector<Range> > sliceRanges;
+        std::vector<std::vector<int> > sliceSteps;
         int axis;
         int num_split;
 
index 52236015d2264f553b1498457bd39139cf1368ff..507964edf9809dccfc5db317758bab11eb9ce45a 100644 (file)
@@ -64,6 +64,7 @@ public:
     SliceLayerImpl(const LayerParams& params)
     {
         setParamsFrom(params);
+        hasSteps = false;
         axis = params.get<int>("axis", 1);
         num_split = params.get<int>("num_split", 0);
         hasDynamicShapes = params.get<bool>("has_dynamic_shapes", false);
@@ -112,6 +113,22 @@ public:
                     sliceRanges[0][i].end = end;  // We'll finalize a negative value later.
                 }
             }
+
+            if (params.has("steps"))
+            {
+                const DictValue &steps = params.get("steps");
+                sliceSteps.resize(1);
+                sliceSteps[0].resize(steps.size());
+
+                for (int i = 0; i < steps.size(); ++i)
+                {
+                    int step = steps.get<int>(i);
+                    CV_Assert(step >= 1);
+                    if (step > 1)
+                        hasSteps = true;
+                    sliceSteps[0][i] = step;
+                }
+            }
         }
     }
 
@@ -120,11 +137,11 @@ public:
 #ifdef HAVE_DNN_IE_NN_BUILDER_2019
         if (backendId == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019)
             return INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2019R1) &&
-                sliceRanges.size() == 1 && sliceRanges[0].size() == 4;
+                sliceRanges.size() == 1 && sliceRanges[0].size() == 4 && !hasSteps;
 #endif
 #ifdef HAVE_DNN_NGRAPH
         if (backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH)
-            return sliceRanges.size() == 1;
+            return sliceRanges.size() == 1 && !hasSteps;
 #endif
         return backendId == DNN_BACKEND_OPENCV;
     }
@@ -147,6 +164,9 @@ public:
                 {
                     if (shapesInitialized || inpShape[j] > 0)
                         outputs[i][j] = normalize_axis_range(sliceRanges[i][j], inpShape[j]).size();
+
+                    if (!sliceSteps.empty() && (i < sliceSteps.size()) && (j < sliceSteps[i].size()) && (sliceSteps[i][j] > 1))
+                        outputs[i][j] = (outputs[i][j] + sliceSteps[i][j] - 1) / sliceSteps[i][j];
                 }
             }
         }
@@ -181,6 +201,7 @@ public:
         const MatSize& inpShape = inputs[0].size;
 
         finalSliceRanges = sliceRanges;
+
         if (sliceRanges.empty())
         {
             // Divide input blob on equal parts by axis.
@@ -213,6 +234,9 @@ public:
             }
         }
 
+        if (!sliceSteps.empty() && sliceSteps[0].size() != inputs[0].dims)
+            sliceSteps[0].resize(inputs[0].dims, 1);
+
 #if 0
         std::cout << "DEBUG: DNN/Slice: " << outputs.size() << " inpShape=" << inpShape << std::endl;
         for (int i = 0; i < outputs.size(); ++i)
@@ -420,6 +444,9 @@ public:
     {
         CV_TRACE_FUNCTION();
 
+        if (hasSteps)
+            return false;  // TODO not implemented yet: https://github.com/opencv/opencv/pull/19546
+
         std::vector<UMat> inputs;
         std::vector<UMat> outputs;
 
@@ -478,9 +505,24 @@ public:
 
         const Mat& inpMat = inputs[0];
         CV_Assert(outputs.size() == finalSliceRanges.size());
-        for (size_t i = 0; i < outputs.size(); i++)
+
+        if (!hasSteps)
         {
-            inpMat(finalSliceRanges[i]).copyTo(outputs[i]);
+            for (size_t i = 0; i < outputs.size(); i++)
+            {
+                inpMat(finalSliceRanges[i]).copyTo(outputs[i]);
+            }
+        }
+        else
+        {
+            int dimsNum = inpMat.dims;
+
+            for (size_t i = 0; i < outputs.size(); i++)
+            {
+                std::vector<int> inpIdx(dimsNum, 0);
+                std::vector<int> outIdx(dimsNum, 0);
+                getSliceRecursive(inpMat, inpIdx, finalSliceRanges[i], sliceSteps[i], 0, dimsNum, outputs[i], outIdx);
+            }
         }
     }
 
@@ -570,11 +612,42 @@ public:
     }
 #endif  // HAVE_DNN_NGRAPH
 
+private:
+    void getSliceRecursive(const Mat &inpMat, std::vector<int> &inpIdx,
+                           const std::vector<Range> &sliceRanges,
+                           const std::vector<int> &sliceSteps, int dim, int dimsNum,
+                           Mat &outputs, std::vector<int> &outIdx)
+    {
+        int begin = sliceRanges[dim].start;
+        int end = sliceRanges[dim].end;
+        int step = !sliceSteps.empty() ? sliceSteps[dim] : 1;
+
+        const bool is32F = inpMat.depth() == CV_32F;
+
+        // TODO optimization is required (for 2D tail case at least)
+        for (int k = begin, j = 0; k < end; k += step, j++)
+        {
+            inpIdx[dim] = k;
+            outIdx[dim] = j;
+
+            if (dim + 1 < dimsNum)
+                getSliceRecursive(inpMat, inpIdx, sliceRanges, sliceSteps, dim + 1, dimsNum, outputs, outIdx);
+            else
+            {
+                if (is32F)
+                    outputs.at<float>(outIdx.data()) = inpMat.at<float>(inpIdx.data());
+                else
+                    outputs.at<short>(outIdx.data()) = inpMat.at<short>(inpIdx.data());  // 16F emulation
+            }
+        }
+    }
+
 protected:
     // The actual non-negative values determined from @p sliceRanges depends on input size.
     std::vector<std::vector<Range> > finalSliceRanges;
     bool hasDynamicShapes;
     bool shapesInitialized;
+    bool hasSteps;
 };
 
 class CropLayerImpl CV_FINAL : public SliceLayerImpl
index 93497a6126e18b4787cd5f0050a20f3b4698c272..651a2ab3334496795c87c6de0708649371a81f74 100644 (file)
@@ -641,20 +641,11 @@ void ONNXImporter::handleNode(const opencv_onnx::NodeProto& node_proto_)
             int axis = 0;
             std::vector<int> begin;
             std::vector<int> end;
+            std::vector<int> steps;
             int inp_size = node_proto.input_size();
 
             if (inp_size == 1)
             {
-                if (layerParams.has("steps"))
-                {
-                    DictValue steps = layerParams.get("steps");
-                    for (int i = 0; i < steps.size(); ++i)
-                    {
-                        if (steps.get<int>(i) != 1)
-                            CV_Error(Error::StsNotImplemented,
-                                "Slice layer only supports steps = 1");
-                    }
-                }
                 if (layerParams.has("axes")) {
                     DictValue axes = layerParams.get("axes");
                     for (int i = 1; i < axes.size(); ++i) {
@@ -677,7 +668,7 @@ void ONNXImporter::handleNode(const opencv_onnx::NodeProto& node_proto_)
                     int finish = ends.get<int>(i);
                     end.push_back((finish < 0) ? --finish : finish); // numpy doesn't include last dim
                 }
-            } else {
+            } else { // inp_size > 1
                 CV_Assert(inp_size >= 3);
                 for (int i = 1; i < inp_size; i++) {
                     CV_Assert(constBlobs.find(node_proto.input(i)) != constBlobs.end());
@@ -711,6 +702,12 @@ void ONNXImporter::handleNode(const opencv_onnx::NodeProto& node_proto_)
                 if (inp_size == 5) {
                     CV_Assert(constBlobs.find(node_proto.input(4)) != constBlobs.end());
                     Mat step_blob = getBlob(node_proto, 4);
+                    const int* steps_ptr = step_blob.ptr<int>();
+
+                    if (axis > 0)
+                        steps.resize(axis, 1);
+
+                    std::copy(steps_ptr, steps_ptr + step_blob.total(), std::back_inserter(steps));
 
                     // Very strange application for Slice op with tensor reversing.
                     // We just workaround it for 2d constants.
@@ -728,13 +725,15 @@ void ONNXImporter::handleNode(const opencv_onnx::NodeProto& node_proto_)
                             return;
                         }
                     }
-                    CV_CheckEQ(countNonZero(step_blob != 1), 0, "Slice layer only supports steps = 1");
                 }
             }
             layerParams.set("begin", DictValue::arrayInt(&begin[0], begin.size()));
             layerParams.set("end", DictValue::arrayInt(&end[0], end.size()));
             layerParams.set("axis", axis);
 
+            if (!steps.empty())
+                layerParams.set("steps", DictValue::arrayInt(&steps[0], steps.size()));
+
             if (constBlobs.find(node_proto.input(0)) != constBlobs.end())
             {
                 Mat inp = getBlob(node_proto, 0);
index acc88c97133380be5e4bd705a4ea72d152248807..c4cb87717200003f99cd7823767b0ef3996f4e1c 100644 (file)
@@ -627,6 +627,26 @@ TEST_P(Test_ONNX_layers, Slice)
 #endif
 }
 
+TEST_P(Test_ONNX_layers, Slice_Steps_2DInput)
+{
+    testONNXModels("slice_opset_11_steps_2d");
+}
+
+TEST_P(Test_ONNX_layers, Slice_Steps_3DInput)
+{
+    testONNXModels("slice_opset_11_steps_3d");
+}
+
+TEST_P(Test_ONNX_layers, Slice_Steps_4DInput)
+{
+    testONNXModels("slice_opset_11_steps_4d");
+}
+
+TEST_P(Test_ONNX_layers, Slice_Steps_5DInput)
+{
+    testONNXModels("slice_opset_11_steps_5d");
+}
+
 TEST_P(Test_ONNX_layers, Softmax)
 {
     testONNXModels("softmax");