diff --git a/modules/core/include/opencv2/core.hpp b/modules/core/include/opencv2/core.hpp index 70ea4f8c1fa2f8bee806f1bf2ea2f15bed164f05..f7807e37ec07a6d0dc409569ef6e9075a572cfcb 100644 --- a/modules/core/include/opencv2/core.hpp +++ b/modules/core/include/opencv2/core.hpp @@ -1739,6 +1739,16 @@ should be done separately if needed. */ CV_EXPORTS_W void transpose(InputArray src, OutputArray dst); +/** @brief Transpose for n-dimensional matrices. + * + * @note Input should be continuous single-channel matrix. + * @param src input array. + * @param order a permutation of [0,1,..,N-1] where N is the number of axes of src. + * The i’th axis of dst will correspond to the axis numbered order[i] of the input. + * @param dst output array of the same type as src. + */ +CV_EXPORTS_W void transposeND(InputArray src, const std::vector& order, OutputArray dst); + /** @brief Performs the matrix transformation of every array element. The function cv::transform performs the matrix transformation of every diff --git a/modules/core/perf/perf_arithm.cpp b/modules/core/perf/perf_arithm.cpp index 70e2f49210e5d1fa532ae4ba206229a725f00b0e..3ac9a246397924462f6870f07dafcaee018e693d 100644 --- a/modules/core/perf/perf_arithm.cpp +++ b/modules/core/perf/perf_arithm.cpp @@ -1,4 +1,5 @@ #include "perf_precomp.hpp" +#include namespace opencv_test { @@ -393,6 +394,29 @@ PERF_TEST_P_(BinaryOpTest, reciprocal) SANITY_CHECK_NOTHING(); } + +PERF_TEST_P_(BinaryOpTest, transposeND) +{ + Size sz = get<0>(GetParam()); + int type = get<1>(GetParam()); + cv::Mat a = Mat(sz, type).reshape(1); + + std::vector order(a.dims); + std::iota(order.begin(), order.end(), 0); + std::reverse(order.begin(), order.end()); + + std::vector new_sz(a.dims); + std::copy(a.size.p, a.size.p + a.dims, new_sz.begin()); + std::reverse(new_sz.begin(), new_sz.end()); + cv::Mat b = Mat(new_sz, type); + + declare.in(a,WARMUP_RNG).out(b); + + TEST_CYCLE() cv::transposeND(a, order, b); + + SANITY_CHECK_NOTHING(); +} + INSTANTIATE_TEST_CASE_P(/*nothing*/ , BinaryOpTest, testing::Combine( testing::Values(szVGA, sz720p, sz1080p), diff --git a/modules/core/src/matrix_transform.cpp b/modules/core/src/matrix_transform.cpp index 727eaf7feef24d8730e8b6658c5edf5df451198f..05ecf450e12752dec23bafbe0cbc3e64c7b3a677 100644 --- a/modules/core/src/matrix_transform.cpp +++ b/modules/core/src/matrix_transform.cpp @@ -4,6 +4,7 @@ #include "precomp.hpp" #include "opencl_kernels_core.hpp" +#include "opencv2/core/detail/dispatch_helper.impl.hpp" namespace cv { @@ -282,6 +283,72 @@ void transpose( InputArray _src, OutputArray _dst ) } +void transposeND(InputArray src_, const std::vector& order, OutputArray dst_) +{ + Mat inp = src_.getMat(); + CV_Assert(inp.isContinuous()); + CV_CheckEQ(inp.channels(), 1, "Input array should be single-channel"); + CV_CheckEQ(order.size(), static_cast(inp.dims), "Number of dimensions shouldn't change"); + + auto order_ = order; + std::sort(order_.begin(), order_.end()); + for (size_t i = 0; i < order_.size(); ++i) + { + CV_CheckEQ(static_cast(order_[i]), i, "New order should be a valid permutation of the old one"); + } + + std::vector newShape(order.size()); + for (size_t i = 0; i < order.size(); ++i) + { + newShape[i] = inp.size[order[i]]; + } + + dst_.create(static_cast(newShape.size()), newShape.data(), inp.type()); + Mat out = dst_.getMat(); + CV_Assert(out.isContinuous()); + CV_Assert(inp.data != out.data); + + int continuous_idx = 0; + for (int i = static_cast(order.size()) - 1; i >= 0; --i) + { + if (order[i] != i) + { + continuous_idx = i + 1; + break; + } + } + + size_t continuous_size = continuous_idx == 0 ? out.total() : out.step1(continuous_idx - 1); + size_t outer_size = out.total() / continuous_size; + + std::vector steps(order.size()); + for (int i = 0; i < static_cast(steps.size()); ++i) + { + steps[i] = inp.step1(order[i]); + } + + auto* src = inp.ptr(); + auto* dst = out.ptr(); + + size_t src_offset = 0; + size_t es = out.elemSize(); + for (size_t i = 0; i < outer_size; ++i) + { + std::memcpy(dst, src + es * src_offset, es * continuous_size); + dst += es * continuous_size; + for (int j = continuous_idx - 1; j >= 0; --j) + { + src_offset += steps[j]; + if ((src_offset / steps[j]) % out.size[j] != 0) + { + break; + } + src_offset -= steps[j] * out.size[j]; + } + } +} + + #if CV_SIMD128 template CV_ALWAYS_INLINE void flipHoriz_single( const uchar* src, size_t sstep, uchar* dst, size_t dstep, Size size, size_t esz ) { diff --git a/modules/core/test/test_arithm.cpp b/modules/core/test/test_arithm.cpp index 014a0cff0a1c5d8c90426b54315581173e9fc7fc..06d295f6948fb1679778f8712debe941684dcc73 100644 --- a/modules/core/test/test_arithm.cpp +++ b/modules/core/test/test_arithm.cpp @@ -3,6 +3,7 @@ // of this distribution and at http://opencv.org/license.html. #include "test_precomp.hpp" #include "ref_reduce_arg.impl.hpp" +#include namespace opencv_test { namespace { @@ -2128,6 +2129,79 @@ TEST(Core_minMaxIdx, regression_9207_1) } +class TransposeND : public testing::TestWithParam< tuple, perf::MatType> > +{ +public: + std::vector m_shape; + int m_type; + + void SetUp() + { + std::tie(m_shape, m_type) = GetParam(); + } +}; + + +TEST_P(TransposeND, basic) +{ + Mat inp(m_shape, m_type); + randu(inp, 0, 255); + + std::vector order(m_shape.size()); + std::iota(order.begin(), order.end(), 0); + auto transposer = [&order] (const std::vector& id) + { + std::vector ret(id.size()); + for (size_t i = 0; i < id.size(); ++i) + { + ret[i] = id[order[i]]; + } + return ret; + }; + auto advancer = [&inp] (std::vector& id) + { + for (int j = static_cast(id.size() - 1); j >= 0; --j) + { + ++id[j]; + if (id[j] != inp.size[j]) + { + break; + } + id[j] = 0; + } + }; + + do + { + Mat out; + cv::transposeND(inp, order, out); + std::vector id(order.size()); + for (size_t i = 0; i < inp.total(); ++i) + { + auto new_id = transposer(id); + switch (inp.type()) + { + case CV_8UC1: + ASSERT_EQ(inp.at(id.data()), out.at(new_id.data())); + break; + case CV_32FC1: + ASSERT_EQ(inp.at(id.data()), out.at(new_id.data())); + break; + default: + FAIL() << "Unsupported type: " << inp.type(); + } + advancer(id); + } + } while (std::next_permutation(order.begin(), order.end())); +} + + +INSTANTIATE_TEST_CASE_P(Arithm, TransposeND, testing::Combine( + testing::Values(std::vector{2, 3, 4}, std::vector{5, 10}), + testing::Values(perf::MatType(CV_8UC1), CV_32FC1) +)); + + TEST(Core_minMaxIdx, regression_9207_2) { const int rows = 13;