From 3e48a91d972e6cfce413a7dd7308cbff5b27fe61 Mon Sep 17 00:00:00 2001 From: Anastasia M Date: Fri, 26 Mar 2021 14:04:57 +0300 Subject: [PATCH] Merge pull request #19546 from LupusSanctus:am/slice_steps * Added Steps support in DNN Slice layer * Added code corrections * dnn(slice): fix OCL and OCL_FP16 processing --- .../dnn/include/opencv2/dnn/all_layers.hpp | 1 + modules/dnn/src/layers/slice_layer.cpp | 81 ++++++++++++++++++- modules/dnn/src/onnx/onnx_importer.cpp | 23 +++--- modules/dnn/test/test_onnx_importer.cpp | 20 +++++ 4 files changed, 109 insertions(+), 16 deletions(-) diff --git a/modules/dnn/include/opencv2/dnn/all_layers.hpp b/modules/dnn/include/opencv2/dnn/all_layers.hpp index 98d7671fdf..e92ce2f565 100644 --- a/modules/dnn/include/opencv2/dnn/all_layers.hpp +++ b/modules/dnn/include/opencv2/dnn/all_layers.hpp @@ -364,6 +364,7 @@ CV__DNN_EXPERIMENTAL_NS_BEGIN * Inner vector has slice ranges for the first number of input dimensions. */ std::vector > sliceRanges; + std::vector > sliceSteps; int axis; int num_split; diff --git a/modules/dnn/src/layers/slice_layer.cpp b/modules/dnn/src/layers/slice_layer.cpp index 52236015d2..507964edf9 100644 --- a/modules/dnn/src/layers/slice_layer.cpp +++ b/modules/dnn/src/layers/slice_layer.cpp @@ -64,6 +64,7 @@ public: SliceLayerImpl(const LayerParams& params) { setParamsFrom(params); + hasSteps = false; axis = params.get("axis", 1); num_split = params.get("num_split", 0); hasDynamicShapes = params.get("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(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 inputs; std::vector 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 inpIdx(dimsNum, 0); + std::vector 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 &inpIdx, + const std::vector &sliceRanges, + const std::vector &sliceSteps, int dim, int dimsNum, + Mat &outputs, std::vector &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(outIdx.data()) = inpMat.at(inpIdx.data()); + else + outputs.at(outIdx.data()) = inpMat.at(inpIdx.data()); // 16F emulation + } + } + } + protected: // The actual non-negative values determined from @p sliceRanges depends on input size. std::vector > finalSliceRanges; bool hasDynamicShapes; bool shapesInitialized; + bool hasSteps; }; class CropLayerImpl CV_FINAL : public SliceLayerImpl diff --git a/modules/dnn/src/onnx/onnx_importer.cpp b/modules/dnn/src/onnx/onnx_importer.cpp index 93497a6126..651a2ab333 100644 --- a/modules/dnn/src/onnx/onnx_importer.cpp +++ b/modules/dnn/src/onnx/onnx_importer.cpp @@ -641,20 +641,11 @@ void ONNXImporter::handleNode(const opencv_onnx::NodeProto& node_proto_) int axis = 0; std::vector begin; std::vector end; + std::vector 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(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(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(); + + 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); diff --git a/modules/dnn/test/test_onnx_importer.cpp b/modules/dnn/test/test_onnx_importer.cpp index acc88c9713..c4cb877172 100644 --- a/modules/dnn/test/test_onnx_importer.cpp +++ b/modules/dnn/test/test_onnx_importer.cpp @@ -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");