diff --git a/modules/core/include/opencv2/core.hpp b/modules/core/include/opencv2/core.hpp index 48023844a984ddf366db546d8524c3f098f9f82e..70ea4f8c1fa2f8bee806f1bf2ea2f15bed164f05 100644 --- a/modules/core/include/opencv2/core.hpp +++ b/modules/core/include/opencv2/core.hpp @@ -819,12 +819,45 @@ mixChannels , or split . @param minLoc pointer to the returned minimum location (in 2D case); NULL is used if not required. @param maxLoc pointer to the returned maximum location (in 2D case); NULL is used if not required. @param mask optional mask used to select a sub-array. -@sa max, min, compare, inRange, extractImageCOI, mixChannels, split, Mat::reshape +@sa max, min, reduceArgMin, reduceArgMax, compare, inRange, extractImageCOI, mixChannels, split, Mat::reshape */ CV_EXPORTS_W void minMaxLoc(InputArray src, CV_OUT double* minVal, CV_OUT double* maxVal = 0, CV_OUT Point* minLoc = 0, CV_OUT Point* maxLoc = 0, InputArray mask = noArray()); +/** + * @brief Finds indices of min elements along provided axis + * + * @note + * - If input or output array is not continuous, this function will create an internal copy. + * - NaN handling is left unspecified, see patchNaNs(). + * - The returned index is always in bounds of input matrix. + * + * @param src input single-channel array. + * @param dst output array of type CV_32SC1 with the same dimensionality as src, + * except for axis being reduced - it should be set to 1. + * @param lastIndex whether to get the index of first or last occurrence of min. + * @param axis axis to reduce along. + * @sa reduceArgMax, minMaxLoc, min, max, compare, reduce + */ +CV_EXPORTS_W void reduceArgMin(InputArray src, OutputArray dst, int axis, bool lastIndex = false); + +/** + * @brief Finds indices of max elements along provided axis + * + * @note + * - If input or output array is not continuous, this function will create an internal copy. + * - NaN handling is left unspecified, see patchNaNs(). + * - The returned index is always in bounds of input matrix. + * + * @param src input single-channel array. + * @param dst output array of type CV_32SC1 with the same dimensionality as src, + * except for axis being reduced - it should be set to 1. + * @param lastIndex whether to get the index of first or last occurrence of max. + * @param axis axis to reduce along. + * @sa reduceArgMin, minMaxLoc, min, max, compare, reduce + */ +CV_EXPORTS_W void reduceArgMax(InputArray src, OutputArray dst, int axis, bool lastIndex = false); /** @brief Finds the global minimum and maximum in an array @@ -886,7 +919,7 @@ a single row. 1 means that the matrix is reduced to a single column. @param rtype reduction operation that could be one of #ReduceTypes @param dtype when negative, the output vector will have the same type as the input matrix, otherwise, its type will be CV_MAKE_TYPE(CV_MAT_DEPTH(dtype), src.channels()). -@sa repeat +@sa repeat, reduceArgMin, reduceArgMax */ CV_EXPORTS_W void reduce(InputArray src, OutputArray dst, int dim, int rtype, int dtype = -1); diff --git a/modules/core/include/opencv2/core/detail/dispatch_helper.impl.hpp b/modules/core/include/opencv2/core/detail/dispatch_helper.impl.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d6ec6769220031ccc2664d7bc96f5747023b97a6 --- /dev/null +++ b/modules/core/include/opencv2/core/detail/dispatch_helper.impl.hpp @@ -0,0 +1,49 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef OPENCV_CORE_DETAIL_DISPATCH_HELPER_IMPL_HPP +#define OPENCV_CORE_DETAIL_DISPATCH_HELPER_IMPL_HPP + +//! @cond IGNORED + +namespace cv { +namespace detail { + +template class Functor, typename... Args> +static inline void depthDispatch(const int depth, Args&&... args) +{ + switch (depth) + { + case CV_8U: + Functor{}(std::forward(args)...); + break; + case CV_8S: + Functor{}(std::forward(args)...); + break; + case CV_16U: + Functor{}(std::forward(args)...); + break; + case CV_16S: + Functor{}(std::forward(args)...); + break; + case CV_32S: + Functor{}(std::forward(args)...); + break; + case CV_32F: + Functor{}(std::forward(args)...); + break; + case CV_64F: + Functor{}(std::forward(args)...); + break; + case CV_16F: + default: + CV_Error(cv::Error::BadDepth, "Unsupported matrix type."); + }; +} + +}} + +//! @endcond + +#endif //OPENCV_CORE_DETAIL_DISPATCH_HELPER_IMPL_HPP diff --git a/modules/core/perf/perf_reduce.cpp b/modules/core/perf/perf_reduce.cpp index 4fc5aa55f995a9767b77407e3c989271bf0f33a1..8f9c2e8349f9fbd6f262f2643e6631656ef2b07f 100644 --- a/modules/core/perf/perf_reduce.cpp +++ b/modules/core/perf/perf_reduce.cpp @@ -65,4 +65,33 @@ PERF_TEST_P(Size_MatType_ROp, reduceC, SANITY_CHECK(vec, 1); } +typedef tuple Size_MatType_RMode_t; +typedef perf::TestBaseWithParam Size_MatType_RMode; + +PERF_TEST_P(Size_MatType_RMode, DISABLED_reduceArgMinMax, testing::Combine( + testing::Values(TYPICAL_MAT_SIZES), + testing::Values(CV_8U, CV_32F), + testing::Values(0, 1) +) +) +{ + Size srcSize = get<0>(GetParam()); + int matType = get<1>(GetParam()); + int axis = get<2>(GetParam()); + + Mat src(srcSize, matType); + + std::vector dstSize(src.dims); + std::copy(src.size.p, src.size.p + src.dims, dstSize.begin()); + dstSize[axis] = 1; + + Mat dst(dstSize, CV_32S, 0.); + + declare.in(src, WARMUP_RNG).out(dst); + + TEST_CYCLE() cv::reduceArgMin(src, dst, axis, true); + + SANITY_CHECK_NOTHING(); +} + } // namespace diff --git a/modules/core/src/minmax.cpp b/modules/core/src/minmax.cpp index 61bddc3d356725ccfc8b35ea99ccdc2656f22d46..96a7d027ada9898350949eb8fc62fb786b4841d2 100644 --- a/modules/core/src/minmax.cpp +++ b/modules/core/src/minmax.cpp @@ -7,6 +7,9 @@ #include "opencl_kernels_core.hpp" #include "opencv2/core/openvx/ovx_defs.hpp" #include "stat.hpp" +#include "opencv2/core/detail/dispatch_helper.impl.hpp" + +#include #undef HAVE_IPP #undef CV_IPP_RUN_FAST @@ -1570,3 +1573,118 @@ void cv::minMaxLoc( InputArray _img, double* minVal, double* maxVal, if( maxLoc ) std::swap(maxLoc->x, maxLoc->y); } + +enum class ReduceMode +{ + FIRST_MIN = 0, //!< get index of first min occurrence + LAST_MIN = 1, //!< get index of last min occurrence + FIRST_MAX = 2, //!< get index of first max occurrence + LAST_MAX = 3, //!< get index of last max occurrence +}; + +template +struct reduceMinMaxImpl +{ + void operator()(const cv::Mat& src, cv::Mat& dst, ReduceMode mode, const int axis) const + { + switch(mode) + { + case ReduceMode::FIRST_MIN: + reduceMinMaxApply(src, dst, axis); + break; + case ReduceMode::LAST_MIN: + reduceMinMaxApply(src, dst, axis); + break; + case ReduceMode::FIRST_MAX: + reduceMinMaxApply(src, dst, axis); + break; + case ReduceMode::LAST_MAX: + reduceMinMaxApply(src, dst, axis); + break; + } + } + + template class Cmp> + static void reduceMinMaxApply(const cv::Mat& src, cv::Mat& dst, const int axis) + { + Cmp cmp; + + const auto *src_ptr = src.ptr(); + auto *dst_ptr = dst.ptr(); + + const size_t outer_size = src.total(0, axis); + const auto mid_size = static_cast(src.size[axis]); + + const size_t outer_step = src.total(axis); + const size_t dst_step = dst.total(axis); + + const size_t mid_step = src.total(axis + 1); + + for (size_t outer = 0; outer < outer_size; ++outer) + { + const size_t outer_offset = outer * outer_step; + const size_t dst_offset = outer * dst_step; + for (size_t mid = 0; mid != mid_size; ++mid) + { + const size_t src_offset = outer_offset + mid * mid_step; + for (size_t inner = 0; inner < mid_step; inner++) + { + int32_t& index = dst_ptr[dst_offset + inner]; + + const size_t prev = outer_offset + index * mid_step + inner; + const size_t curr = src_offset + inner; + + if (cmp(src_ptr[curr], src_ptr[prev])) + { + index = static_cast(mid); + } + } + } + } + } +}; + +static void reduceMinMax(cv::InputArray src, cv::OutputArray dst, ReduceMode mode, int axis) +{ + CV_INSTRUMENT_REGION(); + + cv::Mat srcMat = src.getMat(); + axis = (axis + srcMat.dims) % srcMat.dims; + CV_Assert(srcMat.channels() == 1 && axis >= 0 && axis < srcMat.dims); + + std::vector sizes(srcMat.dims); + std::copy(srcMat.size.p, srcMat.size.p + srcMat.dims, sizes.begin()); + sizes[axis] = 1; + + dst.create(srcMat.dims, sizes.data(), CV_32SC1); // indices + cv::Mat dstMat = dst.getMat(); + dstMat.setTo(cv::Scalar::all(0)); + + if (!srcMat.isContinuous()) + { + srcMat = srcMat.clone(); + } + + bool needs_copy = !dstMat.isContinuous(); + if (needs_copy) + { + dstMat = dstMat.clone(); + } + + cv::detail::depthDispatch(srcMat.depth(), srcMat, dstMat, mode, axis); + + if (needs_copy) + { + dstMat.copyTo(dst); + } +} + +void cv::reduceArgMin(InputArray src, OutputArray dst, int axis, bool lastIndex) +{ + reduceMinMax(src, dst, lastIndex ? ReduceMode::LAST_MIN : ReduceMode::FIRST_MIN, axis); +} + +void cv::reduceArgMax(InputArray src, OutputArray dst, int axis, bool lastIndex) +{ + reduceMinMax(src, dst, lastIndex ? ReduceMode::LAST_MAX : ReduceMode::FIRST_MAX, axis); +} diff --git a/modules/core/test/ref_reduce_arg.impl.hpp b/modules/core/test/ref_reduce_arg.impl.hpp new file mode 100644 index 0000000000000000000000000000000000000000..22b7c2b7a650b5cbeffa62d742505e9d0ff90e29 --- /dev/null +++ b/modules/core/test/ref_reduce_arg.impl.hpp @@ -0,0 +1,78 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef OPENCV_TEST_REF_REDUCE_ARG_HPP +#define OPENCV_TEST_REF_REDUCE_ARG_HPP + +#include "opencv2/core/detail/dispatch_helper.impl.hpp" + +#include +#include + +namespace cvtest { + +template +struct reduceMinMaxImpl +{ + void operator()(const cv::Mat& src, cv::Mat& dst, const int axis) const + { + Cmp cmp; + std::vector sizes(src.dims); + std::copy(src.size.p, src.size.p + src.dims, sizes.begin()); + + std::vector idx(sizes.size(), cv::Range(0, 1)); + idx[axis] = cv::Range::all(); + const int n = std::accumulate(begin(sizes), end(sizes), 1, std::multiplies()); + const std::vector newShape{1, src.size[axis]}; + for (int i = 0; i < n ; ++i) + { + cv::Mat sub = src(idx); + + auto begin = sub.begin(); + auto it = std::min_element(begin, sub.end(), cmp); + *dst(idx).ptr() = static_cast(std::distance(begin, it)); + + for (int j = static_cast(idx.size()) - 1; j >= 0; --j) + { + if (j == axis) + { + continue; + } + const int old_s = idx[j].start; + const int new_s = (old_s + 1) % sizes[j]; + if (new_s > old_s) + { + idx[j] = cv::Range(new_s, new_s + 1); + break; + } + idx[j] = cv::Range(0, 1); + } + } + } +}; + +template class Cmp> +struct MinMaxReducer{ + template + using Impl = reduceMinMaxImpl, T>; + + static void reduce(const Mat& src, Mat& dst, int axis) + { + axis = (axis + src.dims) % src.dims; + CV_Assert(src.channels() == 1 && axis >= 0 && axis < src.dims); + + std::vector sizes(src.dims); + std::copy(src.size.p, src.size.p + src.dims, sizes.begin()); + sizes[axis] = 1; + + dst.create(sizes, CV_32SC1); // indices + dst.setTo(cv::Scalar::all(0)); + + cv::detail::depthDispatch(src.depth(), src, dst, axis); + } +}; + +} + +#endif //OPENCV_TEST_REF_REDUCE_ARG_HPP diff --git a/modules/core/test/test_arithm.cpp b/modules/core/test/test_arithm.cpp index 9e8e242d604a37c3378a65cfe723ab52cc6d4b4b..4a9522d3d1539bf95ad0772ce6544e4bcd0cdffe 100644 --- a/modules/core/test/test_arithm.cpp +++ b/modules/core/test/test_arithm.cpp @@ -2,6 +2,7 @@ // It is subject to the license terms in the LICENSE file found in the top-level directory // of this distribution and at http://opencv.org/license.html. #include "test_precomp.hpp" +#include "ref_reduce_arg.impl.hpp" namespace opencv_test { namespace { @@ -1387,6 +1388,73 @@ struct MinMaxLocOp : public BaseElemWiseOp } }; +struct reduceArgMinMaxOp : public BaseElemWiseOp +{ + reduceArgMinMaxOp() : BaseElemWiseOp(1, FIX_ALPHA+FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)) + { + context = ARITHM_MAX_NDIMS*2 + 2; + }; + int getRandomType(RNG& rng) override + { + return cvtest::randomType(rng, _OutputArray::DEPTH_MASK_ALL_BUT_8S, 1, 1); + } + void getRandomSize(RNG& rng, vector& size) override + { + cvtest::randomSize(rng, 2, ARITHM_MAX_NDIMS, 6, size); + } + void generateScalars(int depth, RNG& rng) override + { + BaseElemWiseOp::generateScalars(depth, rng); + isLast = (randInt(rng) % 2 == 0); + isMax = (randInt(rng) % 2 == 0); + axis = randInt(rng); + } + int getAxis(const Mat& src) const + { + int dims = src.dims; + return static_cast(axis % (2 * dims)) - dims; // [-dims; dims - 1] + } + void op(const vector& src, Mat& dst, const Mat&) override + { + const Mat& inp = src[0]; + const int axis_ = getAxis(inp); + if (isMax) + { + cv::reduceArgMax(inp, dst, axis_, isLast); + } + else + { + cv::reduceArgMin(inp, dst, axis_, isLast); + } + } + void refop(const vector& src, Mat& dst, const Mat&) override + { + const Mat& inp = src[0]; + const int axis_ = getAxis(inp); + + if (!isLast && !isMax) + { + cvtest::MinMaxReducer::reduce(inp, dst, axis_); + } + else if (!isLast && isMax) + { + cvtest::MinMaxReducer::reduce(inp, dst, axis_); + } + else if (isLast && !isMax) + { + cvtest::MinMaxReducer::reduce(inp, dst, axis_); + } + else + { + cvtest::MinMaxReducer::reduce(inp, dst, axis_); + } + } + + bool isLast; + bool isMax; + uint32_t axis; +}; + typedef Ptr ElemWiseOpPtr; class ElemWiseTest : public ::testing::TestWithParam {}; @@ -1492,6 +1560,7 @@ INSTANTIATE_TEST_CASE_P(Core_MeanStdDev, ElemWiseTest, ::testing::Values(ElemWis INSTANTIATE_TEST_CASE_P(Core_Sum, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new SumOp))); INSTANTIATE_TEST_CASE_P(Core_Norm, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new NormOp))); INSTANTIATE_TEST_CASE_P(Core_MinMaxLoc, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new MinMaxLocOp))); +INSTANTIATE_TEST_CASE_P(Core_reduceArgMinMax, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new reduceArgMinMaxOp))); INSTANTIATE_TEST_CASE_P(Core_CartToPolarToCart, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new CartToPolarToCartOp)));