未验证 提交 f044037e 编写于 作者: R rogday 提交者: GitHub

Merge pull request #20733 from rogday:argmaxnd

Implement ArgMax and ArgMin

* add reduceArgMax and reduceArgMin

* fix review comments

* address review concerns
上级 a97f21ba
......@@ -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);
......
// 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);
}
// 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)));
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册