diff --git a/modules/core/src/count_non_zero.dispatch.cpp b/modules/core/src/count_non_zero.dispatch.cpp index 416a07aa43..e7a8aaef58 100644 --- a/modules/core/src/count_non_zero.dispatch.cpp +++ b/modules/core/src/count_non_zero.dispatch.cpp @@ -198,7 +198,9 @@ void findNonZero(InputArray _src, OutputArray _idx) { const ushort* ptr16 = (const ushort*)ptr8; for( j = 0; j < cols; j++ ) - if( (ptr16[j]<<1) != 0 ) buf[k++] = j; + // mask sign bit so -0 (0x8000) is treated as zero; cannot use <<1 here + // because ushort is promoted to int and the sign bit would not be discarded + if( (ptr16[j] & 0x7fff) != 0 ) buf[k++] = j; } else { diff --git a/modules/core/src/count_non_zero.simd.hpp b/modules/core/src/count_non_zero.simd.hpp index 9c61a54a4e..983ade0308 100644 --- a/modules/core/src/count_non_zero.simd.hpp +++ b/modules/core/src/count_non_zero.simd.hpp @@ -89,6 +89,9 @@ static int funcname( const void* src_ptr, int len ) \ #define CHECK_NZ_INT(x) ((x) != 0) #undef CHECK_NZ_FP #define CHECK_NZ_FP(x) ((x)*2 != 0) +// 16-bit float: mask the sign bit so -0.0 (0x8000) reads as zero. +#undef CHECK_NZ_FP16 +#define CHECK_NZ_FP16(x) (((x) & 0x7fff) != 0) #undef VEC_CMP_EQ_Z_FP16 #define VEC_CMP_EQ_Z_FP16(x, z) v_eq(v_add_wrap(x, x), z) #undef VEC_CMP_EQ_Z_FP @@ -116,7 +119,7 @@ DEFINE_NONZERO_FUNC(countNonZero8u, u8, u32, uchar, v_uint8, v_uint32, v_eq, v_a DEFINE_NONZERO_FUNC(countNonZero16u, u16, u32, ushort, v_uint16, v_uint32, v_eq, v_add_wrap, UPDATE_SUM_U16, CHECK_NZ_INT) DEFINE_NONZERO_FUNC(countNonZero32s, s32, s32, int, v_int32, v_int32, v_eq, v_add, UPDATE_SUM_S32, CHECK_NZ_INT) DEFINE_NONZERO_FUNC(countNonZero32f, u32, u32, uint, v_uint32, v_uint32, VEC_CMP_EQ_Z_FP, v_add, UPDATE_SUM_S32, CHECK_NZ_FP) -DEFINE_NONZERO_FUNC(countNonZero16f, u16, u32, ushort, v_uint16, v_uint32, VEC_CMP_EQ_Z_FP16, v_add_wrap, UPDATE_SUM_U16, CHECK_NZ_FP) +DEFINE_NONZERO_FUNC(countNonZero16f, u16, u32, ushort, v_uint16, v_uint32, VEC_CMP_EQ_Z_FP16, v_add_wrap, UPDATE_SUM_U16, CHECK_NZ_FP16) #undef DEFINE_NONZERO_FUNC_NOSIMD #define DEFINE_NONZERO_FUNC_NOSIMD(funcname, T) \ diff --git a/modules/core/test/test_arithm.cpp b/modules/core/test/test_arithm.cpp index 5bc7c23965..4294e8750f 100644 --- a/modules/core/test/test_arithm.cpp +++ b/modules/core/test/test_arithm.cpp @@ -3585,6 +3585,83 @@ INSTANTIATE_TEST_CASE_P( testing::Values(CV_16BF, CV_Bool, CV_64U, CV_64S, CV_32U) ); +typedef testing::TestWithParam NonZeroAccuracyNewTypes; + +TEST_P(NonZeroAccuracyNewTypes, accuracy) +{ + const int depth = GetParam(); + const Size sz(123, 71); + cv::Mat src = cv::Mat::zeros(sz, CV_MAKETYPE(depth, 1)); + + std::vector expected_pts; + const int total = sz.area(); + const int approx_nz = std::max(1, total / 17); + std::vector is_nz(total, 0); + for (int n = 0; n < approx_nz; ) + { + int idx = theRNG().uniform(0, total); + if (is_nz[idx]) + continue; + is_nz[idx] = 1; + ++n; + } + + auto setNonZero = [&](int y, int x) + { + switch(depth) + { + case CV_Bool: src.at(y, x) = 1; break; + case CV_16BF: src.at(y, x) = 0x3F80; /* bf16(1.0f) */ break; + case CV_32U: src.at(y, x) = 7u; break; + case CV_64U: src.at(y, x) = 7ULL; break; + case CV_64S: src.at(y, x) = -7LL; break; + default: FAIL() << "Unexpected depth " << depth; + } + }; + + int nz_ref = 0; + for (int y = 0; y < sz.height; ++y) + for (int x = 0; x < sz.width; ++x) + if (is_nz[y*sz.width + x]) + { + setNonZero(y, x); + expected_pts.emplace_back(x, y); + ++nz_ref; + } + + EXPECT_EQ(nz_ref, cv::countNonZero(src)); + EXPECT_EQ(nz_ref > 0, cv::hasNonZero(src)); + cv::Mat zeros = cv::Mat::zeros(sz, src.type()); + EXPECT_FALSE(cv::hasNonZero(zeros)); + + std::vector pts; + cv::findNonZero(src, pts); + ASSERT_EQ(expected_pts.size(), pts.size()); + for (size_t i = 0; i < pts.size(); ++i) + { + EXPECT_EQ(expected_pts[i].x, pts[i].x) << "i=" << i; + EXPECT_EQ(expected_pts[i].y, pts[i].y) << "i=" << i; + } +} + +TEST(NonZeroAccuracyNewTypes_BF16, negative_zero_is_zero) +{ + const Size sz(64, 32); + cv::Mat src(sz, CV_16BFC1); + src.setTo(cv::Scalar::all(-0.0)); + EXPECT_EQ(0, cv::countNonZero(src)); + EXPECT_FALSE(cv::hasNonZero(src)); + std::vector pts; + cv::findNonZero(src, pts); + EXPECT_TRUE(pts.empty()); +} + +INSTANTIATE_TEST_CASE_P( + NonZeroAcc, + NonZeroAccuracyNewTypes, + testing::Values(CV_16BF, CV_Bool, CV_64U, CV_64S, CV_32U) +); + /////////////////////////////////////////////////////////////////////////////////// typedef testing::TestWithParam MinMaxSupportedMatDepth; @@ -3611,6 +3688,212 @@ INSTANTIATE_TEST_CASE_P( testing::Values(perf::MatDepth(CV_16F), CV_16BF, CV_Bool, CV_64U, CV_64S, CV_32U) ); +typedef testing::TestWithParam MinMaxAccuracyNewTypes; + +TEST_P(MinMaxAccuracyNewTypes, accuracy) +{ + const int depth = GetParam(); + const Size sz(173, 91); + + double fill_val = 0, min_val = 0, max_val = 0; + Point min_pos(11, 7), max_pos(150, 80); + ASSERT_TRUE(min_pos != max_pos); + + switch (depth) + { + case CV_Bool: fill_val = 0; min_val = 0; max_val = 1; break; + case CV_16BF: fill_val = 1.5; min_val = -123.5; max_val = 4096.0; break; + case CV_32U: fill_val = 1000.0; min_val = 7.0; + max_val = (double)std::numeric_limits::max() - 1.0; break; + case CV_64U: fill_val = 1e15; min_val = 0.0; max_val = 1e18; break; + case CV_64S: fill_val = 0.0; min_val = -1e17; max_val = 1e17; break; + default: FAIL() << "Unexpected depth " << depth; + } + + cv::Mat src(sz, CV_MAKETYPE(depth, 1)); + if (depth == CV_Bool) src.setTo(cv::Scalar::all(0)); + else src.setTo(cv::Scalar::all(fill_val)); + + auto put = [&](Point p, double v) + { + switch(depth) + { + case CV_Bool: src.at(p.y, p.x) = (uchar)v; break; + case CV_16BF: src.at(p.y, p.x) = cv::bfloat((float)v); break; + case CV_32U: src.at(p.y, p.x) = (uint32_t)v; break; + case CV_64U: src.at(p.y, p.x) = (uint64_t)v; break; + case CV_64S: src.at(p.y, p.x) = (int64_t)v; break; + default: FAIL() << "Unexpected depth " << depth; + } + }; + put(min_pos, min_val); + put(max_pos, max_val); + + // minMaxLoc: 2D-only, returns Points. + { + double minV = 0, maxV = 0; + Point minLoc(-1, -1), maxLoc(-1, -1); + cv::minMaxLoc(src, &minV, &maxV, &minLoc, &maxLoc); + EXPECT_NEAR(min_val, minV, 1e-5 * std::max(1.0, std::fabs(min_val))); + EXPECT_NEAR(max_val, maxV, 1e-5 * std::max(1.0, std::fabs(max_val))); + if (depth != CV_Bool) + { + EXPECT_EQ(min_pos, minLoc); + } + EXPECT_EQ(max_pos, maxLoc); + } + + // minMaxIdx: same data, idx[] form (row, col). + { + double minV = 0, maxV = 0; + int minIdx[2] = {-1, -1}, maxIdx[2] = {-1, -1}; + cv::minMaxIdx(src, &minV, &maxV, minIdx, maxIdx); + EXPECT_NEAR(min_val, minV, 1e-5 * std::max(1.0, std::fabs(min_val))); + EXPECT_NEAR(max_val, maxV, 1e-5 * std::max(1.0, std::fabs(max_val))); + if (depth != CV_Bool) + { + EXPECT_EQ(min_pos.y, minIdx[0]); + EXPECT_EQ(min_pos.x, minIdx[1]); + } + EXPECT_EQ(max_pos.y, maxIdx[0]); + EXPECT_EQ(max_pos.x, maxIdx[1]); + } +} + +// Mask-aware accuracy: extreme values outside the mask must be ignored. +TEST_P(MinMaxAccuracyNewTypes, accuracy_with_mask) +{ + const int depth = GetParam(); + const Size sz(64, 48); + cv::Mat src(sz, CV_MAKETYPE(depth, 1)); + cv::Mat mask = cv::Mat::zeros(sz, CV_8UC1); + + double fill_val = 0, masked_min = 0, masked_max = 0, outlier_min = 0, outlier_max = 0; + Point min_pos(5, 5), max_pos(40, 30), outlier_min_pos(1, 1), outlier_max_pos(60, 45); + + switch (depth) + { + case CV_Bool: + fill_val = 0; masked_min = 0; masked_max = 1; + outlier_min = 0; outlier_max = 1; break; + case CV_16BF: + fill_val = 1.0; masked_min = -10.0; masked_max = 10.0; + outlier_min = -1000.0; outlier_max = 1000.0; break; + case CV_32U: + fill_val = 100; masked_min = 1; masked_max = 1000; + outlier_min = 0; outlier_max = (double)std::numeric_limits::max(); break; + case CV_64U: + fill_val = 1e10; masked_min = 1.0; masked_max = 1e12; + outlier_min = 0.0; outlier_max = 1e18; break; + case CV_64S: + fill_val = 0.0; masked_min = -1e10; masked_max = 1e10; + outlier_min = -1e17; outlier_max = 1e17; break; + default: FAIL() << "Unexpected depth " << depth; + } + + src.setTo(cv::Scalar::all(fill_val)); + + auto put = [&](Point p, double v) + { + switch(depth) + { + case CV_Bool: src.at(p.y, p.x) = (uchar)v; break; + case CV_16BF: src.at(p.y, p.x) = cv::bfloat((float)v); break; + case CV_32U: src.at(p.y, p.x) = (uint32_t)v; break; + case CV_64U: src.at(p.y, p.x) = (uint64_t)v; break; + case CV_64S: src.at(p.y, p.x) = (int64_t)v; break; + default: FAIL() << "Unexpected depth " << depth; + } + }; + put(min_pos, masked_min); + put(max_pos, masked_max); + put(outlier_min_pos, outlier_min); + put(outlier_max_pos, outlier_max); + + mask.setTo(255); + mask.at(outlier_min_pos.y, outlier_min_pos.x) = 0; + mask.at(outlier_max_pos.y, outlier_max_pos.x) = 0; + + double minV = 0, maxV = 0; + int minIdx[2] = {-1, -1}, maxIdx[2] = {-1, -1}; + cv::minMaxIdx(src, &minV, &maxV, minIdx, maxIdx, mask); + + EXPECT_NEAR(masked_min, minV, 1e-5 * std::max(1.0, std::fabs(masked_min))); + EXPECT_NEAR(masked_max, maxV, 1e-5 * std::max(1.0, std::fabs(masked_max))); + + if (depth != CV_Bool) + { + EXPECT_EQ(min_pos.y, minIdx[0]); + EXPECT_EQ(min_pos.x, minIdx[1]); + EXPECT_EQ(max_pos.y, maxIdx[0]); + EXPECT_EQ(max_pos.x, maxIdx[1]); + } +} + +INSTANTIATE_TEST_CASE_P( + MinMaxAcc, + MinMaxAccuracyNewTypes, + testing::Values(CV_16BF, CV_Bool, CV_64U, CV_64S, CV_32U) +); + +typedef testing::TestWithParam LUTAccuracyNewDstTypes; + +TEST_P(LUTAccuracyNewDstTypes, accuracy) +{ + const int dst_depth = GetParam(); + cv::Mat lut(1, 256, CV_MAKETYPE(dst_depth, 1)); + for (int i = 0; i < 256; ++i) + { + switch (dst_depth) + { + case CV_Bool: lut.at(0, i) = (uchar)(i & 1); break; + case CV_16BF: lut.at(0, i) = cv::bfloat((float)(i - 128) * 0.5f); break; + case CV_32U: lut.at(0, i) = (uint32_t)i * 16777619u; break; + case CV_64U: lut.at(0, i) = ((uint64_t)i << 56) | (uint64_t)i; break; + case CV_64S: lut.at(0, i) = ((int64_t)i - 128) * 1000000000LL; break; + default: FAIL() << "Unexpected depth " << dst_depth; + } + } + + cv::Mat src(16, 16, CV_8UC1); + for (int y = 0; y < 16; ++y) + for (int x = 0; x < 16; ++x) + src.at(y, x) = (uchar)(y * 16 + x); + + cv::Mat dst; + cv::LUT(src, lut, dst); + ASSERT_EQ(dst.size(), src.size()); + ASSERT_EQ(dst.type(), CV_MAKETYPE(dst_depth, 1)); + + for (int y = 0; y < 16; ++y) + { + for (int x = 0; x < 16; ++x) + { + int idx = src.at(y, x); + switch (dst_depth) + { + case CV_Bool: + EXPECT_EQ(lut.at(0, idx), dst.at(y, x)); break; + case CV_16BF: + EXPECT_EQ(lut.at(0, idx), dst.at(y, x)); break; + case CV_32U: + EXPECT_EQ(lut.at(0, idx), dst.at(y, x)); break; + case CV_64U: + EXPECT_EQ(lut.at(0, idx), dst.at(y, x)); break; + case CV_64S: + EXPECT_EQ(lut.at(0, idx), dst.at(y, x)); break; + default: FAIL() << "Unexpected depth " << dst_depth; + } + } + } +} + +INSTANTIATE_TEST_CASE_P( + LUTAcc, + LUTAccuracyNewDstTypes, + testing::Values(CV_16BF, CV_Bool, CV_64U, CV_64S, CV_32U) +); + CV_ENUM(LutIdxType, CV_8U, CV_8S, CV_16U, CV_16S) CV_ENUM(LutMatType, CV_8U, CV_8S, CV_16U, CV_16S, CV_32S, CV_32F, CV_64F, CV_16F, CV_16BF, CV_Bool, CV_64U, CV_64S, CV_32U) diff --git a/modules/ts/src/ts_func.cpp b/modules/ts/src/ts_func.cpp index ff1a761411..ece80c48a3 100644 --- a/modules/ts/src/ts_func.cpp +++ b/modules/ts/src/ts_func.cpp @@ -1223,6 +1223,10 @@ void minMaxLoc(const Mat& src, double* _minval, double* _maxval, minMaxLoc_((const unsigned*)sptr, total, startidx, &minval, &maxval, &minidx, &maxidx, mptr); break; + case CV_Bool: + minMaxLoc_((const uchar*)sptr, total, startidx, + &minval, &maxval, &minidx, &maxidx, mptr); + break; default: CV_Assert(0); }