mirror of https://github.com/opencv/opencv.git
Merge pull request #16118 from smirnov-alexey:as/gopaque
G-API: GOpaque implementation * Stub initial copypasted solution * Fix mov test and add a couple of others * Fix warnings * More code coverage and tests * fix macos warning * address review comments * Address review comments and fix indentation * Fix build on armv7pull/16478/head
parent
2ced568d34
commit
0d456f9111
33 changed files with 850 additions and 23 deletions
@ -0,0 +1,289 @@ |
||||
// 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.
|
||||
//
|
||||
// Copyright (C) 2019 Intel Corporation
|
||||
|
||||
|
||||
#ifndef OPENCV_GAPI_GOPAQUE_HPP |
||||
#define OPENCV_GAPI_GOPAQUE_HPP |
||||
|
||||
#include <functional> |
||||
#include <ostream> |
||||
#include <memory> |
||||
|
||||
#include <opencv2/gapi/own/exports.hpp> |
||||
#include <opencv2/gapi/opencv_includes.hpp> |
||||
|
||||
#include <opencv2/gapi/util/variant.hpp> |
||||
#include <opencv2/gapi/util/throw.hpp> |
||||
#include <opencv2/gapi/own/assert.hpp> |
||||
|
||||
namespace cv |
||||
{ |
||||
// Forward declaration; GNode and GOrigin are an internal
|
||||
// (user-inaccessible) classes.
|
||||
class GNode; |
||||
struct GOrigin; |
||||
|
||||
template<typename T> class GOpaque; |
||||
|
||||
/**
|
||||
* \addtogroup gapi_meta_args |
||||
* @{ |
||||
*/ |
||||
struct GOpaqueDesc |
||||
{ |
||||
// FIXME: Body
|
||||
// FIXME: Also implement proper operator== then
|
||||
bool operator== (const GOpaqueDesc&) const { return true; } |
||||
}; |
||||
template<typename U> GOpaqueDesc descr_of(const U &) { return {};} |
||||
static inline GOpaqueDesc empty_gopaque_desc() {return {}; } |
||||
/** @} */ |
||||
|
||||
std::ostream& operator<<(std::ostream& os, const cv::GOpaqueDesc &desc); |
||||
|
||||
namespace detail |
||||
{ |
||||
// ConstructOpaque is a callback which stores information about T and is used by
|
||||
// G-API runtime to construct an object in host memory (T remains opaque for G-API).
|
||||
// ConstructOpaque is carried into G-API internals by GOpaqueU.
|
||||
// Currently it is suitable for Host (CPU) plugins only, real offload may require
|
||||
// more information for manual memory allocation on-device.
|
||||
class OpaqueRef; |
||||
using ConstructOpaque = std::function<void(OpaqueRef&)>; |
||||
|
||||
// FIXME: garray.hpp already contains hint classes (for actual T type verification),
|
||||
// need to think where it can be moved (currently opaque uses it from garray)
|
||||
|
||||
// This class strips type information from GOpaque<T> and makes it usable
|
||||
// in the G-API graph compiler (expression unrolling, graph generation, etc).
|
||||
// Part of GProtoArg.
|
||||
class GAPI_EXPORTS GOpaqueU |
||||
{ |
||||
public: |
||||
GOpaqueU(const GNode &n, std::size_t out); // Operation result constructor
|
||||
|
||||
template <typename T> |
||||
bool holds() const; // Check if was created from GOpaque<T>
|
||||
|
||||
GOrigin& priv(); // Internal use only
|
||||
const GOrigin& priv() const; // Internal use only
|
||||
|
||||
protected: |
||||
GOpaqueU(); // Default constructor
|
||||
template<class> friend class cv::GOpaque; // (available for GOpaque<T> only)
|
||||
|
||||
void setConstructFcn(ConstructOpaque &&cv); // Store T-aware constructor
|
||||
|
||||
template <typename T> |
||||
void specifyType(); // Store type of initial GOpaque<T>
|
||||
|
||||
std::shared_ptr<GOrigin> m_priv; |
||||
std::shared_ptr<TypeHintBase> m_hint; |
||||
}; |
||||
|
||||
template <typename T> |
||||
bool GOpaqueU::holds() const{ |
||||
GAPI_Assert(m_hint != nullptr); |
||||
using U = typename std::decay<T>::type; |
||||
return dynamic_cast<TypeHint<U>*>(m_hint.get()) != nullptr; |
||||
}; |
||||
|
||||
template <typename T> |
||||
void GOpaqueU::specifyType(){ |
||||
m_hint.reset(new TypeHint<typename std::decay<T>::type>); |
||||
}; |
||||
|
||||
// This class represents a typed object reference.
|
||||
// Depending on origins, this reference may be either "just a" reference to
|
||||
// an object created externally, OR actually own the underlying object
|
||||
// (be value holder).
|
||||
class BasicOpaqueRef |
||||
{ |
||||
public: |
||||
cv::GOpaqueDesc m_desc; |
||||
virtual ~BasicOpaqueRef() {} |
||||
|
||||
virtual void mov(BasicOpaqueRef &ref) = 0; |
||||
}; |
||||
|
||||
template<typename T> class OpaqueRefT final: public BasicOpaqueRef |
||||
{ |
||||
using empty_t = util::monostate; |
||||
using ro_ext_t = const T *; |
||||
using rw_ext_t = T *; |
||||
using rw_own_t = T ; |
||||
util::variant<empty_t, ro_ext_t, rw_ext_t, rw_own_t> m_ref; |
||||
|
||||
inline bool isEmpty() const { return util::holds_alternative<empty_t>(m_ref); } |
||||
inline bool isROExt() const { return util::holds_alternative<ro_ext_t>(m_ref); } |
||||
inline bool isRWExt() const { return util::holds_alternative<rw_ext_t>(m_ref); } |
||||
inline bool isRWOwn() const { return util::holds_alternative<rw_own_t>(m_ref); } |
||||
|
||||
void init(const T* obj = nullptr) |
||||
{ |
||||
if (obj) m_desc = cv::descr_of(*obj); |
||||
} |
||||
|
||||
public: |
||||
OpaqueRefT() { init(); } |
||||
virtual ~OpaqueRefT() {} |
||||
|
||||
explicit OpaqueRefT(const T& obj) : m_ref(&obj) { init(&obj); } |
||||
explicit OpaqueRefT( T& obj) : m_ref(&obj) { init(&obj); } |
||||
explicit OpaqueRefT( T&& obj) : m_ref(std::move(obj)) { init(&obj); } |
||||
|
||||
// Reset a OpaqueRefT. Called only for objects instantiated
|
||||
// internally in G-API (e.g. temporary GOpaque<T>'s within a
|
||||
// computation). Reset here means both initialization
|
||||
// (creating an object) and reset (discarding its existing
|
||||
// content before the next execution). Must never be called
|
||||
// for external OpaqueRefTs.
|
||||
void reset() |
||||
{ |
||||
if (isEmpty()) |
||||
{ |
||||
T empty_obj{}; |
||||
m_desc = cv::descr_of(empty_obj); |
||||
m_ref = std::move(empty_obj); |
||||
GAPI_Assert(isRWOwn()); |
||||
} |
||||
else if (isRWOwn()) |
||||
{ |
||||
util::get<rw_own_t>(m_ref) = {}; |
||||
} |
||||
else GAPI_Assert(false); // shouldn't be called in *EXT modes
|
||||
} |
||||
|
||||
// Obtain a WRITE reference to underlying object
|
||||
// Used by CPU kernel API wrappers when a kernel execution frame
|
||||
// is created
|
||||
T& wref() |
||||
{ |
||||
GAPI_Assert(isRWExt() || isRWOwn()); |
||||
if (isRWExt()) return *util::get<rw_ext_t>(m_ref); |
||||
if (isRWOwn()) return util::get<rw_own_t>(m_ref); |
||||
util::throw_error(std::logic_error("Impossible happened")); |
||||
} |
||||
|
||||
// Obtain a READ reference to underlying object
|
||||
// Used by CPU kernel API wrappers when a kernel execution frame
|
||||
// is created
|
||||
const T& rref() const |
||||
{ |
||||
// ANY object can be accessed for reading, even if it declared for
|
||||
// output. Example -- a GComputation from [in] to [out1,out2]
|
||||
// where [out2] is a result of operation applied to [out1]:
|
||||
//
|
||||
// GComputation boundary
|
||||
// . . . . . . .
|
||||
// . .
|
||||
// [in] ----> foo() ----> [out1]
|
||||
// . . :
|
||||
// . . . .:. . .
|
||||
// . V .
|
||||
// . bar() ---> [out2]
|
||||
// . . . . . . . . . . . .
|
||||
//
|
||||
if (isROExt()) return *util::get<ro_ext_t>(m_ref); |
||||
if (isRWExt()) return *util::get<rw_ext_t>(m_ref); |
||||
if (isRWOwn()) return util::get<rw_own_t>(m_ref); |
||||
util::throw_error(std::logic_error("Impossible happened")); |
||||
} |
||||
|
||||
virtual void mov(BasicOpaqueRef &v) override { |
||||
OpaqueRefT<T> *tv = dynamic_cast<OpaqueRefT<T>*>(&v); |
||||
GAPI_Assert(tv != nullptr); |
||||
wref() = std::move(tv->wref()); |
||||
} |
||||
}; |
||||
|
||||
// This class strips type information from OpaqueRefT<> and makes it usable
|
||||
// in the G-API executables (carrying run-time data/information to kernels).
|
||||
// Part of GRunArg.
|
||||
// Its methods are typed proxies to OpaqueRefT<T>.
|
||||
// OpaqueRef maintains "reference" semantics so two copies of OpaqueRef refer
|
||||
// to the same underlying object.
|
||||
class OpaqueRef |
||||
{ |
||||
std::shared_ptr<BasicOpaqueRef> m_ref; |
||||
|
||||
template<typename T> inline void check() const |
||||
{ |
||||
GAPI_DbgAssert(dynamic_cast<OpaqueRefT<T>*>(m_ref.get()) != nullptr); |
||||
} |
||||
|
||||
public: |
||||
OpaqueRef() = default; |
||||
|
||||
template<typename T> explicit OpaqueRef(T&& obj) : |
||||
m_ref(new OpaqueRefT<typename std::decay<T>::type>(std::forward<T>(obj))) {} |
||||
|
||||
template<typename T> void reset() |
||||
{ |
||||
if (!m_ref) m_ref.reset(new OpaqueRefT<T>()); |
||||
|
||||
check<T>(); |
||||
static_cast<OpaqueRefT<T>&>(*m_ref).reset(); |
||||
} |
||||
|
||||
template<typename T> T& wref() |
||||
{ |
||||
check<T>(); |
||||
return static_cast<OpaqueRefT<T>&>(*m_ref).wref(); |
||||
} |
||||
|
||||
template<typename T> const T& rref() const |
||||
{ |
||||
check<T>(); |
||||
return static_cast<OpaqueRefT<T>&>(*m_ref).rref(); |
||||
} |
||||
|
||||
void mov(OpaqueRef &v) |
||||
{ |
||||
m_ref->mov(*v.m_ref); |
||||
} |
||||
|
||||
cv::GOpaqueDesc descr_of() const |
||||
{ |
||||
return m_ref->m_desc; |
||||
} |
||||
}; |
||||
} // namespace detail
|
||||
|
||||
/** \addtogroup gapi_data_objects
|
||||
* @{ |
||||
*/ |
||||
|
||||
template<typename T> class GOpaque |
||||
{ |
||||
public: |
||||
GOpaque() { putDetails(); } // Empty constructor
|
||||
explicit GOpaque(detail::GOpaqueU &&ref) // GOpaqueU-based constructor
|
||||
: m_ref(ref) { putDetails(); } // (used by GCall, not for users)
|
||||
|
||||
detail::GOpaqueU strip() const { return m_ref; } |
||||
|
||||
private: |
||||
// Host type (or Flat type) - the type this GOpaque is actually
|
||||
// specified to.
|
||||
using HT = typename detail::flatten_g<typename std::decay<T>::type>::type; |
||||
|
||||
static void CTor(detail::OpaqueRef& ref) { |
||||
ref.reset<HT>(); |
||||
} |
||||
void putDetails() { |
||||
m_ref.setConstructFcn(&CTor); |
||||
m_ref.specifyType<HT>(); |
||||
} |
||||
|
||||
detail::GOpaqueU m_ref; |
||||
}; |
||||
|
||||
/** @} */ |
||||
|
||||
} // namespace cv
|
||||
|
||||
#endif // OPENCV_GAPI_GOPAQUE_HPP
|
@ -0,0 +1,45 @@ |
||||
// 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.
|
||||
//
|
||||
// Copyright (C) 2019 Intel Corporation
|
||||
|
||||
|
||||
#include "precomp.hpp" |
||||
#include <opencv2/gapi/gopaque.hpp> |
||||
#include "api/gorigin.hpp" |
||||
|
||||
// cv::detail::GOpaqueU public implementation ///////////////////////////////////
|
||||
cv::detail::GOpaqueU::GOpaqueU() |
||||
: m_priv(new GOrigin(GShape::GOPAQUE, cv::GNode::Param())) |
||||
{ |
||||
} |
||||
|
||||
cv::detail::GOpaqueU::GOpaqueU(const GNode &n, std::size_t out) |
||||
: m_priv(new GOrigin(GShape::GOPAQUE, n, out)) |
||||
{ |
||||
} |
||||
|
||||
cv::GOrigin& cv::detail::GOpaqueU::priv() |
||||
{ |
||||
return *m_priv; |
||||
} |
||||
|
||||
const cv::GOrigin& cv::detail::GOpaqueU::priv() const |
||||
{ |
||||
return *m_priv; |
||||
} |
||||
|
||||
void cv::detail::GOpaqueU::setConstructFcn(ConstructOpaque &&co) |
||||
{ |
||||
m_priv->ctor = std::move(co); |
||||
} |
||||
|
||||
namespace cv { |
||||
std::ostream& operator<<(std::ostream& os, const cv::GOpaqueDesc &) |
||||
{ |
||||
// FIXME: add type information here
|
||||
os << "(Opaque)"; |
||||
return os; |
||||
} |
||||
} |
@ -0,0 +1,197 @@ |
||||
// 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.
|
||||
//
|
||||
// Copyright (C) 2019 Intel Corporation
|
||||
|
||||
#include "test_precomp.hpp" |
||||
#include <string> |
||||
#include <utility> |
||||
|
||||
namespace opencv_test |
||||
{ |
||||
|
||||
namespace ThisTest |
||||
{ |
||||
using GPointOpaque = cv::GOpaque<cv::Point>; |
||||
|
||||
G_TYPED_KERNEL(GeneratePoint, <GPointOpaque(GMat)>, "test.opaque.gen_point") |
||||
{ |
||||
static GOpaqueDesc outMeta(const GMatDesc&) { return empty_gopaque_desc(); } |
||||
}; |
||||
|
||||
G_TYPED_KERNEL(FillMat, <GMat(cv::GOpaque<int>, int, int, cv::Size)>, "test.opaque.fill_mat") |
||||
{ |
||||
static GMatDesc outMeta(const GOpaqueDesc&, int depth, int chan, cv::Size size) |
||||
{ |
||||
return cv::GMatDesc{depth, chan, size}; |
||||
} |
||||
}; |
||||
|
||||
G_TYPED_KERNEL(PaintPoint, <GMat(GPointOpaque, int, int, cv::Size)>, "test.opaque.paint_point") |
||||
{ |
||||
static GMatDesc outMeta(const GOpaqueDesc&, int depth, int chan, cv::Size size) |
||||
{ |
||||
return cv::GMatDesc{depth, chan, size}; |
||||
} |
||||
}; |
||||
|
||||
struct MyCustomType{ |
||||
int num; |
||||
std::string s; |
||||
}; |
||||
|
||||
using GOpaq2 = std::tuple<GOpaque<MyCustomType>,GOpaque<MyCustomType>>; |
||||
|
||||
G_TYPED_KERNEL_M(GenerateOpaque, <GOpaq2(GMat, GMat, std::string)>, "test.opaque.gen_point_multy") |
||||
{ |
||||
static std::tuple<GOpaqueDesc, GOpaqueDesc> outMeta(const GMatDesc&, const GMatDesc&, std::string) |
||||
{ |
||||
return std::make_tuple(empty_gopaque_desc(), empty_gopaque_desc()); |
||||
} |
||||
}; |
||||
|
||||
} // namespace ThisTest
|
||||
|
||||
namespace |
||||
{ |
||||
GAPI_OCV_KERNEL(OCVGeneratePoint, ThisTest::GeneratePoint) |
||||
{ |
||||
static void run(const cv::Mat&, cv::Point& out) |
||||
{ |
||||
out = cv::Point(42, 42); |
||||
} |
||||
}; |
||||
|
||||
GAPI_OCV_KERNEL(OCVFillMat, ThisTest::FillMat) |
||||
{ |
||||
static void run(int a, int, int, cv::Size, cv::Mat& out) |
||||
{ |
||||
out = cv::Scalar(a); |
||||
} |
||||
}; |
||||
|
||||
GAPI_OCV_KERNEL(OCVPaintPoint, ThisTest::PaintPoint) |
||||
{ |
||||
static void run(cv::Point a, int, int, cv::Size, cv::Mat& out) |
||||
{ |
||||
out.at<uint8_t>(a) = 77; |
||||
} |
||||
}; |
||||
|
||||
GAPI_OCV_KERNEL(OCVGenerateOpaque, ThisTest::GenerateOpaque) |
||||
{ |
||||
static void run(const cv::Mat& a, const cv::Mat& b, const std::string& s, |
||||
ThisTest::MyCustomType &out1, ThisTest::MyCustomType &out2) |
||||
{ |
||||
out1.num = a.size().width * a.size().height; |
||||
out1.s = s; |
||||
|
||||
out2.num = b.size().width * b.size().height; |
||||
auto s2 = s; |
||||
std::reverse(s2.begin(), s2.end()); |
||||
out2.s = s2; |
||||
} |
||||
}; |
||||
} // (anonymous namespace)
|
||||
|
||||
TEST(GOpaque, TestOpaqueOut) |
||||
{ |
||||
cv::Mat input = cv::Mat(52, 52, CV_8U); |
||||
cv::Point point; |
||||
|
||||
cv::GMat in; |
||||
auto out = ThisTest::GeneratePoint::on(in); |
||||
|
||||
cv::GComputation c(cv::GIn(in), cv::GOut(out)); |
||||
c.apply(cv::gin(input), cv::gout(point), cv::compile_args(cv::gapi::kernels<OCVGeneratePoint>())); |
||||
|
||||
EXPECT_TRUE(point == cv::Point(42, 42)); |
||||
} |
||||
|
||||
TEST(GOpaque, TestOpaqueIn) |
||||
{ |
||||
cv::Size sz = {42, 42}; |
||||
int depth = CV_8U; |
||||
int chan = 1; |
||||
cv::Mat mat = cv::Mat(sz, CV_MAKETYPE(depth, chan)); |
||||
int fill = 0; |
||||
|
||||
cv::GOpaque<int> in; |
||||
auto out = ThisTest::FillMat::on(in, depth, chan, sz); |
||||
|
||||
cv::GComputation c(cv::GIn(in), cv::GOut(out)); |
||||
c.apply(cv::gin(fill), cv::gout(mat), cv::compile_args(cv::gapi::kernels<OCVFillMat>())); |
||||
|
||||
auto diff = cv::Mat(sz, CV_MAKETYPE(depth, chan), cv::Scalar(fill)) - mat; |
||||
EXPECT_EQ(cv::countNonZero(diff), 0); |
||||
} |
||||
|
||||
TEST(GOpaque, TestOpaqueBetween) |
||||
{ |
||||
cv::Size sz = {50, 50}; |
||||
int depth = CV_8U; |
||||
int chan = 1; |
||||
cv::Mat mat_in = cv::Mat::zeros(sz, CV_MAKETYPE(depth, chan)); |
||||
cv::Mat mat_out = cv::Mat::zeros(sz, CV_MAKETYPE(depth, chan)); |
||||
|
||||
cv::GMat in, out; |
||||
auto betw = ThisTest::GeneratePoint::on(in); |
||||
out = ThisTest::PaintPoint::on(betw, depth, chan, sz); |
||||
|
||||
cv::GComputation c(cv::GIn(in), cv::GOut(out)); |
||||
c.apply(cv::gin(mat_in), cv::gout(mat_out), cv::compile_args(cv::gapi::kernels<OCVGeneratePoint, OCVPaintPoint>())); |
||||
|
||||
int painted = mat_out.at<uint8_t>(42, 42); |
||||
EXPECT_EQ(painted, 77); |
||||
} |
||||
|
||||
TEST(GOpaque, TestOpaqueCustomOut2) |
||||
{ |
||||
cv::Mat input1 = cv::Mat(52, 52, CV_8U); |
||||
cv::Mat input2 = cv::Mat(42, 42, CV_8U); |
||||
std::string str = "opaque"; |
||||
std::string str2 = str; |
||||
std::reverse(str2.begin(), str2.end()); |
||||
|
||||
ThisTest::MyCustomType out1, out2; |
||||
|
||||
cv::GMat in1, in2; |
||||
auto out = ThisTest::GenerateOpaque::on(in1, in2, str); |
||||
|
||||
cv::GComputation c(cv::GIn(in1, in2), cv::GOut(std::get<0>(out), std::get<1>(out))); |
||||
c.apply(cv::gin(input1, input2), cv::gout(out1, out2), cv::compile_args(cv::gapi::kernels<OCVGenerateOpaque>())); |
||||
|
||||
EXPECT_EQ(out1.num, input1.size().width * input1.size().height); |
||||
EXPECT_EQ(out1.s, str); |
||||
|
||||
EXPECT_EQ(out2.num, input2.size().width * input2.size().height); |
||||
EXPECT_EQ(out2.s, str2); |
||||
} |
||||
|
||||
TEST(GOpaque_OpaqueRef, TestMov) |
||||
{ |
||||
// Warning: this test is testing some not-very-public APIs
|
||||
// Test how OpaqueRef's mov() (aka poor man's move()) is working.
|
||||
|
||||
using I = std::string; |
||||
|
||||
std::string str = "this string must be long due to short string optimization"; |
||||
const I gold(str); |
||||
|
||||
I test = gold; |
||||
const char* ptr = test.data(); |
||||
|
||||
cv::detail::OpaqueRef ref(test); |
||||
cv::detail::OpaqueRef mov; |
||||
mov.reset<I>(); |
||||
|
||||
EXPECT_EQ(gold, ref.rref<I>()); // ref = gold
|
||||
|
||||
mov.mov(ref); |
||||
EXPECT_EQ(gold, mov.rref<I>()); // mov obtained the data
|
||||
EXPECT_EQ(ptr, mov.rref<I>().data()); // pointer is unchanged (same data)
|
||||
EXPECT_EQ(test, ref.rref<I>()); // ref = test
|
||||
EXPECT_NE(test, mov.rref<I>()); // ref lost the data
|
||||
} |
||||
} // namespace opencv_test
|
Loading…
Reference in new issue