diff --git a/modules/dnn/include/opencv2/dnn/all_layers.hpp b/modules/dnn/include/opencv2/dnn/all_layers.hpp index 9e9cd5f8e2..330e8a332f 100644 --- a/modules/dnn/include/opencv2/dnn/all_layers.hpp +++ b/modules/dnn/include/opencv2/dnn/all_layers.hpp @@ -1360,6 +1360,11 @@ CV__DNN_INLINE_NS_BEGIN static Ptr create(const LayerParams ¶ms); }; + class CV_EXPORTS ClipLayer : public Layer { + public: + static Ptr create(const LayerParams ¶ms); + }; + class CV_EXPORTS DepthToSpaceLayer : public Layer { public: static Ptr create(const LayerParams ¶ms); diff --git a/modules/dnn/src/init.cpp b/modules/dnn/src/init.cpp index 58c41e4e59..18c0a08485 100644 --- a/modules/dnn/src/init.cpp +++ b/modules/dnn/src/init.cpp @@ -125,6 +125,7 @@ void initializeLayerFactory() CV_DNN_REGISTER_LAYER_CLASS(ReLU, ReLULayer); CV_DNN_REGISTER_LAYER_CLASS(ReLU6, ReLU6Layer); + CV_DNN_REGISTER_LAYER_CLASS(Clip, ClipLayer); CV_DNN_REGISTER_LAYER_CLASS(ChannelsPReLU, ChannelsPReLULayer); CV_DNN_REGISTER_LAYER_CLASS(PReLU, ChannelsPReLULayer); CV_DNN_REGISTER_LAYER_CLASS(Sigmoid, SigmoidLayer); diff --git a/modules/dnn/src/layers/clip_layer.cpp b/modules/dnn/src/layers/clip_layer.cpp new file mode 100644 index 0000000000..e2fd9efd00 --- /dev/null +++ b/modules/dnn/src/layers/clip_layer.cpp @@ -0,0 +1,136 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +// Copyright (C) 2025, BigVision LLC, all rights reserved. +// Third party copyrights are property of their respective owners. +#include "../precomp.hpp" +#include "layers_common.hpp" +#include +#include +#include +#include +#include + +namespace cv { +namespace dnn { + +static double typeMin(int depth) +{ + switch (depth) + { + case CV_8U: return std::numeric_limits::lowest(); + case CV_8S: return std::numeric_limits::lowest(); + case CV_16U: return std::numeric_limits::lowest(); + case CV_16S: return std::numeric_limits::lowest(); + case CV_32S: return std::numeric_limits::lowest(); + case CV_32F: return -FLT_MAX; + case CV_64F: return -DBL_MAX; + default: CV_Error(Error::StsUnsupportedFormat, "Clip: unsupported depth"); + } +} + +static double typeMax(int depth) +{ + switch (depth) + { + case CV_8U: return std::numeric_limits::max(); + case CV_8S: return std::numeric_limits::max(); + case CV_16U: return std::numeric_limits::max(); + case CV_16S: return std::numeric_limits::max(); + case CV_32S: return std::numeric_limits::max(); + case CV_32F: return FLT_MAX; + case CV_64F: return DBL_MAX; + default: CV_Error(Error::StsUnsupportedFormat, "Clip: unsupported depth"); + } +} + +class ClipLayerImpl CV_FINAL : public ClipLayer +{ +public: + float minValue, maxValue; + bool hasMin, hasMax; + + ClipLayerImpl(const LayerParams& params) + { + setParamsFrom(params); + hasMin = params.has("min"); + hasMax = params.has("max"); + if (hasMin) minValue = params.get("min"); + if (hasMax) maxValue = params.get("max"); + if (hasMin && hasMax) + CV_Assert(minValue <= maxValue); + } + + virtual bool supportBackend(int backendId) CV_OVERRIDE + { + return backendId == DNN_BACKEND_OPENCV; + } + + bool getMemoryShapes(const std::vector &inputs, + const int requiredOutputs, + std::vector &outputs, + std::vector &internals) const CV_OVERRIDE + { + CV_Assert(!inputs.empty()); + outputs.assign(1, inputs[0]); + return false; + } + + void getTypes(const std::vector& inputs, + const int requiredOutputs, + const int requiredInternals, + std::vector& outputs, + std::vector& internals) const CV_OVERRIDE + { + CV_Assert(!inputs.empty()); + outputs.assign(requiredOutputs, inputs[0]); + internals.assign(requiredInternals, inputs[0]); + } + + void forward(InputArrayOfArrays inputs_arr, + OutputArrayOfArrays outputs_arr, + OutputArrayOfArrays internals_arr) CV_OVERRIDE + { + CV_TRACE_FUNCTION(); + CV_TRACE_ARG_VALUE(name, "name", name.c_str()); + + if (inputs_arr.depth() == CV_16F) + { + forward_fallback(inputs_arr, outputs_arr, internals_arr); + return; + } + + std::vector inputs, outputs; + inputs_arr.getMatVector(inputs); + outputs_arr.getMatVector(outputs); + CV_Assert(!inputs.empty()); + const Mat& data = inputs[0]; + Mat& dst = outputs[0]; + + bool dynMin = inputs.size() >= 2 && !inputs[1].empty(); + bool dynMax = inputs.size() >= 3 && !inputs[2].empty(); + + auto getScalar = [](const Mat& m)->double { + CV_Assert(m.total()==1); + Mat tmp; + m.convertTo(tmp, CV_64F); + return tmp.at(0); + }; + + double actualMin = dynMin ? getScalar(inputs[1]) : (hasMin ? minValue : typeMin(data.depth())); + double actualMax = dynMax ? getScalar(inputs[2]) : (hasMax ? maxValue : typeMax(data.depth())); + CV_Assert(actualMin <= actualMax); + + Scalar lowS = Scalar::all(actualMin); + Scalar highS = Scalar::all(actualMax); + cv::max(data, lowS, dst); + cv::min(dst, highS, dst); + } +}; + +Ptr ClipLayer::create(const LayerParams& params) +{ + return Ptr(new ClipLayerImpl(params)); +} +}} diff --git a/modules/dnn/src/onnx/onnx_importer2.cpp b/modules/dnn/src/onnx/onnx_importer2.cpp index 63cdb0de2d..1ce93b4fb2 100644 --- a/modules/dnn/src/onnx/onnx_importer2.cpp +++ b/modules/dnn/src/onnx/onnx_importer2.cpp @@ -1187,32 +1187,36 @@ void ONNXImporter2::parseImageScaler(LayerParams& layerParams, const opencv_onnx void ONNXImporter2::parseClip(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) { - layerParams.type = "ReLU6"; + layerParams.type = "Clip"; float min_value = -FLT_MAX, max_value = FLT_MAX; int input_size = node_proto.input_size(); CV_Check(input_size, 1 <= input_size && input_size <= 3, ""); if (input_size >= 2 && !node_proto.input(1).empty()) { - Mat m; - CV_Assert(net.isConstArg(node_inputs[1])); - net.argTensor(node_inputs[1]).convertTo(m, CV_32F); - CV_Assert(m.total() == 1); - min_value = m.at(0); + if (net.isConstArg(node_inputs[1])) + { + Mat m = net.argTensor(node_inputs[1]); + m.convertTo(m, CV_32F); + CV_Assert(m.total() == 1); + min_value = m.at(0); + layerParams.set("min", min_value); + } } if (input_size == 3 && !node_proto.input(2).empty()) { - Mat m; - CV_Assert(net.isConstArg(node_inputs[2])); - net.argTensor(node_inputs[2]).convertTo(m, CV_32F); - CV_Assert(m.total() == 1); - max_value = m.at(0); + if (net.isConstArg(node_inputs[2])) + { + Mat m = net.argTensor(node_inputs[2]); + m.convertTo(m, CV_32F); + CV_Assert(m.total() == 1); + max_value = m.at(0); + layerParams.set("max", max_value); + } } - layerParams.set("min_value", layerParams.get("min", min_value)); - layerParams.set("max_value", layerParams.get("max", max_value)); - addLayer(layerParams, node_proto, 1); + addLayer(layerParams, node_proto); } void ONNXImporter2::parseLeakyRelu(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) diff --git a/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp index eda32d5c5b..0c096b8f41 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp @@ -355,27 +355,27 @@ CASE(test_celu) CASE(test_celu_expanded) // no filter CASE(test_clip) - // no filter + SKIP; CASE(test_clip_default_inbounds) - // no filter + SKIP; CASE(test_clip_default_int8_inbounds) - // no filter + SKIP; CASE(test_clip_default_int8_max) - // no filter + SKIP; CASE(test_clip_default_int8_min) - // no filter + SKIP; CASE(test_clip_default_max) - // no filter + SKIP; CASE(test_clip_default_min) - // no filter + SKIP; CASE(test_clip_example) - // no filter + SKIP; CASE(test_clip_inbounds) - // no filter + SKIP; CASE(test_clip_outbounds) - // no filter + SKIP; CASE(test_clip_splitbounds) - // no filter + SKIP; CASE(test_compress_0) // no filter CASE(test_compress_1) diff --git a/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_classic_denylist.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_classic_denylist.inl.hpp index c78c22b3fc..0e68325946 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_classic_denylist.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_classic_denylist.inl.hpp @@ -55,3 +55,14 @@ "test_unsqueeze_three_axes", "test_unsqueeze_two_axes", "test_unsqueeze_unsorted_axes", +"test_clip", +"test_clip_default_inbounds", +"test_clip_default_int8_inbounds", +"test_clip_default_int8_max", +"test_clip_default_int8_min", +"test_clip_default_max", +"test_clip_default_min", +"test_clip_example", +"test_clip_inbounds", +"test_clip_outbounds", +"test_clip_splitbounds", diff --git a/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp index 6d951cc184..164aabfd37 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp @@ -48,17 +48,6 @@ "test_castlike_FLOAT_to_FLOAT16_expanded", // Issues::Layer::mismatch in input and output shapes inputs.size() == requiredOutputs in function 'getMemoryShapes' "test_castlike_FLOAT_to_STRING", "test_castlike_STRING_to_FLOAT", // Issues::Layer::Can't create layer "onnx_node_output_0!output" of type "CastLike" in function 'getLayerInstance' -"test_clip", // Issue:: Unkonwn error -"test_clip_default_inbounds", // ---- same as above --- -"test_clip_default_int8_inbounds", // ---- same as above --- -"test_clip_default_int8_max", // ---- same as above --- -"test_clip_default_int8_min", // ---- same as above --- -"test_clip_default_max", // ---- same as above --- -"test_clip_default_min", // ---- same as above --- -"test_clip_example", // ---- same as above --- -"test_clip_inbounds", // ---- same as above --- -"test_clip_outbounds", // ---- same as above --- -"test_clip_splitbounds", // ---- same as above --- "test_compress_0", // Issue::Can't create layer "onnx_node_output_0!output" of type "Compress" in function 'getLayerInstance' "test_compress_1", // ---- same as above --- "test_compress_default_axis", // ---- same as above ---