mirror of
https://github.com/opencv/opencv.git
synced 2026-07-29 07:13:02 +04:00
Merge pull request #28964 from abhishek-gola:extend_primitive_core_ops
Extended primitive core operations to support new types #28964 The support was already there, this PR tests them on edge cases and patch the fix. closes: https://github.com/opencv/opencv/issues/24580 ### 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
This commit is contained in:
@@ -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
|
||||
{
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -3585,6 +3585,83 @@ INSTANTIATE_TEST_CASE_P(
|
||||
testing::Values(CV_16BF, CV_Bool, CV_64U, CV_64S, CV_32U)
|
||||
);
|
||||
|
||||
typedef testing::TestWithParam<perf::MatDepth> 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<Point> expected_pts;
|
||||
const int total = sz.area();
|
||||
const int approx_nz = std::max(1, total / 17);
|
||||
std::vector<uchar> 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<uchar>(y, x) = 1; break;
|
||||
case CV_16BF: src.at<uint16_t>(y, x) = 0x3F80; /* bf16(1.0f) */ break;
|
||||
case CV_32U: src.at<uint32_t>(y, x) = 7u; break;
|
||||
case CV_64U: src.at<uint64_t>(y, x) = 7ULL; break;
|
||||
case CV_64S: src.at<int64_t>(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<Point> 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<Point> 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<perf::MatDepth> 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<perf::MatDepth> 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<uint32_t>::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<uchar>(p.y, p.x) = (uchar)v; break;
|
||||
case CV_16BF: src.at<cv::bfloat>(p.y, p.x) = cv::bfloat((float)v); break;
|
||||
case CV_32U: src.at<uint32_t>(p.y, p.x) = (uint32_t)v; break;
|
||||
case CV_64U: src.at<uint64_t>(p.y, p.x) = (uint64_t)v; break;
|
||||
case CV_64S: src.at<int64_t>(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<uint32_t>::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<uchar>(p.y, p.x) = (uchar)v; break;
|
||||
case CV_16BF: src.at<cv::bfloat>(p.y, p.x) = cv::bfloat((float)v); break;
|
||||
case CV_32U: src.at<uint32_t>(p.y, p.x) = (uint32_t)v; break;
|
||||
case CV_64U: src.at<uint64_t>(p.y, p.x) = (uint64_t)v; break;
|
||||
case CV_64S: src.at<int64_t>(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<uchar>(outlier_min_pos.y, outlier_min_pos.x) = 0;
|
||||
mask.at<uchar>(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<perf::MatDepth> 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<uchar>(0, i) = (uchar)(i & 1); break;
|
||||
case CV_16BF: lut.at<cv::bfloat>(0, i) = cv::bfloat((float)(i - 128) * 0.5f); break;
|
||||
case CV_32U: lut.at<uint32_t>(0, i) = (uint32_t)i * 16777619u; break;
|
||||
case CV_64U: lut.at<uint64_t>(0, i) = ((uint64_t)i << 56) | (uint64_t)i; break;
|
||||
case CV_64S: lut.at<int64_t>(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<uchar>(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<uchar>(y, x);
|
||||
switch (dst_depth)
|
||||
{
|
||||
case CV_Bool:
|
||||
EXPECT_EQ(lut.at<uchar>(0, idx), dst.at<uchar>(y, x)); break;
|
||||
case CV_16BF:
|
||||
EXPECT_EQ(lut.at<uint16_t>(0, idx), dst.at<uint16_t>(y, x)); break;
|
||||
case CV_32U:
|
||||
EXPECT_EQ(lut.at<uint32_t>(0, idx), dst.at<uint32_t>(y, x)); break;
|
||||
case CV_64U:
|
||||
EXPECT_EQ(lut.at<uint64_t>(0, idx), dst.at<uint64_t>(y, x)); break;
|
||||
case CV_64S:
|
||||
EXPECT_EQ(lut.at<int64_t>(0, idx), dst.at<int64_t>(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)
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user