From 1bf1e70c0f6a732018fdcb8a11efbd44ae00b45e Mon Sep 17 00:00:00 2001 From: Xi Guo Date: Fri, 17 Jul 2026 20:55:34 +0800 Subject: [PATCH] core: optimize 8UC2 channel extraction --- modules/core/perf/perf_split.cpp | 24 +++++++++++++++++ modules/core/src/channels.cpp | 38 +++++++++++++++++++++++++++ modules/core/test/test_operations.cpp | 27 +++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/modules/core/perf/perf_split.cpp b/modules/core/perf/perf_split.cpp index 2cbc0b289c..a4cd564d7a 100644 --- a/modules/core/perf/perf_split.cpp +++ b/modules/core/perf/perf_split.cpp @@ -30,4 +30,28 @@ PERF_TEST_P( Size_Depth_Channels, split, SANITY_CHECK(mv, 2e-5); } +typedef tuple Size_Channel_t; +typedef perf::TestBaseWithParam Size_Channel; + +PERF_TEST_P( Size_Channel, extractChannel, + testing::Combine + ( + testing::Values(TYPICAL_MAT_SIZES), + testing::Values(0, 1) + ) + ) +{ + Size sz = get<0>(GetParam()); + int channel = get<1>(GetParam()); + + Mat src(sz, CV_8UC2); + Mat dst(sz, CV_8UC1); + randu(src, 0, 255); + + int runs = (sz.width <= 640) ? 8 : 1; + TEST_CYCLE_MULTIRUN(runs) extractChannel(src, dst, channel); + + SANITY_CHECK(dst); +} + } // namespace diff --git a/modules/core/src/channels.cpp b/modules/core/src/channels.cpp index 3ee7088a4f..be6f7423a3 100644 --- a/modules/core/src/channels.cpp +++ b/modules/core/src/channels.cpp @@ -418,6 +418,35 @@ static bool ipp_insertChannel(const Mat &src, Mat &dst, int channel) } #endif +namespace cv +{ +template static void extractChannel8uC2_(const Mat& src, Mat& dst) +{ + for (int row = 0; row < src.rows; ++row) + { + const uchar* source = src.ptr(row); + uchar* destination = dst.ptr(row); + int column = 0; + +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int lanes = VTraits::vlanes(); + for (; column <= src.cols - lanes; column += lanes) + { + v_uint8 channel0, channel1; + v_load_deinterleave(source + column * 2, channel0, channel1); + v_store(destination + column, channel == 0 ? channel0 : channel1); + } +#endif + for (; column < src.cols; ++column) + destination[column] = source[column * 2 + channel]; + } + +#if (CV_SIMD || CV_SIMD_SCALABLE) + vx_cleanup(); +#endif +} +} + void cv::extractChannel(InputArray _src, OutputArray _dst, int coi) { CV_INSTRUMENT_REGION(); @@ -443,6 +472,15 @@ void cv::extractChannel(InputArray _src, OutputArray _dst, int coi) CV_IPP_RUN_FAST(ipp_extractChannel(src, dst, coi)) + if (src.dims <= 2 && src.type() == CV_8UC2) + { + if (coi == 0) + extractChannel8uC2_<0>(src, dst); + else + extractChannel8uC2_<1>(src, dst); + return; + } + mixChannels(&src, 1, &dst, 1, ch, 1); } diff --git a/modules/core/test/test_operations.cpp b/modules/core/test/test_operations.cpp index 335623194c..57fa39adbe 100644 --- a/modules/core/test/test_operations.cpp +++ b/modules/core/test/test_operations.cpp @@ -1602,6 +1602,33 @@ TEST(Core_Arithm, scalar_handling_19599) // https://github.com/opencv/opencv/is EXPECT_EQ(1, c.rows); } +TEST(Core_ExtractChannel, twoChannel8uSubmatrix) +{ + Mat sourceStorage(5, 37, CV_8UC2); + randu(sourceStorage, 0, 256); + Mat source = sourceStorage(Rect(3, 1, 31, 3)); + ASSERT_FALSE(source.isContinuous()); + + for (int channel = 0; channel < 2; ++channel) + { + Mat destinationStorage(5, 37, CV_8UC1, Scalar::all(0)); + Mat destination = destinationStorage(Rect(3, 1, source.cols, source.rows)); + Mat expected(source.size(), CV_8UC1); + ASSERT_FALSE(destination.isContinuous()); + + for (int row = 0; row < source.rows; ++row) + { + const uchar* sourceRow = source.ptr(row); + uchar* expectedRow = expected.ptr(row); + for (int column = 0; column < source.cols; ++column) + expectedRow[column] = sourceRow[column * 2 + channel]; + } + + extractChannel(source, destination, channel); + EXPECT_EQ(0, cvtest::norm(expected, destination, NORM_INF)); + } +} + // https://github.com/opencv/opencv/issues/24163 typedef tuple Arith_Regression24163Param; typedef testing::TestWithParam Core_Arith_Regression24163;