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

Merge pull request #29008 from ManfredShao:gapi/fluid-inrange-simd

gapi/fluid: add Universal Intrinsics SIMD for InRange kernel
This commit is contained in:
Alexander Smorkalov
2026-05-14 15:41:03 +03:00
committed by GitHub
5 changed files with 295 additions and 44 deletions
@@ -237,7 +237,9 @@ INSTANTIATE_TEST_CASE_P(ThresholdPerfTestFluid, ThresholdOTPerfTest,
INSTANTIATE_TEST_CASE_P(InRangePerfTestFluid, InRangePerfTest,
Combine(Values(AbsExact().to_compare_f()),
Values(szSmall128, szVGA, sz720p, sz1080p),
Values(CV_8UC1),
Values(CV_8UC1, CV_8UC2, CV_8UC3, CV_8UC4,
CV_16UC1, CV_16UC3,
CV_16SC1, CV_16SC3),
Values(cv::compile_args(CORE_FLUID))));
INSTANTIATE_TEST_CASE_P(Split3PerfTestFluid, Split3PerfTest,
+31 -43
View File
@@ -2084,32 +2084,26 @@ GAPI_FLUID_KERNEL(GFluidThreshold, cv::gapi::core::GThreshold, false)
//
//------------------------
static void run_inrange3(uchar out[], const uchar in[], int width,
const uchar lower[], const uchar upper[])
#if (CV_SIMD || CV_SIMD_SCALABLE)
template<typename SRC>
static CV_ALWAYS_INLINE
typename std::enable_if<std::is_integral<SRC>::value, int>::type
call_inrange_simd(const SRC in[], const SRC lower[], const SRC upper[],
uchar out[], int width, int chan)
{
int w = 0; // cycle index
#if CV_SIMD128
for (; w <= width-16; w+=16)
{
v_uint8x16 i0, i1, i2;
v_load_deinterleave(&in[3*w], i0, i1, i2);
v_uint8x16 o;
o = v_and(v_and(v_and(v_and(v_and(v_ge(i0, v_setall_u8(lower[0])), v_le(i0, v_setall_u8(upper[0]))), v_ge(i1, v_setall_u8(lower[1]))), v_le(i1, v_setall_u8(upper[1]))), v_ge(i2, v_setall_u8(lower[2]))), v_le(i2, v_setall_u8(upper[2])));
v_store(&out[w], o);
}
#endif
for (; w < width; w++)
{
out[w] = in[3*w ] >= lower[0] && in[3*w ] <= upper[0] &&
in[3*w+1] >= lower[1] && in[3*w+1] <= upper[1] &&
in[3*w+2] >= lower[2] && in[3*w+2] <= upper[2] ? 255: 0;
}
return inrange_simd(in, lower, upper, out, width, chan);
}
template<typename SRC>
static CV_ALWAYS_INLINE
typename std::enable_if<!std::is_integral<SRC>::value, int>::type
call_inrange_simd(const SRC[], const SRC[], const SRC[],
uchar[], int, int)
{
return 0; // float: no SIMD, scalar fallback handles everything
}
#endif
template<typename DST, typename SRC>
static void run_inrange(Buffer &dst, const View &src, const cv::Scalar &upperb,
const cv::Scalar &lowerb)
@@ -2123,7 +2117,7 @@ static void run_inrange(Buffer &dst, const View &src, const cv::Scalar &upperb,
int chan = src.meta().chan;
GAPI_Assert(dst.meta().chan == 1);
SRC lower[4], upper[4];
SRC lower[4] = {}, upper[4] = {};
for (int c=0; c < chan; c++)
{
if (std::is_integral<SRC>::value)
@@ -2145,40 +2139,34 @@ static void run_inrange(Buffer &dst, const View &src, const cv::Scalar &upperb,
}
}
// manually SIMD for important case if RGB/BGR
if (std::is_same<SRC,uchar>::value && chan==3)
{
run_inrange3((uchar*)out, (const uchar*)in, width,
(const uchar*)lower, (const uchar*)upper);
return;
}
// TODO: please manually SIMD if multiple channels:
// modern compilers would perfectly vectorize this code if one channel,
// but may need help with de-interleaving channels if RGB/BGR image etc
// SIMD path for integral types (uchar, ushort, short); float falls through
int w = 0;
#if (CV_SIMD || CV_SIMD_SCALABLE)
w = call_inrange_simd(in, lower, upper, out, width, chan);
#endif
switch (chan)
{
case 1:
for (int w=0; w < width; w++)
out[w] = in[w] >= lower[0] && in[w] <= upper[0]? 255: 0;
for (; w < width; w++)
out[w] = in[w] >= lower[0] && in[w] <= upper[0] ? 255 : 0;
break;
case 2:
for (int w=0; w < width; w++)
for (; w < width; w++)
out[w] = in[2*w ] >= lower[0] && in[2*w ] <= upper[0] &&
in[2*w+1] >= lower[1] && in[2*w+1] <= upper[1] ? 255: 0;
in[2*w+1] >= lower[1] && in[2*w+1] <= upper[1] ? 255 : 0;
break;
case 3:
for (int w=0; w < width; w++)
for (; w < width; w++)
out[w] = in[3*w ] >= lower[0] && in[3*w ] <= upper[0] &&
in[3*w+1] >= lower[1] && in[3*w+1] <= upper[1] &&
in[3*w+2] >= lower[2] && in[3*w+2] <= upper[2] ? 255: 0;
in[3*w+2] >= lower[2] && in[3*w+2] <= upper[2] ? 255 : 0;
break;
case 4:
for (int w=0; w < width; w++)
for (; w < width; w++)
out[w] = in[4*w ] >= lower[0] && in[4*w ] <= upper[0] &&
in[4*w+1] >= lower[1] && in[4*w+1] <= upper[1] &&
in[4*w+2] >= lower[2] && in[4*w+2] <= upper[2] &&
in[4*w+3] >= lower[3] && in[4*w+3] <= upper[3] ? 255: 0;
in[4*w+3] >= lower[3] && in[4*w+3] <= upper[3] ? 255 : 0;
break;
default: CV_Error(cv::Error::StsBadArg, "unsupported number of channels");
}
@@ -400,6 +400,26 @@ CONVERTTO_SCALED_SIMD(float, float)
#undef CONVERTTO_SCALED_SIMD
//-------------------------
//
// Fluid kernels: InRange
//
//-------------------------
#define INRANGE_SIMD(SRC) \
int inrange_simd(const SRC in[], const SRC lower[], const SRC upper[], \
uchar out[], const int length, const int chan) \
{ \
CV_CPU_DISPATCH(inrange_simd, (in, lower, upper, out, length, chan), \
CV_CPU_DISPATCH_MODES_ALL); \
}
INRANGE_SIMD(uchar)
INRANGE_SIMD(ushort)
INRANGE_SIMD(short)
#undef INRANGE_SIMD
} // namespace fluid
} // namespace gapi
} // namespace cv
@@ -315,6 +315,22 @@ CONVERTTO_SCALED_SIMD(float, float)
#undef CONVERTTO_SCALED_SIMD
//-------------------------
//
// Fluid kernels: InRange
//
//-------------------------
#define INRANGE_SIMD(SRC) \
int inrange_simd(const SRC in[], const SRC lower[], const SRC upper[], \
uchar out[], const int length, const int chan);
INRANGE_SIMD(uchar)
INRANGE_SIMD(ushort)
INRANGE_SIMD(short)
#undef INRANGE_SIMD
} // namespace fluid
} // namespace gapi
} // namespace cv
@@ -322,6 +322,16 @@ int split3_simd(const uchar in[], uchar out1[], uchar out2[],
int split4_simd(const uchar in[], uchar out1[], uchar out2[],
uchar out3[], uchar out4[], const int width);
#define INRANGE_SIMD(SRC) \
int inrange_simd(const SRC in[], const SRC lower[], const SRC upper[], \
uchar out[], const int length, const int chan);
INRANGE_SIMD(uchar)
INRANGE_SIMD(ushort)
INRANGE_SIMD(short)
#undef INRANGE_SIMD
#define MERGE3_SIMD(T) \
int merge3_simd(const T in1[], const T in2[], const T in3[], \
T out[], const int width);
@@ -3154,6 +3164,221 @@ CONVERTTO_SCALED_SIMD(float, float)
#undef CONVERTTO_SCALED_SIMD
//-------------------------
//
// Fluid kernels: InRange
//
//-------------------------
CV_ALWAYS_INLINE int inrange_simd_impl(const uchar in[],
const uchar lower[], const uchar upper[],
uchar out[], const int length, const int chan)
{
const int nlanes = VTraits<v_uint8>::vlanes();
if (length < nlanes)
return 0;
// broadcast scalar bounds into vectors
v_uint8 vlo0 = vx_setall_u8(lower[0]), vhi0 = vx_setall_u8(upper[0]);
v_uint8 vlo1 = vx_setzero_u8(), vhi1 = vx_setzero_u8();
v_uint8 vlo2 = vx_setzero_u8(), vhi2 = vx_setzero_u8();
v_uint8 vlo3 = vx_setzero_u8(), vhi3 = vx_setzero_u8();
if (chan >= 2) { vlo1 = vx_setall_u8(lower[1]); vhi1 = vx_setall_u8(upper[1]); }
if (chan >= 3) { vlo2 = vx_setall_u8(lower[2]); vhi2 = vx_setall_u8(upper[2]); }
if (chan == 4) { vlo3 = vx_setall_u8(lower[3]); vhi3 = vx_setall_u8(upper[3]); }
int x = 0; // pixel index
for (;;)
{
for (; x <= length - nlanes; x += nlanes)
{
v_uint8 mask;
if (chan == 1)
{
v_uint8 a = vx_load(&in[x]);
mask = v_and(v_ge(a, vlo0), v_le(a, vhi0));
}
else if (chan == 2)
{
v_uint8 a, b;
v_load_deinterleave(&in[2 * x], a, b);
mask = v_and(v_and(v_ge(a, vlo0), v_le(a, vhi0)),
v_and(v_ge(b, vlo1), v_le(b, vhi1)));
}
else if (chan == 3)
{
v_uint8 a, b, c;
v_load_deinterleave(&in[3 * x], a, b, c);
mask = v_and(v_and(v_and(v_ge(a, vlo0), v_le(a, vhi0)),
v_and(v_ge(b, vlo1), v_le(b, vhi1))),
v_and(v_ge(c, vlo2), v_le(c, vhi2)));
}
else // chan == 4
{
v_uint8 a, b, c, d;
v_load_deinterleave(&in[4 * x], a, b, c, d);
mask = v_and(v_and(v_and(v_ge(a, vlo0), v_le(a, vhi0)),
v_and(v_ge(b, vlo1), v_le(b, vhi1))),
v_and(v_and(v_ge(c, vlo2), v_le(c, vhi2)),
v_and(v_ge(d, vlo3), v_le(d, vhi3))));
}
vx_store(&out[x], mask);
}
if (x < length) { x = length - nlanes; continue; }
break;
}
return x;
}
CV_ALWAYS_INLINE int inrange_simd_impl(const ushort in[],
const ushort lower[], const ushort upper[],
uchar out[], const int length, const int chan)
{
const int nlanes = VTraits<v_uint8>::vlanes(); // output pixels per iter
const int nlanes16 = VTraits<v_uint16>::vlanes(); // = nlanes / 2
if (length < nlanes)
return 0;
v_uint16 vlo0 = vx_setall_u16(lower[0]), vhi0 = vx_setall_u16(upper[0]);
v_uint16 vlo1 = vx_setzero_u16(), vhi1 = vx_setzero_u16();
v_uint16 vlo2 = vx_setzero_u16(), vhi2 = vx_setzero_u16();
v_uint16 vlo3 = vx_setzero_u16(), vhi3 = vx_setzero_u16();
if (chan >= 2) { vlo1 = vx_setall_u16(lower[1]); vhi1 = vx_setall_u16(upper[1]); }
if (chan >= 3) { vlo2 = vx_setall_u16(lower[2]); vhi2 = vx_setall_u16(upper[2]); }
if (chan == 4) { vlo3 = vx_setall_u16(lower[3]); vhi3 = vx_setall_u16(upper[3]); }
// Helper lambda: compute mask for nlanes16 pixels starting at in[base*chan]
auto cmp16 = [&](int base) -> v_uint16
{
if (chan == 1)
{
v_uint16 a = vx_load(&in[base]);
return v_and(v_ge(a, vlo0), v_le(a, vhi0));
}
else if (chan == 2)
{
v_uint16 a, b;
v_load_deinterleave(&in[2 * base], a, b);
return v_and(v_and(v_ge(a, vlo0), v_le(a, vhi0)),
v_and(v_ge(b, vlo1), v_le(b, vhi1)));
}
else if (chan == 3)
{
v_uint16 a, b, c;
v_load_deinterleave(&in[3 * base], a, b, c);
return v_and(v_and(v_and(v_ge(a, vlo0), v_le(a, vhi0)),
v_and(v_ge(b, vlo1), v_le(b, vhi1))),
v_and(v_ge(c, vlo2), v_le(c, vhi2)));
}
else // chan == 4
{
v_uint16 a, b, c, d;
v_load_deinterleave(&in[4 * base], a, b, c, d);
return v_and(v_and(v_and(v_ge(a, vlo0), v_le(a, vhi0)),
v_and(v_ge(b, vlo1), v_le(b, vhi1))),
v_and(v_and(v_ge(c, vlo2), v_le(c, vhi2)),
v_and(v_ge(d, vlo3), v_le(d, vhi3))));
}
};
int x = 0;
for (;;)
{
for (; x <= length - nlanes; x += nlanes)
{
v_uint16 m1 = cmp16(x);
v_uint16 m2 = cmp16(x + nlanes16);
pack_store_uchar(&out[x], m1, m2);
}
if (x < length) { x = length - nlanes; continue; }
break;
}
return x;
}
CV_ALWAYS_INLINE int inrange_simd_impl(const short in[],
const short lower[], const short upper[],
uchar out[], const int length, const int chan)
{
const int nlanes = VTraits<v_uint8>::vlanes();
const int nlanes16 = VTraits<v_int16>::vlanes();
if (length < nlanes)
return 0;
v_int16 vlo0 = vx_setall_s16(lower[0]), vhi0 = vx_setall_s16(upper[0]);
v_int16 vlo1 = vx_setzero_s16(), vhi1 = vx_setzero_s16();
v_int16 vlo2 = vx_setzero_s16(), vhi2 = vx_setzero_s16();
v_int16 vlo3 = vx_setzero_s16(), vhi3 = vx_setzero_s16();
if (chan >= 2) { vlo1 = vx_setall_s16(lower[1]); vhi1 = vx_setall_s16(upper[1]); }
if (chan >= 3) { vlo2 = vx_setall_s16(lower[2]); vhi2 = vx_setall_s16(upper[2]); }
if (chan == 4) { vlo3 = vx_setall_s16(lower[3]); vhi3 = vx_setall_s16(upper[3]); }
auto cmp16 = [&](int base) -> v_int16
{
if (chan == 1)
{
v_int16 a = vx_load(&in[base]);
return v_and(v_ge(a, vlo0), v_le(a, vhi0));
}
else if (chan == 2)
{
v_int16 a, b;
v_load_deinterleave(&in[2 * base], a, b);
return v_and(v_and(v_ge(a, vlo0), v_le(a, vhi0)),
v_and(v_ge(b, vlo1), v_le(b, vhi1)));
}
else if (chan == 3)
{
v_int16 a, b, c;
v_load_deinterleave(&in[3 * base], a, b, c);
return v_and(v_and(v_and(v_ge(a, vlo0), v_le(a, vhi0)),
v_and(v_ge(b, vlo1), v_le(b, vhi1))),
v_and(v_ge(c, vlo2), v_le(c, vhi2)));
}
else // chan == 4
{
v_int16 a, b, c, d;
v_load_deinterleave(&in[4 * base], a, b, c, d);
return v_and(v_and(v_and(v_ge(a, vlo0), v_le(a, vhi0)),
v_and(v_ge(b, vlo1), v_le(b, vhi1))),
v_and(v_and(v_ge(c, vlo2), v_le(c, vhi2)),
v_and(v_ge(d, vlo3), v_le(d, vhi3))));
}
};
int x = 0;
for (;;)
{
for (; x <= length - nlanes; x += nlanes)
{
v_int16 m1 = cmp16(x);
v_int16 m2 = cmp16(x + nlanes16);
v_uint16 um1 = v_reinterpret_as_u16(m1);
v_uint16 um2 = v_reinterpret_as_u16(m2);
pack_store_uchar(&out[x], um1, um2);
}
if (x < length) { x = length - nlanes; continue; }
break;
}
return x;
}
#define INRANGE_SIMD(SRC) \
int inrange_simd(const SRC in[], const SRC lower[], const SRC upper[], \
uchar out[], const int length, const int chan) \
{ \
return inrange_simd_impl(in, lower, upper, out, length, chan); \
}
INRANGE_SIMD(uchar)
INRANGE_SIMD(ushort)
INRANGE_SIMD(short)
#undef INRANGE_SIMD
#endif // CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY
CV_CPU_OPTIMIZATION_NAMESPACE_END