diff --git a/modules/core/src/convert.dispatch.cpp b/modules/core/src/convert.dispatch.cpp index 85d6e26841..a18d4beb85 100644 --- a/modules/core/src/convert.dispatch.cpp +++ b/modules/core/src/convert.dispatch.cpp @@ -134,8 +134,16 @@ void Mat::convertTo(OutputArray dst, int type_, double alpha, double beta) const if (empty()) { + int ddepth = CV_MAT_DEPTH(type_); + if (ddepth < 0) + ddepth = dst.fixedType() ? dst.depth() : depth(); + const int dtype = CV_MAKETYPE(ddepth, channels()); + dst.release(); - dst.create(size(), type_ >= 0 ? type_ : type()); + if (dims <= 2) + dst.create(size(), dtype); + else + dst.create(dims, size.p, dtype); return; } diff --git a/modules/dnn/src/layers/elementwise_layers.cpp b/modules/dnn/src/layers/elementwise_layers.cpp index 094328ae85..5cc913c836 100644 --- a/modules/dnn/src/layers/elementwise_layers.cpp +++ b/modules/dnn/src/layers/elementwise_layers.cpp @@ -232,6 +232,10 @@ public: { const Mat &src = inputs[i]; Mat &dst = outputs[i]; + + if (src.total() == 0) + continue; + CV_Assert_N(src.size == dst.size, src.isContinuous(), dst.isContinuous()); if (ElementWiseIntDispatch::apply(func, src, dst)) diff --git a/modules/dnn/src/layers/nary_eltwise_layers.cpp b/modules/dnn/src/layers/nary_eltwise_layers.cpp index 2b317d927a..4b9f80a377 100644 --- a/modules/dnn/src/layers/nary_eltwise_layers.cpp +++ b/modules/dnn/src/layers/nary_eltwise_layers.cpp @@ -117,8 +117,6 @@ public: assert(st_i % elemsize[k] == 0); this->shapes[k][i] = sz_i; this->steps[k][i] = st_i; - if (this->shapes[k][i] == 0) - return false; } } @@ -316,7 +314,8 @@ public: if (shape[i] != outShape[i]) { CV_Assert(shape[i] == 1 || outShape[i] == 1); - outShape[i] = std::max(outShape[i], shape[i]); + if (outShape[i] == 1) + outShape[i] = shape[i]; } } } diff --git a/modules/dnn/src/layers/reduce2_layer.cpp b/modules/dnn/src/layers/reduce2_layer.cpp index 56c59e96ce..615c4dd37d 100644 --- a/modules/dnn/src/layers/reduce2_layer.cpp +++ b/modules/dnn/src/layers/reduce2_layer.cpp @@ -167,6 +167,8 @@ public: using dtype_input = T; using work_type = WT; using acc_type = AccT; + typedef T dtype; + typedef WT WorkT; ReduceBase(size_t n, const T& init) : n_(n), accumulator_(static_cast(static_cast(init))) {} AccT finalize() const { return accumulator_; } protected: @@ -179,7 +181,8 @@ public: public: using Base = ReduceBase; ReduceMin(size_t n, const WT& init) : Base(n, static_cast(init)) { this->accumulator_ = static_cast(init); } - inline void update(const WT& a) { this->accumulator_ = a > static_cast(this->accumulator_) ? this->accumulator_ : static_cast(a); } + inline void update(const WT& a) { this->accumulator_ = a < static_cast(this->accumulator_) ? static_cast(a) : this->accumulator_; } + static WT identity() { return std::numeric_limits::has_infinity ? std::numeric_limits::infinity() : std::numeric_limits::max(); } }; template @@ -188,6 +191,7 @@ public: using Base = ReduceBase; ReduceMax(size_t n, const WT& init) : Base(n, static_cast(init)) { this->accumulator_ = static_cast(init); } inline void update(const WT& a) { this->accumulator_ = a > static_cast(this->accumulator_) ? static_cast(a) : this->accumulator_; } + static WT identity() { return std::numeric_limits::has_infinity ? -std::numeric_limits::infinity() : std::numeric_limits::lowest(); } }; template @@ -196,6 +200,7 @@ public: using Base = ReduceBase; ReduceSum(size_t n, const WT&) : Base(n, static_cast(0)) { this->accumulator_ = AccT(0); } inline void update(const WT& a) { this->accumulator_ += static_cast(a); } + static WT identity() { return WT(0); } }; template @@ -203,7 +208,10 @@ public: public: using Base = ReduceSum; ReduceMean(size_t n, const WT& init) : Base(n, init) {} - inline AccT finalize() const { return this->accumulator_ / static_cast(this->n_); } + inline AccT finalize() const { + return (this->n_ > 0) ? (this->accumulator_ / static_cast(this->n_)) : AccT(0); + } + static WT identity() { return WT(0); } }; template @@ -212,6 +220,7 @@ public: using Base = ReduceBase; ReduceSumSquare(size_t n, const WT&) : Base(n, static_cast(0)) { this->accumulator_ = AccT(0); } inline void update(const WT& a) { this->accumulator_ += static_cast(a) * static_cast(a); } + static WT identity() { return WT(0); } }; template @@ -220,6 +229,7 @@ public: using Base = ReduceBase; ReduceL1(size_t n, const WT&) : Base(n, static_cast(0)) { this->accumulator_ = AccT(0); } inline void update(const WT& a) { this->accumulator_ += static_cast(a >= WT(0) ? a : -a); } + static WT identity() { return WT(0); } }; template @@ -229,6 +239,7 @@ public: ReduceL2(size_t n, const WT&) : Base(n, static_cast(0)) { this->accumulator_ = AccT(0); } inline void update(const WT& a) { this->accumulator_ += static_cast(a) * static_cast(a); } inline AccT finalize() const { return static_cast(std::sqrt(this->accumulator_)); } + static WT identity() { return WT(0); } }; template @@ -237,6 +248,7 @@ public: using Base = ReduceBase; ReduceProd(size_t n, const WT&) : Base(n, static_cast(1)) { this->accumulator_ = static_cast(WT(1)); } inline void update(const WT& a) { this->accumulator_ = static_cast(this->accumulator_) * static_cast(a); } + static WT identity() { return WT(1); } }; template @@ -245,7 +257,10 @@ public: using Base = ReduceBase; ReduceLogSum(size_t n, const WT&) : Base(n, static_cast(0)) { this->accumulator_ = AccT(0); } inline void update(const WT& a) { this->accumulator_ += static_cast(a); } - inline AccT finalize() const { return static_cast(std::log(this->accumulator_)); } + inline AccT finalize() const { + return (this->n_ > 0) ? static_cast(std::log(this->accumulator_)) : -std::numeric_limits::infinity(); + } + static WT identity() { return -std::numeric_limits::infinity(); } }; template @@ -255,6 +270,7 @@ public: ReduceLogSumExp(size_t n, const WT&) : Base(n, static_cast(0)) { this->accumulator_ = AccT(0); } inline void update(const WT& a) { this->accumulator_ += static_cast(std::exp(static_cast(a))); } inline AccT finalize() const { return static_cast(std::log(this->accumulator_)); } + static WT identity() { return -std::numeric_limits::infinity(); } }; template @@ -388,6 +404,11 @@ public: static void run(const Mat& src, Mat& dst, std::vector axes, bool noop_with_empty_axes) { CV_Assert(src.isContinuous()); CV_Assert(dst.isContinuous()); + if (src.total() == 0) { + dst.setTo(Scalar(static_cast(Op::identity()))); + return; + } + if (shape(src).empty() || (shape(src).size() == 1)){ ReduceAllInvoker p(src, dst); p(Range(0, p.total)); @@ -468,8 +489,7 @@ public: outShape = inpShape; } else { if (keepdims) { - outShape = inpShape; - for (int i = 0; i < (int)outShape.size(); ++i) outShape[i] = 1; + outShape.assign(inpShape.size(), 1); } else { outShape = MatShape::scalar(); } @@ -487,7 +507,7 @@ public: outShape.push_back(tmp[i]); } } - if (outShape.empty()) outShape = MatShape::scalar(); + if (outShape.size() == 0) outShape = MatShape{1}; axes = norm_axes; } diff --git a/modules/dnn/src/onnx/onnx_importer.cpp b/modules/dnn/src/onnx/onnx_importer.cpp index 38ee54589b..a9f020e69c 100644 --- a/modules/dnn/src/onnx/onnx_importer.cpp +++ b/modules/dnn/src/onnx/onnx_importer.cpp @@ -64,6 +64,25 @@ static T getScalarFromMat(Mat m) return m.at(0); } +static int onnxDataTypeToCvDepth(int onnxType) +{ + switch (onnxType) + { + case opencv_onnx::TensorProto_DataType_FLOAT: return CV_32F; + case opencv_onnx::TensorProto_DataType_UINT8: return CV_8U; + case opencv_onnx::TensorProto_DataType_UINT16: return CV_16U; + case opencv_onnx::TensorProto_DataType_FLOAT16: return CV_16F; + case opencv_onnx::TensorProto_DataType_INT8: return CV_8S; + case opencv_onnx::TensorProto_DataType_INT16: return CV_16S; + case opencv_onnx::TensorProto_DataType_INT32: return CV_32S; + case opencv_onnx::TensorProto_DataType_INT64: return CV_64S; + case opencv_onnx::TensorProto_DataType_BOOL: return CV_Bool; + case opencv_onnx::TensorProto_DataType_DOUBLE: return CV_64F; + case opencv_onnx::TensorProto_DataType_BFLOAT16: return CV_16BF; + default: return CV_32F; + } +} + class ONNXImporter { FPDenormalsIgnoreHintScope fp_denormals_ignore_scope; @@ -2525,19 +2544,7 @@ void ONNXImporter::parseShape(LayerParams& layerParams, const opencv_onnx::NodeP void ONNXImporter::parseCast(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) { - int type; - switch (layerParams.get("to")) - { - case opencv_onnx::TensorProto_DataType_FLOAT: type = CV_32F; break; - case opencv_onnx::TensorProto_DataType_UINT8: type = CV_8U; break; - case opencv_onnx::TensorProto_DataType_UINT16: type = CV_16U; break; - case opencv_onnx::TensorProto_DataType_FLOAT16: type = CV_16F; break; - case opencv_onnx::TensorProto_DataType_INT8: type = CV_8S; break; - case opencv_onnx::TensorProto_DataType_INT16: type = CV_16S; break; - case opencv_onnx::TensorProto_DataType_INT32: type = CV_32S; break; - case opencv_onnx::TensorProto_DataType_INT64: type = CV_64S; break; - default: CV_Error(Error::BadDepth, "Unsupported type"); - } + const int type = onnxDataTypeToCvDepth(layerParams.get("to")); if (constBlobs.find(node_proto.input(0)) != constBlobs.end()) { @@ -4236,6 +4243,14 @@ Mat readTensorFromONNX(const String& path) CV_Error(Error::StsUnsupportedFormat, cv::format("Failed to parse ONNX data: %s", path.c_str())); } Mat mat = getMatFromTensor(tensor_proto, false); + int dims = (int)tensor_proto.dims_size(); + if (dims > 0 && mat.total() == 0) { + int cv_type = onnxDataTypeToCvDepth(tensor_proto.data_type()); + + std::vector sizes(dims); + for (int i = 0; i < dims; ++i) sizes[i] = (int)tensor_proto.dims(i); + mat.create(dims, sizes.data(), cv_type); + } releaseONNXTensor(tensor_proto); return mat; } diff --git a/modules/dnn/src/onnx/onnx_importer2.cpp b/modules/dnn/src/onnx/onnx_importer2.cpp index db6b9e9dac..4bfab6728c 100644 --- a/modules/dnn/src/onnx/onnx_importer2.cpp +++ b/modules/dnn/src/onnx/onnx_importer2.cpp @@ -734,8 +734,24 @@ bool ONNXImporter2::parseValueInfo(const opencv_onnx::ValueInfoProto& valueInfoP const std::string& param_j = dimension.dim_param(); val_j = net.findDim(param_j, true); } else { - raiseError(); - return false; + // ONNX allows dimensions without dim_value and dim_param. + // Treat them as unnamed symbolic dimensions. + // NOTE: LSTM with unnamed dimensions is not ready in the new graph + // engine yet, so force fallback to classic parser. + if (curr_graph_proto) + { + const int n_nodes = curr_graph_proto->node_size(); + for (int i = 0; i < n_nodes; ++i) + { + const std::string& op = curr_graph_proto->node(i).op_type(); + if (op == "LSTM") + { + raiseError(); + return false; + } + } + } + val_j = net.findDim("", true); } //CV_Assert(0 <= val_j && val_j <= INT_MAX); shape[j] = (int)val_j; diff --git a/modules/dnn/test/test_common.impl.hpp b/modules/dnn/test/test_common.impl.hpp index 499b610b4a..ad77db95fc 100644 --- a/modules/dnn/test/test_common.impl.hpp +++ b/modules/dnn/test/test_common.impl.hpp @@ -95,6 +95,15 @@ void normAssert( testMat = testMat.reshape(1, oneShape); } + // Empty tensors are valid for ONNX conformance tests. Avoid 0/0 in normL1 + // and verify emptiness compatibility directly. + if (refMat.total() == 0 || testMat.total() == 0) + { + EXPECT_EQ(refMat.total(), testMat.total()) << comment; + EXPECT_EQ(refMat.size, testMat.size) << comment; + return; + } + double normL1 = cvtest::norm(refMat, testMat, cv::NORM_L1) / refMat.total(); EXPECT_LE(normL1, l1) << comment << " |ref| = " << cvtest::norm(refMat, cv::NORM_INF); 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 f7f37ba88a..67336a2ea2 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 @@ -2129,6 +2129,36 @@ CASE(test_reduce_log_sum_exp_negative_axes_keepdims_random_expanded) SKIP; CASE(test_reduce_sum_empty_axes_input_noop) SKIP; +CASE(test_reduce_l1_empty_set) + SKIP; +CASE(test_reduce_l1_empty_set_expanded) + SKIP; +CASE(test_reduce_l2_empty_set) + SKIP; +CASE(test_reduce_l2_empty_set_expanded) + SKIP; +CASE(test_reduce_log_sum_empty_set) + SKIP; +CASE(test_reduce_log_sum_empty_set_expanded) + SKIP; +CASE(test_reduce_log_sum_exp_empty_set) + SKIP; +CASE(test_reduce_max_empty_set) + SKIP; +CASE(test_reduce_min_empty_set) + SKIP; +CASE(test_reduce_prod_empty_set) + SKIP; +CASE(test_reduce_sum_empty_set) + SKIP; +CASE(test_reduce_sum_empty_set_non_reduced_axis_zero) + SKIP; +CASE(test_reduce_sum_square_empty_set) + SKIP; +CASE(test_reduce_sum_square_empty_set_expanded) + SKIP; +CASE(test_reduce_log_sum_exp_empty_set_expanded) + SKIP; CASE(test_reduce_sum_square_default_axes_keepdims_random) #if SKIP_SET_1 if (target == DNN_TARGET_MYRIAD) 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 173e6ffe49..6f1a9ad09d 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 @@ -718,3 +718,18 @@ "test_gru_defaults", "test_gru_seq_length", "test_gru_with_initial_bias", +"test_reduce_l1_empty_set", +"test_reduce_l1_empty_set_expanded", +"test_reduce_l2_empty_set", +"test_reduce_l2_empty_set_expanded", +"test_reduce_log_sum_empty_set", +"test_reduce_log_sum_empty_set_expanded", +"test_reduce_log_sum_exp_empty_set", +"test_reduce_max_empty_set", +"test_reduce_min_empty_set", +"test_reduce_prod_empty_set", +"test_reduce_sum_empty_set", +"test_reduce_sum_empty_set_non_reduced_axis_zero", +"test_reduce_sum_square_empty_set", +"test_reduce_sum_square_empty_set_expanded", +"test_reduce_log_sum_exp_empty_set_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 0a82f6ecbf..62cd2624e3 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 @@ -395,21 +395,6 @@ "test_quantizelinear_uint4", "test_range_float_type_positive_delta_expanded", // ---- Unsupported operations: Loop --- "test_range_int32_type_negative_delta_expanded", // ---- same as above --- -"test_reduce_l1_empty_set", -"test_reduce_l1_empty_set_expanded", -"test_reduce_l2_empty_set", -"test_reduce_l2_empty_set_expanded", -"test_reduce_log_sum_empty_set", -"test_reduce_log_sum_empty_set_expanded", -"test_reduce_log_sum_exp_empty_set", -"test_reduce_log_sum_exp_empty_set_expanded", -"test_reduce_max_empty_set", -"test_reduce_min_empty_set", -"test_reduce_prod_empty_set", -"test_reduce_sum_empty_set", -"test_reduce_sum_empty_set_non_reduced_axis_zero", -"test_reduce_sum_square_empty_set", -"test_reduce_sum_square_empty_set_expanded", "test_regex_full_match_basic", "test_regex_full_match_email_domain", "test_regex_full_match_empty",