diff --git a/modules/dnn/include/opencv2/dnn/all_layers.hpp b/modules/dnn/include/opencv2/dnn/all_layers.hpp index c7c33a6721..caf953436a 100644 --- a/modules/dnn/include/opencv2/dnn/all_layers.hpp +++ b/modules/dnn/include/opencv2/dnn/all_layers.hpp @@ -1305,6 +1305,12 @@ CV__DNN_INLINE_NS_BEGIN static Ptr create(const LayerParams ¶ms); }; + class CV_EXPORTS CenterCropPadLayer : public Layer + { + public: + static Ptr create(const LayerParams& params); + }; + class CV_EXPORTS Resize2Layer : public Layer { public: diff --git a/modules/dnn/src/init.cpp b/modules/dnn/src/init.cpp index f001c5c770..daaa6693e8 100644 --- a/modules/dnn/src/init.cpp +++ b/modules/dnn/src/init.cpp @@ -116,6 +116,7 @@ void initializeLayerFactory() CV_DNN_REGISTER_LAYER_CLASS(IsNaN, IsNaNLayer); CV_DNN_REGISTER_LAYER_CLASS(IsInf, IsInfLayer); CV_DNN_REGISTER_LAYER_CLASS(Det, DetLayer); + CV_DNN_REGISTER_LAYER_CLASS(CenterCropPad, CenterCropPadLayer); CV_DNN_REGISTER_LAYER_CLASS(BitShift, BitShiftLayer); CV_DNN_REGISTER_LAYER_CLASS(GridSample, GridSampleLayer); CV_DNN_REGISTER_LAYER_CLASS(Reduce2, Reduce2Layer); diff --git a/modules/dnn/src/layers/centercroppad_layer.cpp b/modules/dnn/src/layers/centercroppad_layer.cpp new file mode 100644 index 0000000000..b7ecc500d8 --- /dev/null +++ b/modules/dnn/src/layers/centercroppad_layer.cpp @@ -0,0 +1,249 @@ +// 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 "../net_impl.hpp" + +namespace cv { +namespace dnn { + +// ONNX CenterCropPad operator +// Spec: https://onnx.ai/onnx/operators/onnx__CenterCropPad.html +// Supported opsets: 18 + +class CenterCropPadLayerImpl CV_FINAL : public CenterCropPadLayer +{ +public: + std::vector axes_attr; + + CenterCropPadLayerImpl(const LayerParams& params) + { + setParamsFrom(params); + if (params.has("axes")) + { + DictValue dv = params.get("axes"); + axes_attr.resize(dv.size()); + for (int i = 0; i < dv.size(); ++i) axes_attr[i] = dv.get(i); + } + } + + bool supportBackend(int backendId) CV_OVERRIDE + { + return backendId == DNN_BACKEND_OPENCV; + } + + bool dynamicOutputShapes() const CV_OVERRIDE + { + Net::Impl* netimpl_ = getNetImpl(this); + CV_Assert(netimpl_); + const size_t ninputs = this->inputs.size(); + CV_Assert(ninputs >= 2); + bool shapeDynamic = !netimpl_->isConstArg(this->inputs[1]); + bool axesDynamic = (ninputs >= 3) && !netimpl_->isConstArg(this->inputs[2]); + return shapeDynamic || axesDynamic; + } + + void buildTargetShape(const MatShape& inputShape, const Mat& shapeTensor, const Mat& axesTensor, + std::vector& targetShape, std::vector& usedAxes) const + { + const int rank = inputShape.dims; + targetShape.assign(inputShape.begin(), inputShape.end()); + usedAxes.clear(); + + CV_Assert(shapeTensor.dims == 1); + CV_Assert(shapeTensor.type() == CV_32S || shapeTensor.type() == CV_64S); + + std::vector axes; + if (!axesTensor.empty()) + { + CV_Assert(axesTensor.dims == 1); + int naxes = (int)axesTensor.total(); + axes.resize(naxes); + if (axesTensor.type() == CV_32S) + { + const int32_t* a32 = (const int32_t*)axesTensor.data; + for (int i = 0; i < naxes; ++i) axes[i] = normalize_axis((int)a32[i], rank); + } + else + { + const int64_t* a64 = (const int64_t*)axesTensor.data; + for (int i = 0; i < naxes; ++i) axes[i] = normalize_axis((int)a64[i], rank); + } + } + else if (!axes_attr.empty()) + { + axes.resize((int)axes_attr.size()); + for (size_t i = 0; i < axes_attr.size(); ++i) axes[i] = normalize_axis(axes_attr[i], rank); + } + else + { + axes.resize(rank); + std::iota(axes.begin(), axes.end(), 0); + } + + CV_Assert((int)shapeTensor.total() == (int)axes.size()); + + if (shapeTensor.type() == CV_32S) + { + const int32_t* shp = (const int32_t*)shapeTensor.data; + applyShapeFromPtr(shp, (int)axes.size(), axes, targetShape, usedAxes); + } + else if (shapeTensor.type() == CV_64S) + { + const int64_t* shp = (const int64_t*)shapeTensor.data; + applyShapeFromPtr(shp, (int)axes.size(), axes, targetShape, usedAxes); + } + } + + bool getMemoryShapes(const std::vector& inputs, + const int /*requiredOutputs*/, + std::vector &outputs, + std::vector &internals) const CV_OVERRIDE + { + CV_Assert(inputs.size() >= 2); + + const MatShape& inShape = inputs[0]; + Net::Impl* netimpl_ = getNetImpl(this); + CV_Assert(netimpl_); + + Mat shapeTensor = netimpl_->argTensor(this->inputs[1]); + Mat axesTensor; + if (this->inputs.size() >= 3) + axesTensor = netimpl_->argTensor(this->inputs[2]); + + std::vector targetShape, usedAxes; + buildTargetShape(inShape, shapeTensor, axesTensor, targetShape, usedAxes); + + outputs.assign(1, MatShape(targetShape.begin(), targetShape.end())); + internals.clear(); + return true; + } + + 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]); + CV_Assert(requiredInternals == 0); + internals.clear(); + } + + void forward(InputArrayOfArrays inputs_arr, + OutputArrayOfArrays outputs_arr, + OutputArrayOfArrays) CV_OVERRIDE + { + CV_TRACE_FUNCTION(); + CV_TRACE_ARG_VALUE(name, "name", name.c_str()); + + Size size = inputs_arr.size(); + int ninputs = size.area(); + CV_Assert(ninputs >= 2); + + Mat X = inputs_arr.getMat(0); + Mat shapeTensor = inputs_arr.getMat(1); + Mat axesTensor; + if (ninputs >= 3) + axesTensor = inputs_arr.getMat(2); + + const int rank = X.dims; + std::vector outShape(rank); + std::vector usedAxes; + + MatShape inShape = X.shape(); + std::vector tgt; + buildTargetShape(inShape, shapeTensor, axesTensor, tgt, usedAxes); + outShape.assign(tgt.begin(), tgt.end()); + + auto kind = outputs_arr.kind(); + if (kind == _InputArray::STD_VECTOR_MAT) + { + std::vector& outs = outputs_arr.getMatVecRef(); + outs.resize(1); + outs[0].fit(MatShape(outShape.begin(), outShape.end()), X.type()); + centerCropPad(X, outs[0], usedAxes); + } + else if (kind == _InputArray::STD_VECTOR_UMAT) + { + std::vector& outs = outputs_arr.getUMatVecRef(); + outs.resize(1); + outs[0].fit(MatShape(outShape.begin(), outShape.end()), X.type()); + Mat temp(MatShape(outShape.begin(), outShape.end()), X.type()); + centerCropPad(X, temp, usedAxes); + temp.copyTo(outs[0]); + } + else + { + CV_Error(Error::StsNotImplemented, ""); + } + } + +private: + template + static void applyShapeFromPtr(const T* shp, int count, const std::vector& axes, + std::vector& targetShape, std::vector& usedAxes) + { + for (int i = 0; i < count; ++i) + { + int a = axes[i]; + int desired = (int)shp[i]; + CV_Assert(desired >= 0); + targetShape[a] = desired; + usedAxes.push_back(a); + } + } + + void centerCropPad(const Mat& src, Mat& dst, const std::vector& axes) const + { + CV_Assert(src.dims == dst.dims); + const int rank = src.dims; + + std::vector srcRanges(rank), dstRanges(rank); + for (int i = 0; i < rank; ++i) + { + int s = src.size[i]; + int d = dst.size[i]; + if (std::find(axes.begin(), axes.end(), i) == axes.end()) + { + CV_Assert(s == d); + srcRanges[i] = Range(0, s); + dstRanges[i] = Range(0, d); + continue; + } + + if (d <= s) + { + int diff = s - d; + int start = diff / 2; + int end = start + d; + srcRanges[i] = Range(start, end); + dstRanges[i] = Range(0, d); + } + else + { + int diff = d - s; + int start = diff / 2; + int end = start + s; + srcRanges[i] = Range(0, s); + dstRanges[i] = Range(start, end); + } + } + + dst.setTo(Scalar(0)); + src(srcRanges.data()).copyTo(dst(dstRanges.data())); + } +}; + +Ptr CenterCropPadLayer::create(const LayerParams& params) +{ + return Ptr(new CenterCropPadLayerImpl(params)); +} + +} // namespace dnn +} // namespace cv diff --git a/modules/dnn/src/onnx/onnx_importer2.cpp b/modules/dnn/src/onnx/onnx_importer2.cpp index a2af21a8ea..84037ec24f 100644 --- a/modules/dnn/src/onnx/onnx_importer2.cpp +++ b/modules/dnn/src/onnx/onnx_importer2.cpp @@ -218,6 +218,7 @@ protected: void parseIsNaN (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseIsInf (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseDet (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); + void parseCenterCropPad (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseGridSample (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseNegativeLogLikelihoodLoss(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseSoftmaxCrossEntropyLoss (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); @@ -1674,6 +1675,14 @@ void ONNXImporter2::parseResize2(LayerParams& layerParams, const opencv_onnx::No addLayer(layerParams, node_proto, ninputs); } +void ONNXImporter2::parseCenterCropPad(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) +{ + int ninputs = node_proto.input_size(); + CV_Assert(ninputs >= 2); + layerParams.type = "CenterCropPad"; + addLayer(layerParams, node_proto); +} + void ONNXImporter2::parseUnique(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) { layerParams.type = "Unique"; @@ -2620,6 +2629,7 @@ void ONNXImporter2::buildDispatchMap_ONNX_AI(int opset_version) dispatch["Trilu"] = &ONNXImporter2::parseTrilu; dispatch["IsNaN"] = &ONNXImporter2::parseIsNaN; dispatch["IsInf"] = &ONNXImporter2::parseIsInf; + dispatch["CenterCropPad"] = &ONNXImporter2::parseCenterCropPad; dispatch["Det"] = &ONNXImporter2::parseDet; dispatch["GridSample"] = &ONNXImporter2::parseGridSample; dispatch["Upsample"] = &ONNXImporter2::parseUpsample; 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 71a8edda93..0fa1d1af97 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 @@ -394,6 +394,30 @@ CASE(test_celu) // no filter CASE(test_celu_expanded) // no filter +CASE(test_center_crop_pad_crop) + SKIP; +CASE(test_center_crop_pad_crop_and_pad) + SKIP; +CASE(test_center_crop_pad_crop_and_pad_expanded) + SKIP; +CASE(test_center_crop_pad_crop_axes_chw) + SKIP; +CASE(test_center_crop_pad_crop_axes_chw_expanded) + SKIP; +CASE(test_center_crop_pad_crop_axes_hwc) + SKIP; +CASE(test_center_crop_pad_crop_axes_hwc_expanded) + SKIP; +CASE(test_center_crop_pad_crop_expanded) + SKIP; +CASE(test_center_crop_pad_crop_negative_axes_hwc) + SKIP; +CASE(test_center_crop_pad_crop_negative_axes_hwc_expanded) + SKIP; +CASE(test_center_crop_pad_pad) + SKIP; +CASE(test_center_crop_pad_pad_expanded) + SKIP; CASE(test_clip) SKIP; CASE(test_clip_default_inbounds) 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 50ee183e85..272ac55802 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 @@ -552,3 +552,15 @@ "test_nllloss_NCd1d2d3_sum_weight_high_ii_expanded", "test_nllloss_NCd1d2d3d4d5_mean_weight", "test_nllloss_NCd1d2d3d4d5_none_no_weight", +"test_center_crop_pad_crop", +"test_center_crop_pad_crop_and_pad", +"test_center_crop_pad_crop_and_pad_expanded", +"test_center_crop_pad_crop_axes_chw", +"test_center_crop_pad_crop_axes_chw_expanded", +"test_center_crop_pad_crop_axes_hwc", +"test_center_crop_pad_crop_axes_hwc_expanded", +"test_center_crop_pad_crop_expanded", +"test_center_crop_pad_crop_negative_axes_hwc", +"test_center_crop_pad_crop_negative_axes_hwc_expanded", +"test_center_crop_pad_pad", +"test_center_crop_pad_pad_expanded", 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 3d367f066d..28456edb24 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 @@ -286,18 +286,6 @@ "test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2FNUZ", "test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2FNUZ_expanded", "test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2_expanded", -"test_center_crop_pad_crop", -"test_center_crop_pad_crop_and_pad", -"test_center_crop_pad_crop_and_pad_expanded", -"test_center_crop_pad_crop_axes_chw", -"test_center_crop_pad_crop_axes_chw_expanded", -"test_center_crop_pad_crop_axes_hwc", -"test_center_crop_pad_crop_axes_hwc_expanded", -"test_center_crop_pad_crop_expanded", -"test_center_crop_pad_crop_negative_axes_hwc", -"test_center_crop_pad_crop_negative_axes_hwc_expanded", -"test_center_crop_pad_pad", -"test_center_crop_pad_pad_expanded", "test_clip_example_expanded", //wrong output "test_clip_expanded", "test_clip_min_greater_than_max",