From 66bb0a801765a411b1e801e06f4e02c9b93bace8 Mon Sep 17 00:00:00 2001 From: Abhishek Gola Date: Sun, 21 Dec 2025 22:48:28 +0530 Subject: [PATCH] Merge pull request #28164 from abhishek-gola:randomNormalLike_layer_4x Added randomNormalLike layer to 4.x branch #28164 OpenCV Extra: https://github.com/opencv/opencv_extra/pull/1297 Backport of https://github.com/opencv/opencv/pull/28110 to 4.x ### 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 --- modules/dnn/src/layers/layers_common.hpp | 68 ++++++++++ .../dnn/src/layers/randomnormallike_layer.cpp | 122 ++++++++++++++++++ modules/dnn/src/onnx/onnx_importer.cpp | 10 ++ modules/dnn/test/test_onnx_importer.cpp | 43 ++++++ 4 files changed, 243 insertions(+) create mode 100644 modules/dnn/src/layers/randomnormallike_layer.cpp diff --git a/modules/dnn/src/layers/layers_common.hpp b/modules/dnn/src/layers/layers_common.hpp index 4510f6b106..5d36004669 100644 --- a/modules/dnn/src/layers/layers_common.hpp +++ b/modules/dnn/src/layers/layers_common.hpp @@ -59,6 +59,74 @@ namespace cv { namespace dnn { + +enum OnnxDataType +{ + ONNX_UNDEFINED = 0, + ONNX_FLOAT = 1, // float + ONNX_UINT8 = 2, // uint8_t + ONNX_INT8 = 3, // int8_t + ONNX_UINT16 = 4, // uint16_t + ONNX_INT16 = 5, // int16_t + ONNX_INT32 = 6, // int32_t + ONNX_INT64 = 7, // int64_t + ONNX_STRING = 8, // string + ONNX_BOOL = 9, // bool + ONNX_FLOAT16 = 10, + ONNX_DOUBLE = 11, + ONNX_UINT32 = 12, + ONNX_UINT64 = 13, + ONNX_BFLOAT16 = 14 +}; + +inline int onnxDataTypeToCV(OnnxDataType dt) +{ + switch (dt) + { + case ONNX_UINT8: return CV_8U; + case ONNX_INT8: return CV_8S; + case ONNX_UINT16: return CV_16U; + case ONNX_INT16: return CV_16S; + case ONNX_UINT32: +#ifdef CV_32U + return CV_32U; +#else + return CV_32S; +#endif + case ONNX_INT32: return CV_32S; + case ONNX_UINT64: +#ifdef CV_64U + return CV_64U; +#else + return CV_32S; +#endif + case ONNX_INT64: +#ifdef CV_64S + return CV_64S; +#else + return CV_32S; +#endif + case ONNX_FLOAT: return CV_32F; + case ONNX_DOUBLE: return CV_64F; + case ONNX_FLOAT16: return CV_16F; + case ONNX_BFLOAT16: +#ifdef CV_16BF + return CV_16BF; +#else + return CV_16F; +#endif + case ONNX_BOOL: +#ifdef CV_Bool + return CV_Bool; +#else + return CV_8U; +#endif + default: + // Fallback to default ONNX FLOAT if value is unknown. + return CV_32F; + } +} + void getConvolutionKernelParams(const LayerParams ¶ms, std::vector& kernel, std::vector& pads_begin, std::vector& pads_end, std::vector& strides, std::vector& dilations, cv::String &padMode, std::vector& adjust_pads, bool& useWinograd); diff --git a/modules/dnn/src/layers/randomnormallike_layer.cpp b/modules/dnn/src/layers/randomnormallike_layer.cpp new file mode 100644 index 0000000000..30713390c9 --- /dev/null +++ b/modules/dnn/src/layers/randomnormallike_layer.cpp @@ -0,0 +1,122 @@ +// 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 + +namespace cv { namespace dnn { + +class RandomNormalLikeLayerImpl CV_FINAL : public Layer +{ +public: + RandomNormalLikeLayerImpl(const LayerParams& params) + { + setParamsFrom(params); + + mean = params.get("mean", 0.0); + scale = params.get("scale", 1.0); + + hasSeed = params.has("seed"); + if (hasSeed) + { + seed = params.get("seed"); + } + + depth = params.get("depth", CV_32F); + if (params.has("dtype")) + { + depth = onnxDataTypeToCV(static_cast(params.get("dtype"))); + } + } + + virtual bool supportBackend(int backendId) CV_OVERRIDE + { + return backendId == DNN_BACKEND_OPENCV; + } + + virtual bool getMemoryShapes(const std::vector& inputs, + const int requiredOutputs, + std::vector& outputs, + std::vector& internals) const CV_OVERRIDE + { + CV_UNUSED(requiredOutputs); + CV_UNUSED(internals); + CV_CheckEQ(inputs.size(), 1ull, "RandomNormalLike: one input is expected"); + + outputs.assign(1, inputs[0]); + return false; + } + + virtual void forward(InputArrayOfArrays inputs_arr, + OutputArrayOfArrays outputs_arr, + OutputArrayOfArrays internals_arr) CV_OVERRIDE + { + CV_UNUSED(internals_arr); + CV_TRACE_FUNCTION(); + CV_TRACE_ARG_VALUE(name, "name", name.c_str()); + + std::vector inputs, outputs; + inputs_arr.getMatVector(inputs); + outputs_arr.getMatVector(outputs); + + CV_Assert(!inputs.empty()); + CV_Assert(!outputs.empty()); + + Mat out = outputs[0]; + const int desiredDepth = depth; + const bool needRecreate = out.depth() != desiredDepth; + Mat outBlob; + if (needRecreate) + { + const int dims = out.dims; + const int* sizes = out.size.p; + outBlob = Mat(dims, sizes, CV_MAKETYPE(desiredDepth, out.channels())); + } + else + { + outBlob = out; + } + + RNG seededRng; + RNG* rng = &theRNG(); + if (hasSeed) + { + Cv64suf u; + u.f = seed; + seededRng = RNG(u.u ? u.u : 1); + rng = &seededRng; + } + + if (outBlob.depth() == CV_32F || outBlob.depth() == CV_64F || outBlob.depth() == CV_16F) + { + rng->fill(outBlob, RNG::NORMAL, mean, scale); + } + else + { + Mat tmp(outBlob.size.dims(), outBlob.size.p, CV_32F); + rng->fill(tmp, RNG::NORMAL, mean, scale); + tmp.convertTo(outBlob, outBlob.type()); + } + + if (needRecreate) + { + outputs_arr.assign(std::vector{outBlob}); + } + } + +private: + double mean; + double scale; + bool hasSeed = false; + double seed = 0.0; + int depth; +}; + +CV_DNN_REGISTER_LAYER_CLASS_STATIC(RandomNormalLike, RandomNormalLikeLayerImpl); + +}} // namespace cv::dnn diff --git a/modules/dnn/src/onnx/onnx_importer.cpp b/modules/dnn/src/onnx/onnx_importer.cpp index 93198d66d1..713867287e 100644 --- a/modules/dnn/src/onnx/onnx_importer.cpp +++ b/modules/dnn/src/onnx/onnx_importer.cpp @@ -191,6 +191,7 @@ private: void parseElementWise (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseDepthSpaceOps (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseRange (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); + void parseRandomNormalLike (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseScatter (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseTile (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseLayerNorm (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); @@ -2952,6 +2953,14 @@ void ONNXImporter::parseRange(LayerParams& layerParams, const opencv_onnx::NodeP constBlobsExtraInfo.insert(std::make_pair(node_proto.output(0), TensorInfo(1))); } +void ONNXImporter::parseRandomNormalLike(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) +{ + CV_CheckEQ(node_proto.input_size(), 1, "RandomNormalLike: one input is required"); + + layerParams.type = "RandomNormalLike"; + addLayer(layerParams, node_proto); +} + void ONNXImporter::parseScatter(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) { CV_CheckEQ(node_proto.input_size(), 3, "Scatter: three inputs are required."); @@ -3958,6 +3967,7 @@ void ONNXImporter::buildDispatchMap_ONNX_AI(int opset_version) dispatch["Sum"] = dispatch["Min"] = dispatch["Max"] = dispatch["Mean"] = &ONNXImporter::parseElementWise; dispatch["Where"] = &ONNXImporter::parseElementWise; dispatch["Range"] = &ONNXImporter::parseRange; + dispatch["RandomNormalLike"] = &ONNXImporter::parseRandomNormalLike; dispatch["Einsum"] = &ONNXImporter::parseEinsum; std::vector simpleLayers{"Acos", "Acosh", "Asin", "Asinh", "Atan", "Atanh", "Ceil", "Celu", "Cos", diff --git a/modules/dnn/test/test_onnx_importer.cpp b/modules/dnn/test/test_onnx_importer.cpp index 70859092a7..c1b50f3fcf 100644 --- a/modules/dnn/test/test_onnx_importer.cpp +++ b/modules/dnn/test/test_onnx_importer.cpp @@ -3233,6 +3233,49 @@ TEST_P(Test_ONNX_layers, TopK) { test("top_k_smallest"); } +TEST_P(Test_ONNX_layers, RandomNormalLike_basic) +{ + Net net = readNetFromONNX(findDataFile("dnn/onnx/models/random_normal_like.onnx", true)); + + Mat input(2, 3, CV_32F, Scalar(0)); + net.setInput(input); + Mat out = net.forward(); + + EXPECT_EQ(out.rows, 2); + EXPECT_EQ(out.cols, 3); + EXPECT_EQ(out.type(), CV_32F); + + double minVal, maxVal; + minMaxLoc(out, &minVal, &maxVal); + EXPECT_NE(minVal, 0.0); + EXPECT_NE(maxVal, 0.0); + EXPECT_NE(minVal, maxVal); + + Mat out2 = net.forward(); + EXPECT_EQ(countNonZero(out != out2), 0); +} + +TEST_P(Test_ONNX_layers, RandomNormalLike_complex) +{ + Net net = readNetFromONNX(findDataFile("dnn/onnx/models/random_normal_like_complex.onnx", true)); + + Mat input(2, 3, CV_32F, Scalar(0)); + net.setInput(input); + Mat out = net.forward(); + + EXPECT_EQ(out.rows, 2); + EXPECT_EQ(out.cols, 3); + EXPECT_EQ(out.type(), CV_32F); + + double minVal, maxVal; + minMaxLoc(out, &minVal, &maxVal); + EXPECT_NE(minVal, maxVal); + + net.setInput(input); + Mat out2 = net.forward(); + EXPECT_EQ(countNonZero(out != out2), 0); +} + INSTANTIATE_TEST_CASE_P(/**/, Test_ONNX_nets, dnnBackendsAndTargets()); }} // namespace