From 724001aa0f646aa58913c5e46917d104334275ed Mon Sep 17 00:00:00 2001 From: Ruslan Garnov Date: Tue, 3 Nov 2020 18:50:49 +0300 Subject: [PATCH] Added multidimensional RMat::View steps --- modules/gapi/include/opencv2/gapi/rmat.hpp | 27 ++-- modules/gapi/src/api/rmat.cpp | 70 ++++++++-- modules/gapi/src/backends/common/gbackend.hpp | 16 ++- modules/gapi/test/rmat/rmat_test_common.hpp | 16 ++- modules/gapi/test/rmat/rmat_view_tests.cpp | 130 ++++++++++++++++-- modules/gapi/test/s11n/gapi_s11n_tests.cpp | 9 +- 6 files changed, 230 insertions(+), 38 deletions(-) diff --git a/modules/gapi/include/opencv2/gapi/rmat.hpp b/modules/gapi/include/opencv2/gapi/rmat.hpp index ff834b46b1..f50bd08b65 100644 --- a/modules/gapi/include/opencv2/gapi/rmat.hpp +++ b/modules/gapi/include/opencv2/gapi/rmat.hpp @@ -54,11 +54,11 @@ public: { public: using DestroyCallback = std::function; + using stepsT = std::vector; View() = default; - View(const GMatDesc& desc, uchar* data, size_t step = 0u, DestroyCallback&& cb = nullptr) - : m_desc(desc), m_data(data), m_step(step == 0u ? elemSize()*cols() : step), m_cb(std::move(cb)) - {} + View(const GMatDesc& desc, uchar* data, const stepsT& steps = {}, DestroyCallback&& cb = nullptr); + View(const GMatDesc& desc, uchar* data, size_t step, DestroyCallback&& cb = nullptr); View(const View&) = delete; View& operator=(const View&) = delete; @@ -70,23 +70,30 @@ public: const std::vector& dims() const { return m_desc.dims; } int cols() const { return m_desc.size.width; } int rows() const { return m_desc.size.height; } - int type() const { return CV_MAKE_TYPE(depth(), chan()); } + int type() const; int depth() const { return m_desc.depth; } int chan() const { return m_desc.chan; } size_t elemSize() const { return CV_ELEM_SIZE(type()); } - template T* ptr(int y = 0, int x = 0) { - return reinterpret_cast(m_data + m_step*y + x*CV_ELEM_SIZE(type())); + template T* ptr(int y = 0) { + return reinterpret_cast(m_data + step()*y); } - template const T* ptr(int y = 0, int x = 0) const { - return reinterpret_cast(m_data + m_step*y + x*CV_ELEM_SIZE(type())); + template const T* ptr(int y = 0) const { + return reinterpret_cast(m_data + step()*y); } - size_t step() const { return m_step; } + template T* ptr(int y, int x) { + return reinterpret_cast(m_data + step()*y + step(1)*x); + } + template const T* ptr(int y, int x) const { + return reinterpret_cast(m_data + step()*y + step(1)*x); + } + size_t step(size_t i = 0) const { GAPI_DbgAssert(i{desc.size.height, desc.size.width} + : desc.dims; + View::stepsT steps(dims.size(), 0u); + auto type = typeFromDesc(desc); + steps.back() = CV_ELEM_SIZE(type); + for (int i = static_cast(dims.size())-2; i >= 0; i--) { + steps[i] = steps[i+1]*dims[i]; + } + return steps; +} +} // anonymous namespace + +View::View(const cv::GMatDesc& desc, uchar* data, size_t step, DestroyCallback&& cb) + : m_desc(checkDesc(desc)) + , m_data(data) + , m_steps([this, step](){ + GAPI_Assert(m_desc.dims.empty()); + auto steps = defaultSteps(m_desc); + if (step != 0u) { + steps[0] = step; + } + return steps; + }()) + , m_cb(std::move(cb)) { +} + +View::View(const cv::GMatDesc& desc, uchar* data, const stepsT &steps, DestroyCallback&& cb) + : m_desc(checkDesc(desc)) + , m_data(data) + , m_steps(steps == stepsT{} ? defaultSteps(m_desc): steps) + , m_cb(std::move(cb)) { +} + +int View::type() const { return typeFromDesc(m_desc); } + // There is an issue with default generated operator=(View&&) on Mac: -// it doesn't nullify m_cb of a moved object +// it doesn't nullify m_cb of the moved object View& View::operator=(View&& v) { - m_desc = v.m_desc; - m_data = v.m_data; - m_step = v.m_step; - m_cb = v.m_cb; - v.m_desc = {}; - v.m_data = nullptr; - v.m_step = 0u; - v.m_cb = nullptr; + m_desc = v.m_desc; + m_data = v.m_data; + m_steps = v.m_steps; + m_cb = v.m_cb; + v.m_desc = {}; + v.m_data = nullptr; + v.m_steps = {0u}; + v.m_cb = nullptr; return *this; } diff --git a/modules/gapi/src/backends/common/gbackend.hpp b/modules/gapi/src/backends/common/gbackend.hpp index 8c1749377e..4914715fa7 100644 --- a/modules/gapi/src/backends/common/gbackend.hpp +++ b/modules/gapi/src/backends/common/gbackend.hpp @@ -23,12 +23,26 @@ namespace cv { namespace gimpl { inline cv::Mat asMat(RMat::View& v) { +#if !defined(GAPI_STANDALONE) + return v.dims().empty() ? cv::Mat(v.rows(), v.cols(), v.type(), v.ptr(), v.step()) + : cv::Mat(v.dims(), v.type(), v.ptr(), v.steps().data()); +#else + // FIXME: add a check that steps are default return v.dims().empty() ? cv::Mat(v.rows(), v.cols(), v.type(), v.ptr(), v.step()) : cv::Mat(v.dims(), v.type(), v.ptr()); + +#endif } inline RMat::View asView(const Mat& m, RMat::View::DestroyCallback&& cb = nullptr) { - // FIXME: View doesn't support multidimensional cv::Mat's +#if !defined(GAPI_STANDALONE) + RMat::View::stepsT steps(m.dims); + for (int i = 0; i < m.dims; i++) { + steps[i] = m.step[i]; + } + return RMat::View(cv::descr_of(m), m.data, steps, std::move(cb)); +#else return RMat::View(cv::descr_of(m), m.data, m.step, std::move(cb)); +#endif } class RMatAdapter : public RMat::Adapter { diff --git a/modules/gapi/test/rmat/rmat_test_common.hpp b/modules/gapi/test/rmat/rmat_test_common.hpp index 47a744499e..5685d06253 100644 --- a/modules/gapi/test/rmat/rmat_test_common.hpp +++ b/modules/gapi/test/rmat/rmat_test_common.hpp @@ -19,14 +19,18 @@ public: : m_mat(m), m_callbackCalled(callbackCalled) {} virtual RMat::View access(RMat::Access access) override { + RMat::View::stepsT steps(m_mat.dims); + for (int i = 0; i < m_mat.dims; i++) { + steps[i] = m_mat.step[i]; + } if (access == RMat::Access::W) { - return RMat::View(cv::descr_of(m_mat), m_mat.data, m_mat.step, + return RMat::View(cv::descr_of(m_mat), m_mat.data, steps, [this](){ EXPECT_FALSE(m_callbackCalled); m_callbackCalled = true; }); } else { - return RMat::View(cv::descr_of(m_mat), m_mat.data, m_mat.step); + return RMat::View(cv::descr_of(m_mat), m_mat.data, steps); } } virtual cv::GMatDesc desc() const override { return cv::descr_of(m_mat); } @@ -42,8 +46,12 @@ public: : m_deviceMat(m), m_hostMat(m.clone()), m_callbackCalled(callbackCalled) {} virtual RMat::View access(RMat::Access access) override { + RMat::View::stepsT steps(m_hostMat.dims); + for (int i = 0; i < m_hostMat.dims; i++) { + steps[i] = m_hostMat.step[i]; + } if (access == RMat::Access::W) { - return RMat::View(cv::descr_of(m_hostMat), m_hostMat.data, m_hostMat.step, + return RMat::View(cv::descr_of(m_hostMat), m_hostMat.data, steps, [this](){ EXPECT_FALSE(m_callbackCalled); m_callbackCalled = true; @@ -51,7 +59,7 @@ public: }); } else { m_deviceMat.copyTo(m_hostMat); - return RMat::View(cv::descr_of(m_hostMat), m_hostMat.data, m_hostMat.step); + return RMat::View(cv::descr_of(m_hostMat), m_hostMat.data, steps); } } virtual cv::GMatDesc desc() const override { return cv::descr_of(m_hostMat); } diff --git a/modules/gapi/test/rmat/rmat_view_tests.cpp b/modules/gapi/test/rmat/rmat_view_tests.cpp index abc251660b..14025231a7 100644 --- a/modules/gapi/test/rmat/rmat_view_tests.cpp +++ b/modules/gapi/test/rmat/rmat_view_tests.cpp @@ -15,6 +15,8 @@ namespace opencv_test using cv::GMatDesc; using View = cv::RMat::View; using cv::Mat; +using cv::gimpl::asMat; +using cv::gimpl::asView; using namespace ::testing; static void expect_eq_desc(const GMatDesc& desc, const View& view) { @@ -22,7 +24,8 @@ static void expect_eq_desc(const GMatDesc& desc, const View& view) { EXPECT_EQ(desc.dims, view.dims()); EXPECT_EQ(desc.size.width, view.cols()); EXPECT_EQ(desc.size.height, view.rows()); - EXPECT_EQ(CV_MAKE_TYPE(desc.depth,desc.chan), view.type()); + EXPECT_EQ(desc.depth, view.depth()); + EXPECT_EQ(desc.chan, view.chan()); EXPECT_EQ(desc.depth, view.depth()); EXPECT_EQ(desc.chan, view.chan()); } @@ -40,10 +43,10 @@ TEST_P(RMatViewTest, ConstructionFromMat) { auto type = GetParam(); Mat mat(8,8,type); const auto desc = cv::descr_of(mat); - View view(cv::descr_of(mat), mat.ptr(), mat.step1()); + View view = asView(mat); expect_eq_desc(desc, view); EXPECT_EQ(mat.ptr(), view.ptr()); - EXPECT_EQ(mat.step1(), view.step()); + EXPECT_EQ(mat.step, view.step()); } TEST(RMatView, TestConstructionFromMatND) { @@ -66,16 +69,98 @@ TEST_P(RMatViewTest, DefaultStep) { EXPECT_EQ(static_cast(desc.size.width)*CV_ELEM_SIZE(type), view.step()); } -static Mat asMat(View& view) { - return Mat(view.size(), view.type(), view.ptr(), view.step()); +struct RMatViewNDTest : public TestWithParam< + std::tuple>{}; +TEST_P(RMatViewNDTest, DefaultStep) { + int depth = 0, ndims = 0; + std::tie(depth, ndims) = GetParam(); + std::vector dims(ndims, 12); + GMatDesc desc; + desc.dims = dims; + desc.depth = depth; + GAPI_Assert(desc.chan == -1); + auto elemSize = CV_ELEM_SIZE(depth); + auto total = std::accumulate(dims.begin(), dims.end(), elemSize, std::multiplies()); + std::vector data(total); + View view(desc, data.data()); + auto step = static_cast(total/dims[0]); + EXPECT_EQ(step, view.step(0)); + for (int i = 1; i < ndims; i++) { + step /= dims[i]; + EXPECT_EQ(step, view.step(i)); + } +} + +TEST_P(RMatViewNDTest, StepFromMat) { + int depth = 0, ndims = 0; + std::tie(depth, ndims) = GetParam(); + std::vector dims(ndims, 12); + cv::Mat mat(dims, depth); + auto view = asView(mat); + EXPECT_EQ(mat.ptr(), view.ptr()); + for (int i = 0; i < ndims; i++) { + EXPECT_EQ(mat.step[i], view.step(i)); + } +} + +TEST_P(RMatViewNDTest, StepFromView) { + int depth = 0, ndims = 0; + std::tie(depth, ndims) = GetParam(); + std::vector dims(ndims, 12); + std::vector aligned(ndims, 16); + GMatDesc desc; + desc.dims = dims; + desc.depth = depth; + GAPI_Assert(desc.chan == -1); + auto elemSize = CV_ELEM_SIZE(depth); + auto total = std::accumulate(aligned.begin(), aligned.end(), elemSize, std::multiplies()); + std::vector data(total); + View::stepsT steps(ndims); + auto step = static_cast(total/aligned[0]); + steps[0] = step; + for (int i = 1; i < ndims; i++) { + step /= aligned[i]; + steps[i] = step; + } + View view(desc, data.data(), steps); + auto mat = asMat(view); + EXPECT_EQ(mat.ptr(), view.ptr()); + for (int i = 0; i < ndims; i++) { + EXPECT_EQ(mat.step[i], view.step(i)); + } +} + +INSTANTIATE_TEST_CASE_P(Test, RMatViewNDTest, + Combine(Values(CV_8U, CV_32F), // depth + Values(1,2,3,4,7))); // ndims + +struct RMatViewNDTestNegative : public TestWithParam< + std::tuple>{}; +TEST_P(RMatViewNDTestNegative, DefaultStep) { + int depth = 0, chan = 0, ndims = 0; + std::tie(depth, chan, ndims) = GetParam(); + std::vector dims(ndims, 12); + GMatDesc desc; + desc.dims = dims; + desc.depth = depth; + desc.chan = chan; + auto elemSize = CV_ELEM_SIZE(depth); + auto total = std::accumulate(dims.begin(), dims.end(), elemSize, std::multiplies()); + std::vector data(total); + EXPECT_ANY_THROW(View view(desc, data.data())); } +INSTANTIATE_TEST_CASE_P(Test, RMatViewNDTestNegative, + Combine(Values(CV_8U, CV_32F), // depth + Values(1,2,3,4), // chan + Values(2,4,7))); // ndims + TEST_P(RMatViewTest, NonDefaultStepInput) { auto type = GetParam(); Mat bigMat(16,16,type); cv::randn(bigMat, cv::Scalar::all(127), cv::Scalar::all(40)); Mat mat = bigMat(cv::Rect{4,4,8,8}); - View view(cv::descr_of(mat), mat.data, mat.step); + View view = asView(mat); const auto viewMat = asMat(view); Mat ref, out; cv::Size ksize{1,1}; @@ -90,7 +175,36 @@ TEST_P(RMatViewTest, NonDefaultStepOutput) { cv::randn(mat, cv::Scalar::all(127), cv::Scalar::all(40)); Mat bigMat = Mat::zeros(16,16,type); Mat out = bigMat(cv::Rect{4,4,8,8}); - View view(cv::descr_of(out), out.ptr(), out.step); + View view = asView(out); + auto viewMat = asMat(view); + Mat ref; + cv::Size ksize{1,1}; + cv::blur(mat, viewMat, ksize); + cv::blur(mat, ref, ksize); + EXPECT_EQ(0, cvtest::norm(ref, out, NORM_INF)); +} + +TEST_P(RMatViewTest, NonDefaultStep2DInput) { + auto type = GetParam(); + Mat bigMat(16,16,type); + cv::randn(bigMat, cv::Scalar::all(127), cv::Scalar::all(40)); + Mat mat = bigMat(cv::Rect{4,4,8,8}); + View view(cv::descr_of(mat), mat.data, mat.step); + const auto viewMat = asMat(view); + Mat ref, out; + cv::Size ksize{1,1}; + cv::blur(viewMat, out, ksize); + cv::blur( mat, ref, ksize); + EXPECT_EQ(0, cvtest::norm(ref, out, NORM_INF)); +} + +TEST_P(RMatViewTest, NonDefaultStep2DOutput) { + auto type = GetParam(); + Mat mat(8,8,type); + cv::randn(mat, cv::Scalar::all(127), cv::Scalar::all(40)); + Mat bigMat = Mat::zeros(16,16,type); + Mat out = bigMat(cv::Rect{4,4,8,8}); + View view(cv::descr_of(out), out.data, out.step); auto viewMat = asMat(view); Mat ref; cv::Size ksize{1,1}; @@ -107,7 +221,7 @@ struct RMatViewCallbackTest : public ::testing::Test { : mat(8,8,CV_8UC1) { cv::randn(mat, cv::Scalar::all(127), cv::Scalar::all(40)); } - View getView() { return {cv::descr_of(mat), mat.ptr(), mat.step1(), [this](){ callbackCalls++; }}; } + View getView() { return asView(mat, [this](){ callbackCalls++; }); } int callbackCalls = 0; Mat mat; }; diff --git a/modules/gapi/test/s11n/gapi_s11n_tests.cpp b/modules/gapi/test/s11n/gapi_s11n_tests.cpp index 2fc1e46253..74aac19306 100644 --- a/modules/gapi/test/s11n/gapi_s11n_tests.cpp +++ b/modules/gapi/test/s11n/gapi_s11n_tests.cpp @@ -2,6 +2,7 @@ #include "backends/common/serialization.hpp" #include +#include <../src/backends/common/gbackend.hpp> // asView namespace { struct EmptyCustomType { }; @@ -134,12 +135,8 @@ public: MyRMatAdapter(cv::Mat m, int value, const std::string& str) : m_mat(m), m_value(value), m_str(str) {} - virtual cv::RMat::View access(cv::RMat::Access access) override { - if (access == cv::RMat::Access::W) { - return cv::RMat::View(cv::descr_of(m_mat), m_mat.data, m_mat.step); - } else { - return cv::RMat::View(cv::descr_of(m_mat), m_mat.data, m_mat.step); - } + virtual cv::RMat::View access(cv::RMat::Access) override { + return cv::gimpl::asView(m_mat); } virtual cv::GMatDesc desc() const override { return cv::descr_of(m_mat); } virtual void serialize(cv::gapi::s11n::IOStream& os) override {