From 21b2c91814e454fd26fb654e11444ed4dce4232b Mon Sep 17 00:00:00 2001 From: Abhishek Gola Date: Tue, 11 Nov 2025 15:58:24 +0530 Subject: [PATCH] Merge pull request #27941 from abhishek-gola:dft_layer_add Added DFT layer to new DNN engine #27941 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake --- .../dnn/include/opencv2/dnn/all_layers.hpp | 10 + modules/dnn/src/init.cpp | 1 + modules/dnn/src/layers/dft_layer.cpp | 362 ++++++++++++++++++ modules/dnn/src/onnx/onnx_importer2.cpp | 7 + ...conformance_layer_filter__openvino.inl.hpp | 10 + ...yer_filter_opencv_classic_denylist.inl.hpp | 5 + ..._conformance_layer_parser_denylist.inl.hpp | 5 - 7 files changed, 395 insertions(+), 5 deletions(-) create mode 100644 modules/dnn/src/layers/dft_layer.cpp diff --git a/modules/dnn/include/opencv2/dnn/all_layers.hpp b/modules/dnn/include/opencv2/dnn/all_layers.hpp index 0e217df85d..41382dbc61 100644 --- a/modules/dnn/include/opencv2/dnn/all_layers.hpp +++ b/modules/dnn/include/opencv2/dnn/all_layers.hpp @@ -1318,6 +1318,16 @@ CV__DNN_INLINE_NS_BEGIN static Ptr create(const LayerParams& params); }; + class CV_EXPORTS DFTLayer : public Layer + { + public: + bool inverse; + bool onesided; + int axis_attr; + std::vector axes; + static Ptr create(const LayerParams ¶ms); + }; + class CV_EXPORTS Resize2Layer : public Layer { public: diff --git a/modules/dnn/src/init.cpp b/modules/dnn/src/init.cpp index 0d62f364ca..5103ac5820 100644 --- a/modules/dnn/src/init.cpp +++ b/modules/dnn/src/init.cpp @@ -118,6 +118,7 @@ void initializeLayerFactory() CV_DNN_REGISTER_LAYER_CLASS(OneHot, OneHotLayer); CV_DNN_REGISTER_LAYER_CLASS(Det, DetLayer); CV_DNN_REGISTER_LAYER_CLASS(CenterCropPad, CenterCropPadLayer); + CV_DNN_REGISTER_LAYER_CLASS(DFT, DFTLayer); 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/dft_layer.cpp b/modules/dnn/src/layers/dft_layer.cpp new file mode 100644 index 0000000000..1e4df190b2 --- /dev/null +++ b/modules/dnn/src/layers/dft_layer.cpp @@ -0,0 +1,362 @@ +// 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 +#include +#include "../net_impl.hpp" +#include "layers_common.hpp" +#include + +// ONNX DFT operator +// Spec: https://onnx.ai/onnx/operators/onnx__DFT.html +// Supported opset: 17-22 + +namespace cv { +namespace dnn { + +template +static void dftAlongAxisWorker(const Mat& src, + Mat& dst, + const std::vector& dimSizesSrc, + const std::vector& stridesSrc, + const std::vector& stridesDst, + const std::vector& iterDims, + const std::vector& outerSizes, + const std::vector& outerStep, + const size_t totalOuter, + const int axis, + const int N, + const int outN, + const size_t strideAxisSrc, + const size_t strideAxisDst, + const int inMatType, + const int flagsBase, + const bool inverse, + FillFn&& fill) +{ + const T* sp = src.ptr(); + T* dp = dst.ptr(); + cv::parallel_for_(Range(0, (int)totalOuter), [&](const Range& r){ + Mat inRow(1, N, inMatType); + Mat outRow; + for (int pos = r.start; pos < r.end; ++pos) + { + size_t baseSrc = 0; + size_t baseDst = 0; + for (size_t t = 0; t < iterDims.size(); ++t) + { + int idxVal = outerStep.empty() ? 0 : (int)((pos / outerStep[t]) % (size_t)outerSizes[t]); + int d = iterDims[t]; + baseSrc += (size_t)idxVal * stridesSrc[d]; + baseDst += (size_t)idxVal * stridesDst[d]; + } + const T* in = sp + baseSrc; + T* out = dp + baseDst; + fill(inRow, in, dimSizesSrc[axis], N, strideAxisSrc); + int flags = flagsBase | (inverse ? (DFT_INVERSE | DFT_SCALE) : 0); + cv::dft(inRow, outRow, flags); + const ComplexVec* p = outRow.ptr(0); + for (int k = 0; k < outN; ++k) + { + size_t ok = (size_t)k * strideAxisDst; + out[ok + 0] = p[k][0]; + out[ok + 1] = p[k][1]; + } + } + }); +} + +template +static void runTypedDFT(const Mat& src, + Mat& dst, + const std::vector& dimSizesSrc, + const std::vector& stridesSrc, + const std::vector& stridesDst, + const std::vector& iterDims, + const std::vector& outerSizes, + const std::vector& outerStep, + const size_t totalOuter, + const int axis, + const int N, + const int outN, + const size_t strideAxisSrc, + const size_t strideAxisDst, + const bool srcHasComplex, + const bool inverse) +{ + const int matTypeReal = std::is_same::value ? CV_32F : CV_64F; + const int matTypeComplex = std::is_same::value ? CV_32FC2 : CV_64FC2; + + if (srcHasComplex) + { + dftAlongAxisWorker( + src, dst, dimSizesSrc, stridesSrc, stridesDst, + iterDims, outerSizes, outerStep, totalOuter, + axis, N, outN, strideAxisSrc, strideAxisDst, + matTypeComplex, 0, inverse, + [&](Mat& inRow, const T* in, int origLen, int len, size_t stride){ + ComplexVec* ptr = inRow.ptr(0); + for (int n = 0; n < origLen; ++n) + { + size_t offSrc = (size_t)n * stride; + ptr[n][0] = in[offSrc + 0]; + ptr[n][1] = in[offSrc + 1]; + } + const ComplexVec zeroVal(T(0), T(0)); + for (int n = origLen; n < len; ++n) ptr[n] = zeroVal; + } + ); + } + else + { + dftAlongAxisWorker( + src, dst, dimSizesSrc, stridesSrc, stridesDst, + iterDims, outerSizes, outerStep, totalOuter, + axis, N, outN, strideAxisSrc, strideAxisDst, + matTypeReal, DFT_COMPLEX_OUTPUT, inverse, + [&](Mat& inRow, const T* in, int origLen, int len, size_t stride){ + T* ptr = inRow.ptr(0); + for (int n = 0; n < origLen; ++n) + { + size_t offSrc = (size_t)n * stride; + ptr[n] = in[offSrc]; + } + for (int n = origLen; n < len; ++n) ptr[n] = T(0); + } + ); + } +} + +class DFTLayerImpl CV_FINAL : public DFTLayer { +public: + DFTLayerImpl(const LayerParams ¶ms) + { + setParamsFrom(params); + inverse = params.get("inverse", 0) != 0; + onesided = params.get("onesided", 0) != 0; + axis_attr = params.get("axis", 1); + } + + virtual bool dynamicOutputShapes() const CV_OVERRIDE + { + if (this->inputs.size() >= 2) + { + Net::Impl* netimpl_ = getNetImpl(const_cast(this)); + if (!netimpl_ || !netimpl_->isConstArg(this->inputs[1])) + { + return true; + } + } + return false; + } + +private: + int getDftLengthFromConstant() const + { + if (this->inputs.size() < 2) + { + return -1; + } + + Net::Impl* netimpl_ = getNetImpl(const_cast(this)); + if (!netimpl_) + { + return -1; + } + + Mat dft_length_tensor = netimpl_->argTensor(this->inputs[1]); + if (dft_length_tensor.empty() || dft_length_tensor.total() != 1) + { + return -1; + } + + int64_t dft_length64 = 0; + tensorToScalar(dft_length_tensor, CV_64S, &dft_length64); + if (dft_length64 > 0) + { + return static_cast(dft_length64); + } + return -1; + } + +public: + virtual bool getMemoryShapes(const std::vector &inputs, + const int /*requiredOutputs*/, + std::vector &outputs, + std::vector &/*internals*/) const CV_OVERRIDE + { + CV_Assert(inputs.size() >= 1); + const MatShape &inshape = inputs[0]; + CV_Assert(!inshape.empty()); + MatShape out = inshape; + + int last = out.back(); + if (last == 1) + out.back() = 2; + else if (last != 2) + out.push_back(2); + + int ndims_in = (int)inshape.size(); + int ax = axis_attr; + if (ax == INT_MIN) + { + ax = (inshape.back() == 2 || inshape.back() == 1) ? ndims_in - 2 : ndims_in - 1; + } + if (ax < 0) ax += ndims_in; + if (ax >= 0 && ax < (int)out.size() - 1) + { + int dft_length = getDftLengthFromConstant(); + int signalLen = dft_length > 0 ? dft_length : (ax < (int)inshape.size() ? inshape[ax] : out[ax]); + out[ax] = onesided ? (signalLen / 2 + 1) : signalLen; + } + outputs.assign(1, out); + return false; + } + + void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays /*internals_arr*/) CV_OVERRIDE + { + std::vector inputs, outputs; + inputs_arr.getMatVector(inputs); + + CV_Assert(!inputs.empty()); + CV_Assert(inputs[0].dims >= 1); + + const Mat &src = inputs[0]; + + const int ndims = src.dims; + CV_Assert(ndims >= 1); + const bool srcHasComplex = (src.size[ndims - 1] == 2); + const bool srcLastIsOne = (src.size[ndims - 1] == 1); + int axis = (srcHasComplex ? ndims - 2 : ndims - 1); + if (axis_attr != INT_MIN) + { + axis = axis_attr; + if (axis < 0) axis += ndims; + } + else if (!axes.empty()) + { + CV_Assert(axes.size() == 1); + axis = axes[0]; + if (axis < 0) axis += ndims; + } + CV_Assert(axis >= 0 && axis < (srcHasComplex ? ndims - 1 : ndims)); + if (onesided) + { + CV_Assert(!srcHasComplex); + CV_Assert(!inverse); + } + + int dft_length = -1; + if (inputs.size() >= 2 && !inputs[1].empty()) + { + CV_Assert(inputs[1].total() == 1); + int64_t dft_length64 = -1; + tensorToScalar(inputs[1], CV_64S, &dft_length64); + dft_length = static_cast(dft_length64); + } + + std::vector outSizesVec; + outSizesVec.resize(srcHasComplex ? ndims : ndims + (srcLastIsOne ? 0 : 1)); + for (int i = 0; i < ndims; ++i) outSizesVec[i] = src.size[i]; + int complexDim = (int)outSizesVec.size() - 1; + outSizesVec[complexDim] = 2; + { + int dstDimsNoComplex = (int)outSizesVec.size() - 1; + if (axis >= 0 && axis < dstDimsNoComplex) + { + int signalLen = dft_length > 0 ? dft_length : outSizesVec[axis]; + outSizesVec[axis] = onesided ? (signalLen / 2 + 1) : signalLen; + } + } + MatShape outShape; + outShape.assign(outSizesVec.begin(), outSizesVec.end()); + auto kind = outputs_arr.kind(); + if (kind == _InputArray::STD_VECTOR_MAT) { + outputs_arr.getMatVecRef()[0].fit(outShape, src.type()); + } else { + CV_Assert(kind == _InputArray::STD_VECTOR_UMAT); + outputs_arr.getUMatVecRef()[0].fit(outShape, src.type()); + } + outputs_arr.getMatVector(outputs); + CV_Assert(outputs.size() == 1); + Mat &dst = outputs[0]; + + std::vector dimSizesSrc(ndims); + for (int i = 0; i < ndims; ++i) { + dimSizesSrc[i] = src.size[i]; + } + std::vector stridesSrc(ndims, 1); + for (int i = ndims - 2; i >= 0; --i) { + stridesSrc[i] = stridesSrc[i + 1] * (size_t)dimSizesSrc[i + 1]; + } + const int ndimsDst = (int)outSizesVec.size(); + std::vector stridesDst(ndimsDst, 1); + for (int i = ndimsDst - 2; i >= 0; --i) { + stridesDst[i] = stridesDst[i + 1] * (size_t)outSizesVec[i + 1]; + } + + int N = dimSizesSrc[axis]; + if (dft_length > 0) N = dft_length; + int outN = onesided ? (N / 2 + 1) : N; + const size_t strideAxisSrc = stridesSrc[axis]; + const size_t strideAxisDst = stridesDst[axis]; + std::vector iterDims; + for (int i = 0; i < (srcHasComplex ? ndims - 1 : ndims); ++i){ + if (i != axis){ + iterDims.push_back(i); + } + } + std::vector outerSizes(iterDims.size(), 0); + for (size_t j = 0; j < iterDims.size(); ++j){ + outerSizes[j] = dimSizesSrc[iterDims[j]]; + } + std::vector outerStep(iterDims.size(), 1); + for (int j = (int)iterDims.size() - 2; j >= 0; --j){ + outerStep[j] = outerStep[j + 1] * (size_t)outerSizes[j + 1]; + } + size_t totalOuter = 1; + for (int s : outerSizes){ + totalOuter *= (size_t)s; + } + + const int depth = src.depth(); + if (depth == CV_32F) + { + runTypedDFT(src, dst, dimSizesSrc, stridesSrc, stridesDst, + iterDims, outerSizes, outerStep, totalOuter, + axis, N, outN, strideAxisSrc, strideAxisDst, + srcHasComplex, inverse); + } + else if (depth == CV_64F) + { + runTypedDFT(src, dst, dimSizesSrc, stridesSrc, stridesDst, + iterDims, outerSizes, outerStep, totalOuter, + axis, N, outN, strideAxisSrc, strideAxisDst, + srcHasComplex, inverse); + } + else + { + CV_Error(Error::StsNotImplemented, "DFT supports float32/float64 only"); + } + } + + void getTypes(const std::vector& inputs, + const int /*requiredOutputs*/, + const int /*requiredInternals*/, + std::vector& outputs, + std::vector& /*internals*/) const CV_OVERRIDE + { + outputs.assign(1, inputs[0]); + } +}; + +Ptr DFTLayer::create(const LayerParams& params) +{ + return makePtr(params); +} + +}} // namespace diff --git a/modules/dnn/src/onnx/onnx_importer2.cpp b/modules/dnn/src/onnx/onnx_importer2.cpp index 04079cf5fa..375d9a20b3 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 parseOneHot (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); + void parseDFT (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); @@ -1769,6 +1770,11 @@ void ONNXImporter2::parseDet(LayerParams& layerParams, const opencv_onnx::NodePr addLayer(layerParams, node_proto); } +void ONNXImporter2::parseDFT(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) +{ + layerParams.type = "DFT"; + addLayer(layerParams, node_proto); +} void ONNXImporter2::parseGridSample(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) { layerParams.type = "GridSample"; @@ -2638,6 +2644,7 @@ void ONNXImporter2::buildDispatchMap_ONNX_AI(int opset_version) dispatch["IsInf"] = &ONNXImporter2::parseIsInf; dispatch["CenterCropPad"] = &ONNXImporter2::parseCenterCropPad; dispatch["OneHot"] = &ONNXImporter2::parseOneHot; + dispatch["DFT"] = &ONNXImporter2::parseDFT; 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 dd91cf8fa1..b17430029a 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 @@ -594,6 +594,16 @@ CASE(test_det_2d) SKIP; CASE(test_det_nd) SKIP; +CASE(test_dft) + SKIP; +CASE(test_dft_axis_opset19) + SKIP; +CASE(test_dft_inverse) + SKIP; +CASE(test_dft_inverse_opset19) + SKIP; +CASE(test_dft_opset19) + SKIP; CASE(test_div) // no filter CASE(test_div_bcast) 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 11858d29f9..572e9c4576 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 @@ -576,3 +576,8 @@ "test_onehot_with_axis", "test_onehot_with_negative_axis", "test_onehot_without_axis", +"test_dft", +"test_dft_axis_opset19", +"test_dft_inverse", +"test_dft_inverse_opset19", +"test_dft_opset19", 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 8d5a38af34..98374916d9 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 @@ -328,12 +328,7 @@ "test_dequantizelinear_int4", "test_dequantizelinear_uint16", "test_dequantizelinear_uint4", -"test_dft", "test_dft_axis", -"test_dft_axis_opset19", -"test_dft_inverse", -"test_dft_inverse_opset19", -"test_dft_opset19", "test_dropout_default_mask", // Issue::cvtest::norm::wrong data type "test_dropout_default_mask_ratio", // ---- same as above --- "test_dynamicquantizelinear", // Issue:: Unkonwn error