mirror of
https://github.com/opencv/opencv.git
synced 2025-06-07 01:13:28 +08:00
Merge pull request #20733 from rogday:argmaxnd
Implement ArgMax and ArgMin * add reduceArgMax and reduceArgMin * fix review comments * address review concerns
This commit is contained in:
parent
a97f21ba4e
commit
f044037ec5
@ -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);
|
||||
|
||||
|
@ -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<template<typename> class Functor, typename... Args>
|
||||
static inline void depthDispatch(const int depth, Args&&... args)
|
||||
{
|
||||
switch (depth)
|
||||
{
|
||||
case CV_8U:
|
||||
Functor<uint8_t>{}(std::forward<Args>(args)...);
|
||||
break;
|
||||
case CV_8S:
|
||||
Functor<int8_t>{}(std::forward<Args>(args)...);
|
||||
break;
|
||||
case CV_16U:
|
||||
Functor<uint16_t>{}(std::forward<Args>(args)...);
|
||||
break;
|
||||
case CV_16S:
|
||||
Functor<int16_t>{}(std::forward<Args>(args)...);
|
||||
break;
|
||||
case CV_32S:
|
||||
Functor<int32_t>{}(std::forward<Args>(args)...);
|
||||
break;
|
||||
case CV_32F:
|
||||
Functor<float>{}(std::forward<Args>(args)...);
|
||||
break;
|
||||
case CV_64F:
|
||||
Functor<double>{}(std::forward<Args>(args)...);
|
||||
break;
|
||||
case CV_16F:
|
||||
default:
|
||||
CV_Error(cv::Error::BadDepth, "Unsupported matrix type.");
|
||||
};
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
//! @endcond
|
||||
|
||||
#endif //OPENCV_CORE_DETAIL_DISPATCH_HELPER_IMPL_HPP
|
@ -65,4 +65,33 @@ PERF_TEST_P(Size_MatType_ROp, reduceC,
|
||||
SANITY_CHECK(vec, 1);
|
||||
}
|
||||
|
||||
typedef tuple<Size, MatType, int> Size_MatType_RMode_t;
|
||||
typedef perf::TestBaseWithParam<Size_MatType_RMode_t> 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<int> 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
|
||||
|
@ -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 <algorithm>
|
||||
|
||||
#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 <typename T>
|
||||
struct reduceMinMaxImpl
|
||||
{
|
||||
void operator()(const cv::Mat& src, cv::Mat& dst, ReduceMode mode, const int axis) const
|
||||
{
|
||||
switch(mode)
|
||||
{
|
||||
case ReduceMode::FIRST_MIN:
|
||||
reduceMinMaxApply<std::less>(src, dst, axis);
|
||||
break;
|
||||
case ReduceMode::LAST_MIN:
|
||||
reduceMinMaxApply<std::less_equal>(src, dst, axis);
|
||||
break;
|
||||
case ReduceMode::FIRST_MAX:
|
||||
reduceMinMaxApply<std::greater>(src, dst, axis);
|
||||
break;
|
||||
case ReduceMode::LAST_MAX:
|
||||
reduceMinMaxApply<std::greater_equal>(src, dst, axis);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <template<class> class Cmp>
|
||||
static void reduceMinMaxApply(const cv::Mat& src, cv::Mat& dst, const int axis)
|
||||
{
|
||||
Cmp<T> cmp;
|
||||
|
||||
const auto *src_ptr = src.ptr<T>();
|
||||
auto *dst_ptr = dst.ptr<int32_t>();
|
||||
|
||||
const size_t outer_size = src.total(0, axis);
|
||||
const auto mid_size = static_cast<size_t>(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<int32_t>(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<int> 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<reduceMinMaxImpl>(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);
|
||||
}
|
||||
|
78
modules/core/test/ref_reduce_arg.impl.hpp
Normal file
78
modules/core/test/ref_reduce_arg.impl.hpp
Normal file
@ -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 <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
namespace cvtest {
|
||||
|
||||
template <class Cmp, typename T>
|
||||
struct reduceMinMaxImpl
|
||||
{
|
||||
void operator()(const cv::Mat& src, cv::Mat& dst, const int axis) const
|
||||
{
|
||||
Cmp cmp;
|
||||
std::vector<int> sizes(src.dims);
|
||||
std::copy(src.size.p, src.size.p + src.dims, sizes.begin());
|
||||
|
||||
std::vector<cv::Range> idx(sizes.size(), cv::Range(0, 1));
|
||||
idx[axis] = cv::Range::all();
|
||||
const int n = std::accumulate(begin(sizes), end(sizes), 1, std::multiplies<int>());
|
||||
const std::vector<int> newShape{1, src.size[axis]};
|
||||
for (int i = 0; i < n ; ++i)
|
||||
{
|
||||
cv::Mat sub = src(idx);
|
||||
|
||||
auto begin = sub.begin<T>();
|
||||
auto it = std::min_element(begin, sub.end<T>(), cmp);
|
||||
*dst(idx).ptr<int32_t>() = static_cast<int32_t>(std::distance(begin, it));
|
||||
|
||||
for (int j = static_cast<int>(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<template<class> class Cmp>
|
||||
struct MinMaxReducer{
|
||||
template <typename T>
|
||||
using Impl = reduceMinMaxImpl<Cmp<T>, 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<int> 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<Impl>(src.depth(), src, dst, axis);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //OPENCV_TEST_REF_REDUCE_ARG_HPP
|
@ -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<int>& 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<int>(axis % (2 * dims)) - dims; // [-dims; dims - 1]
|
||||
}
|
||||
void op(const vector<Mat>& 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<Mat>& src, Mat& dst, const Mat&) override
|
||||
{
|
||||
const Mat& inp = src[0];
|
||||
const int axis_ = getAxis(inp);
|
||||
|
||||
if (!isLast && !isMax)
|
||||
{
|
||||
cvtest::MinMaxReducer<std::less>::reduce(inp, dst, axis_);
|
||||
}
|
||||
else if (!isLast && isMax)
|
||||
{
|
||||
cvtest::MinMaxReducer<std::greater>::reduce(inp, dst, axis_);
|
||||
}
|
||||
else if (isLast && !isMax)
|
||||
{
|
||||
cvtest::MinMaxReducer<std::less_equal>::reduce(inp, dst, axis_);
|
||||
}
|
||||
else
|
||||
{
|
||||
cvtest::MinMaxReducer<std::greater_equal>::reduce(inp, dst, axis_);
|
||||
}
|
||||
}
|
||||
|
||||
bool isLast;
|
||||
bool isMax;
|
||||
uint32_t axis;
|
||||
};
|
||||
|
||||
|
||||
typedef Ptr<BaseElemWiseOp> ElemWiseOpPtr;
|
||||
class ElemWiseTest : public ::testing::TestWithParam<ElemWiseOpPtr> {};
|
||||
@ -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)));
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user