1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-29 15:23:05 +04:00

Merge pull request #28574 from abhishek-gola:reduce_layer_empty_set

* added empty set support

* accept unnamed dynamic dims

* Fix for LSTM test failure

* removed converToND

* openvino failing test fix

* ARM CI issue fix

* ARM issue fix
This commit is contained in:
Abhishek Gola
2026-03-01 17:49:44 +05:30
committed by GitHub
parent b54d0fbbbf
commit 31cab2a055
10 changed files with 141 additions and 40 deletions
+9 -1
View File
@@ -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;
}
@@ -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<Func>::apply(func, src, dst))
@@ -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];
}
}
}
+26 -6
View File
@@ -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<AccT>(static_cast<WT>(init))) {}
AccT finalize() const { return accumulator_; }
protected:
@@ -179,7 +181,8 @@ public:
public:
using Base = ReduceBase<T, WT, AccT>;
ReduceMin(size_t n, const WT& init) : Base(n, static_cast<T>(init)) { this->accumulator_ = static_cast<AccT>(init); }
inline void update(const WT& a) { this->accumulator_ = a > static_cast<WT>(this->accumulator_) ? this->accumulator_ : static_cast<AccT>(a); }
inline void update(const WT& a) { this->accumulator_ = a < static_cast<WT>(this->accumulator_) ? static_cast<AccT>(a) : this->accumulator_; }
static WT identity() { return std::numeric_limits<WT>::has_infinity ? std::numeric_limits<WT>::infinity() : std::numeric_limits<WT>::max(); }
};
template <typename T, typename WT, typename AccT>
@@ -188,6 +191,7 @@ public:
using Base = ReduceBase<T, WT, AccT>;
ReduceMax(size_t n, const WT& init) : Base(n, static_cast<T>(init)) { this->accumulator_ = static_cast<AccT>(init); }
inline void update(const WT& a) { this->accumulator_ = a > static_cast<WT>(this->accumulator_) ? static_cast<AccT>(a) : this->accumulator_; }
static WT identity() { return std::numeric_limits<WT>::has_infinity ? -std::numeric_limits<WT>::infinity() : std::numeric_limits<WT>::lowest(); }
};
template <typename T, typename WT, typename AccT>
@@ -196,6 +200,7 @@ public:
using Base = ReduceBase<T, WT, AccT>;
ReduceSum(size_t n, const WT&) : Base(n, static_cast<T>(0)) { this->accumulator_ = AccT(0); }
inline void update(const WT& a) { this->accumulator_ += static_cast<AccT>(a); }
static WT identity() { return WT(0); }
};
template <typename T, typename WT, typename AccT>
@@ -203,7 +208,10 @@ public:
public:
using Base = ReduceSum<T, WT, AccT>;
ReduceMean(size_t n, const WT& init) : Base(n, init) {}
inline AccT finalize() const { return this->accumulator_ / static_cast<AccT>(this->n_); }
inline AccT finalize() const {
return (this->n_ > 0) ? (this->accumulator_ / static_cast<AccT>(this->n_)) : AccT(0);
}
static WT identity() { return WT(0); }
};
template <typename T, typename WT, typename AccT>
@@ -212,6 +220,7 @@ public:
using Base = ReduceBase<T, WT, AccT>;
ReduceSumSquare(size_t n, const WT&) : Base(n, static_cast<T>(0)) { this->accumulator_ = AccT(0); }
inline void update(const WT& a) { this->accumulator_ += static_cast<AccT>(a) * static_cast<AccT>(a); }
static WT identity() { return WT(0); }
};
template <typename T, typename WT, typename AccT>
@@ -220,6 +229,7 @@ public:
using Base = ReduceBase<T, WT, AccT>;
ReduceL1(size_t n, const WT&) : Base(n, static_cast<T>(0)) { this->accumulator_ = AccT(0); }
inline void update(const WT& a) { this->accumulator_ += static_cast<AccT>(a >= WT(0) ? a : -a); }
static WT identity() { return WT(0); }
};
template <typename T, typename WT, typename AccT>
@@ -229,6 +239,7 @@ public:
ReduceL2(size_t n, const WT&) : Base(n, static_cast<T>(0)) { this->accumulator_ = AccT(0); }
inline void update(const WT& a) { this->accumulator_ += static_cast<AccT>(a) * static_cast<AccT>(a); }
inline AccT finalize() const { return static_cast<AccT>(std::sqrt(this->accumulator_)); }
static WT identity() { return WT(0); }
};
template <typename T, typename WT, typename AccT>
@@ -237,6 +248,7 @@ public:
using Base = ReduceBase<T, WT, AccT>;
ReduceProd(size_t n, const WT&) : Base(n, static_cast<T>(1)) { this->accumulator_ = static_cast<AccT>(WT(1)); }
inline void update(const WT& a) { this->accumulator_ = static_cast<AccT>(this->accumulator_) * static_cast<AccT>(a); }
static WT identity() { return WT(1); }
};
template <typename T, typename WT, typename AccT>
@@ -245,7 +257,10 @@ public:
using Base = ReduceBase<T, WT, AccT>;
ReduceLogSum(size_t n, const WT&) : Base(n, static_cast<T>(0)) { this->accumulator_ = AccT(0); }
inline void update(const WT& a) { this->accumulator_ += static_cast<AccT>(a); }
inline AccT finalize() const { return static_cast<AccT>(std::log(this->accumulator_)); }
inline AccT finalize() const {
return (this->n_ > 0) ? static_cast<AccT>(std::log(this->accumulator_)) : -std::numeric_limits<AccT>::infinity();
}
static WT identity() { return -std::numeric_limits<WT>::infinity(); }
};
template <typename T, typename WT, typename AccT>
@@ -255,6 +270,7 @@ public:
ReduceLogSumExp(size_t n, const WT&) : Base(n, static_cast<T>(0)) { this->accumulator_ = AccT(0); }
inline void update(const WT& a) { this->accumulator_ += static_cast<AccT>(std::exp(static_cast<AccT>(a))); }
inline AccT finalize() const { return static_cast<AccT>(std::log(this->accumulator_)); }
static WT identity() { return -std::numeric_limits<WT>::infinity(); }
};
template <typename Op>
@@ -388,6 +404,11 @@ public:
static void run(const Mat& src, Mat& dst, std::vector<int> axes, bool noop_with_empty_axes) {
CV_Assert(src.isContinuous());
CV_Assert(dst.isContinuous());
if (src.total() == 0) {
dst.setTo(Scalar(static_cast<double>(Op::identity())));
return;
}
if (shape(src).empty() || (shape(src).size() == 1)){
ReduceAllInvoker<Op> 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;
}
+28 -13
View File
@@ -64,6 +64,25 @@ static T getScalarFromMat(Mat m)
return m.at<T>(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<int>("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<int>("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<int> 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;
}
+18 -2
View File
@@ -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;
+9
View File
@@ -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);
@@ -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)
@@ -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",
@@ -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",