Merge pull request #18793 from dmatveev:dm/in_graph_metadata

G-API: Introduce runtime in-graph metadata

* G-API: In-graph metadata -- initial implementation

* G-API: Finish the in-graph metadata implementation for Streaming

* G-API: Fix standalone build & warnings for in-graph metadata

* G-API: In-graph meta -- fixed review comments

* G-API: Fix issues with desync causing failing tests
This commit is contained in:
Dmitry Matveev 2020-11-17 17:04:19 +03:00 committed by GitHub
parent b5c162175b
commit b866d0dc38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 681 additions and 55 deletions

View File

@ -57,6 +57,7 @@ file(GLOB gapi_ext_hdrs
set(gapi_srcs
# Front-end part
src/api/grunarg.cpp
src/api/gorigin.cpp
src/api/gmat.cpp
src/api/garray.cpp
@ -131,18 +132,19 @@ set(gapi_srcs
src/backends/ie/giebackend.cpp
src/backends/ie/giebackend/giewrapper.cpp
# ONNX Backend.
# ONNX backend
src/backends/onnx/gonnxbackend.cpp
# Render Backend.
# Render backend
src/backends/render/grenderocv.cpp
src/backends/render/ft_render.cpp
#PlaidML Backend
# PlaidML Backend
src/backends/plaidml/gplaidmlcore.cpp
src/backends/plaidml/gplaidmlbackend.cpp
# Compound
# Common backend code
src/backends/common/gmetabackend.cpp
src/backends/common/gcompoundbackend.cpp
src/backends/common/gcompoundkernel.cpp

View File

@ -9,12 +9,14 @@
#define OPENCV_GAPI_GARG_HPP
#include <vector>
#include <unordered_map>
#include <type_traits>
#include <opencv2/gapi/opencv_includes.hpp>
#include <opencv2/gapi/own/mat.hpp>
#include <opencv2/gapi/media.hpp>
#include <opencv2/gapi/util/util.hpp>
#include <opencv2/gapi/util/any.hpp>
#include <opencv2/gapi/util/variant.hpp>
@ -93,7 +95,7 @@ using GArgs = std::vector<GArg>;
// FIXME: Express as M<GProtoArg...>::type
// FIXME: Move to a separate file!
using GRunArg = util::variant<
using GRunArgBase = util::variant<
#if !defined(GAPI_STANDALONE)
cv::UMat,
#endif // !defined(GAPI_STANDALONE)
@ -105,6 +107,61 @@ using GRunArg = util::variant<
cv::detail::OpaqueRef,
cv::MediaFrame
>;
namespace detail {
template<typename,typename>
struct in_variant;
template<typename T, typename... Types>
struct in_variant<T, util::variant<Types...> >
: std::integral_constant<bool, cv::detail::contains<T, Types...>::value > {
};
} // namespace detail
struct GAPI_EXPORTS GRunArg: public GRunArgBase
{
// Metadata information here
using Meta = std::unordered_map<std::string, util::any>;
Meta meta;
// Mimic the old GRunArg semantics here, old of the times when
// GRunArg was an alias to variant<>
GRunArg();
GRunArg(const cv::GRunArg &arg);
GRunArg(cv::GRunArg &&arg);
GRunArg& operator= (const GRunArg &arg);
GRunArg& operator= (GRunArg &&arg);
template <typename T>
GRunArg(const T &t,
const Meta &m = Meta{},
typename std::enable_if< detail::in_variant<T, GRunArgBase>::value, int>::type = 0)
: GRunArgBase(t)
, meta(m)
{
}
template <typename T>
GRunArg(T &&t,
const Meta &m = Meta{},
typename std::enable_if< detail::in_variant<T, GRunArgBase>::value, int>::type = 0)
: GRunArgBase(std::move(t))
, meta(m)
{
}
template <typename T> auto operator= (const T &t)
-> typename std::enable_if< detail::in_variant<T, GRunArgBase>::value, cv::GRunArg>::type&
{
GRunArgBase::operator=(t);
return *this;
}
template <typename T> auto operator= (T&& t)
-> typename std::enable_if< detail::in_variant<T, GRunArgBase>::value, cv::GRunArg>::type&
{
GRunArgBase::operator=(std::move(t));
return *this;
}
};
using GRunArgs = std::vector<GRunArg>;
// TODO: Think about the addition operator
@ -129,11 +186,13 @@ namespace gapi
namespace wip
{
/**
* @brief This aggregate type represents all types which G-API can handle (via variant).
* @brief This aggregate type represents all types which G-API can
* handle (via variant).
*
* It only exists to overcome C++ language limitations (where a `using`-defined class can't be forward-declared).
* It only exists to overcome C++ language limitations (where a
* `using`-defined class can't be forward-declared).
*/
struct Data: public GRunArg
struct GAPI_EXPORTS Data: public GRunArg
{
using GRunArg::GRunArg;
template <typename T>

View File

@ -15,6 +15,7 @@
#include <opencv2/gapi/own/exports.hpp>
#include <opencv2/gapi/opencv_includes.hpp>
#include <opencv2/gapi/util/any.hpp>
#include <opencv2/gapi/util/variant.hpp>
#include <opencv2/gapi/util/throw.hpp>
#include <opencv2/gapi/util/type_traits.hpp>
@ -119,6 +120,7 @@ namespace detail
virtual void mov(BasicOpaqueRef &ref) = 0;
virtual const void* ptr() const = 0;
virtual void set(const cv::util::any &a) = 0;
};
template<typename T> class OpaqueRefT final: public BasicOpaqueRef
@ -212,6 +214,10 @@ namespace detail
}
virtual const void* ptr() const override { return &rref(); }
virtual void set(const cv::util::any &a) override {
wref() = util::any_cast<T>(a);
}
};
// This class strips type information from OpaqueRefT<> and makes it usable
@ -285,6 +291,13 @@ namespace detail
// May be used to uniquely identify this object internally
const void *ptr() const { return m_ref->ptr(); }
// Introduced for in-graph meta handling
OpaqueRef& operator= (const cv::util::any &a)
{
m_ref->set(a);
return *this;
}
};
} // namespace detail

View File

@ -21,9 +21,11 @@
* Note for developers: please don't put videoio dependency in G-API
* because of this file.
*/
#include <chrono>
#include <opencv2/videoio.hpp>
#include <opencv2/gapi/garg.hpp>
#include <opencv2/gapi/streaming/meta.hpp>
namespace cv {
namespace gapi {
@ -55,6 +57,7 @@ protected:
cv::VideoCapture cap;
cv::Mat first;
bool first_pulled = false;
int64_t counter = 0;
void prep()
{
@ -80,19 +83,26 @@ protected:
GAPI_Assert(!first.empty());
first_pulled = true;
data = first; // no need to clone here since it was cloned already
return true;
}
if (!cap.isOpened()) return false;
cv::Mat frame;
if (!cap.read(frame))
else
{
// end-of-stream happened
return false;
if (!cap.isOpened()) return false;
cv::Mat frame;
if (!cap.read(frame))
{
// end-of-stream happened
return false;
}
// Same reason to clone as in prep()
data = frame.clone();
}
// Same reason to clone as in prep()
data = frame.clone();
// Tag data with seq_id/ts
const auto now = std::chrono::system_clock::now();
const auto dur = std::chrono::duration_cast<std::chrono::microseconds>
(now.time_since_epoch());
data.meta[cv::gapi::streaming::meta_tag::timestamp] = int64_t{dur.count()};
data.meta[cv::gapi::streaming::meta_tag::seq_id] = int64_t{counter++};
return true;
}

View File

@ -0,0 +1,79 @@
// 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) 2020 Intel Corporation
#ifndef OPENCV_GAPI_GSTREAMING_META_HPP
#define OPENCV_GAPI_GSTREAMING_META_HPP
#include <opencv2/gapi/gopaque.hpp>
#include <opencv2/gapi/gcall.hpp>
#include <opencv2/gapi/gkernel.hpp>
#include <opencv2/gapi/gtype_traits.hpp>
namespace cv {
namespace gapi {
namespace streaming {
// FIXME: the name is debatable
namespace meta_tag {
static constexpr const char * timestamp = "org.opencv.gapi.meta.timestamp";
static constexpr const char * seq_id = "org.opencv.gapi.meta.seq_id";
} // namespace meta_tag
namespace detail {
struct GMeta {
static const char *id() {
return "org.opencv.streaming.meta";
}
// A universal yield for meta(), same as in GDesync
template<typename... R, int... IIs>
static std::tuple<R...> yield(cv::GCall &call, cv::detail::Seq<IIs...>) {
return std::make_tuple(cv::detail::Yield<R>::yield(call, IIs)...);
}
// Also a universal outMeta stub here
static GMetaArgs getOutMeta(const GMetaArgs &args, const GArgs &) {
return args;
}
};
} // namespace detail
template<typename T, typename G>
cv::GOpaque<T> meta(G g, const std::string &tag) {
using O = cv::GOpaque<T>;
cv::GKernel k{
detail::GMeta::id() // kernel id
, tag // kernel tag. Use meta tag here
, &detail::GMeta::getOutMeta // outMeta callback
, {cv::detail::GTypeTraits<O>::shape} // output Shape
, {cv::detail::GTypeTraits<G>::op_kind} // input data kinds
, {cv::detail::GObtainCtor<O>::get()} // output template ctors
};
cv::GCall call(std::move(k));
call.pass(g);
return std::get<0>(detail::GMeta::yield<O>(call, cv::detail::MkSeq<1>::type()));
}
template<typename G>
cv::GOpaque<int64_t> timestamp(G g) {
return meta<int64_t>(g, meta_tag::timestamp);
}
template<typename G>
cv::GOpaque<int64_t> seq_id(G g) {
return meta<int64_t>(g, meta_tag::seq_id);
}
template<typename G>
cv::GOpaque<int64_t> seqNo(G g) {
// Old name, compatibility only
return seq_id(g);
}
} // namespace streaming
} // namespace gapi
} // namespace cv
#endif // OPENCV_GAPI_GSTREAMING_META_HPP

View File

@ -143,6 +143,14 @@ void bindInArg(Mag& mag, const RcDesc &rc, const GRunArg &arg, HandleRMat handle
if (handleRMat == HandleRMat::SKIP) return;
GAPI_Assert(arg.index() == GRunArg::index_of<cv::RMat>());
bindRMat(mag, rc, util::get<cv::RMat>(arg), RMat::Access::R);
// FIXME: Here meta may^WWILL be copied multiple times!
// Replace it is reference-counted object?
mag.meta<cv::RMat>()[rc.id] = arg.meta;
mag.meta<cv::Mat>()[rc.id] = arg.meta;
#if !defined(GAPI_STANDALONE)
mag.meta<cv::UMat>()[rc.id] = arg.meta;
#endif
break;
}
@ -154,19 +162,23 @@ void bindInArg(Mag& mag, const RcDesc &rc, const GRunArg &arg, HandleRMat handle
case GRunArg::index_of<cv::Scalar>() : mag_scalar = util::get<cv::Scalar>(arg); break;
default: util::throw_error(std::logic_error("content type of the runtime argument does not match to resource description ?"));
}
mag.meta<cv::Scalar>()[rc.id] = arg.meta;
break;
}
case GShape::GARRAY:
mag.template slot<cv::detail::VectorRef>()[rc.id] = util::get<cv::detail::VectorRef>(arg);
mag.slot<cv::detail::VectorRef>()[rc.id] = util::get<cv::detail::VectorRef>(arg);
mag.meta<cv::detail::VectorRef>()[rc.id] = arg.meta;
break;
case GShape::GOPAQUE:
mag.template slot<cv::detail::OpaqueRef>()[rc.id] = util::get<cv::detail::OpaqueRef>(arg);
mag.slot<cv::detail::OpaqueRef>()[rc.id] = util::get<cv::detail::OpaqueRef>(arg);
mag.meta<cv::detail::OpaqueRef>()[rc.id] = arg.meta;
break;
case GShape::GFRAME:
mag.template slot<cv::MediaFrame>()[rc.id] = util::get<cv::MediaFrame>(arg);
mag.slot<cv::MediaFrame>()[rc.id] = util::get<cv::MediaFrame>(arg);
mag.meta<cv::MediaFrame>()[rc.id] = arg.meta;
break;
default:
@ -250,13 +262,23 @@ cv::GRunArg getArg(const Mag& mag, const RcDesc &ref)
// Wrap associated CPU object (either host or an internal one)
switch (ref.shape)
{
case GShape::GMAT: return GRunArg(mag.template slot<cv::RMat>().at(ref.id));
case GShape::GSCALAR: return GRunArg(mag.template slot<cv::Scalar>().at(ref.id));
case GShape::GMAT:
return GRunArg(mag.slot<cv::RMat>().at(ref.id),
mag.meta<cv::RMat>().at(ref.id));
case GShape::GSCALAR:
return GRunArg(mag.slot<cv::Scalar>().at(ref.id),
mag.meta<cv::Scalar>().at(ref.id));
// Note: .at() is intentional for GArray and GOpaque as objects MUST be already there
// (and constructed by either bindIn/Out or resetInternal)
case GShape::GARRAY: return GRunArg(mag.template slot<cv::detail::VectorRef>().at(ref.id));
case GShape::GOPAQUE: return GRunArg(mag.template slot<cv::detail::OpaqueRef>().at(ref.id));
case GShape::GFRAME: return GRunArg(mag.template slot<cv::MediaFrame>().at(ref.id));
case GShape::GARRAY:
return GRunArg(mag.slot<cv::detail::VectorRef>().at(ref.id),
mag.meta<cv::detail::VectorRef>().at(ref.id));
case GShape::GOPAQUE:
return GRunArg(mag.slot<cv::detail::OpaqueRef>().at(ref.id),
mag.meta<cv::detail::OpaqueRef>().at(ref.id));
case GShape::GFRAME:
return GRunArg(mag.slot<cv::MediaFrame>().at(ref.id),
mag.meta<cv::MediaFrame>().at(ref.id));
default:
util::throw_error(std::logic_error("Unsupported GShape type"));
break;

View File

@ -0,0 +1,33 @@
// 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) 2020 Intel Corporation
#include "precomp.hpp"
#include <opencv2/gapi/garg.hpp>
cv::GRunArg::GRunArg() {
}
cv::GRunArg::GRunArg(const cv::GRunArg &arg)
: cv::GRunArgBase(static_cast<const cv::GRunArgBase&>(arg))
, meta(arg.meta) {
}
cv::GRunArg::GRunArg(cv::GRunArg &&arg)
: cv::GRunArgBase(std::move(static_cast<const cv::GRunArgBase&>(arg)))
, meta(std::move(arg.meta)) {
}
cv::GRunArg& cv::GRunArg::operator= (const cv::GRunArg &arg) {
cv::GRunArgBase::operator=(static_cast<const cv::GRunArgBase&>(arg));
meta = arg.meta;
return *this;
}
cv::GRunArg& cv::GRunArg::operator= (cv::GRunArg &&arg) {
cv::GRunArgBase::operator=(std::move(static_cast<const cv::GRunArgBase&>(arg)));
meta = std::move(arg.meta);
return *this;
}

View File

@ -62,6 +62,8 @@ namespace magazine {
template<typename... Ts> struct Class
{
template<typename T> using MapT = std::unordered_map<int, T>;
using MapM = std::unordered_map<int, GRunArg::Meta>;
template<typename T> MapT<T>& slot()
{
return std::get<ade::util::type_list_index<T, Ts...>::value>(slots);
@ -70,8 +72,17 @@ namespace magazine {
{
return std::get<ade::util::type_list_index<T, Ts...>::value>(slots);
}
template<typename T> MapM& meta()
{
return metas[ade::util::type_list_index<T, Ts...>::value];
}
template<typename T> const MapM& meta() const
{
return metas[ade::util::type_list_index<T, Ts...>::value];
}
private:
std::tuple<MapT<Ts>...> slots;
std::array<MapM, sizeof...(Ts)> metas;
};
} // namespace magazine

View File

@ -0,0 +1,105 @@
// 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) 2020 Intel Corporation
#include "precomp.hpp"
#include <opencv2/gapi/gcommon.hpp> // compile args
#include <opencv2/gapi/util/any.hpp> // any
#include <opencv2/gapi/streaming/meta.hpp> // GMeta
#include "compiler/gobjref.hpp" // RcDesc
#include "compiler/gmodel.hpp" // GModel, Op
#include "backends/common/gbackend.hpp"
#include "api/gbackend_priv.hpp" // FIXME: Make it part of Backend SDK!
#include "backends/common/gmetabackend.hpp"
namespace {
class GraphMetaExecutable final: public cv::gimpl::GIslandExecutable {
std::string m_meta_tag;
public:
GraphMetaExecutable(const ade::Graph& g,
const std::vector<ade::NodeHandle>& nodes);
bool canReshape() const override;
void reshape(ade::Graph&, const cv::GCompileArgs&) override;
void run(std::vector<InObj> &&input_objs,
std::vector<OutObj> &&output_objs) override;
};
bool GraphMetaExecutable::canReshape() const {
return true;
}
void GraphMetaExecutable::reshape(ade::Graph&, const cv::GCompileArgs&) {
// do nothing here
}
GraphMetaExecutable::GraphMetaExecutable(const ade::Graph& g,
const std::vector<ade::NodeHandle>& nodes) {
// There may be only one node in the graph
GAPI_Assert(nodes.size() == 1u);
cv::gimpl::GModel::ConstGraph cg(g);
const auto &op = cg.metadata(nodes[0]).get<cv::gimpl::Op>();
GAPI_Assert(op.k.name == cv::gapi::streaming::detail::GMeta::id());
m_meta_tag = op.k.tag;
}
void GraphMetaExecutable::run(std::vector<InObj> &&input_objs,
std::vector<OutObj> &&output_objs) {
GAPI_Assert(input_objs.size() == 1u);
GAPI_Assert(output_objs.size() == 1u);
const cv::GRunArg in_arg = input_objs[0].second;
cv::GRunArgP out_arg = output_objs[0].second;
auto it = in_arg.meta.find(m_meta_tag);
if (it == in_arg.meta.end()) {
cv::util::throw_error
(std::logic_error("Run-time meta "
+ m_meta_tag
+ " is not found in object "
+ std::to_string(static_cast<int>(input_objs[0].first.shape))
+ "/"
+ std::to_string(input_objs[0].first.id)));
}
cv::util::get<cv::detail::OpaqueRef>(out_arg) = it->second;
}
class GraphMetaBackendImpl final: public cv::gapi::GBackend::Priv {
virtual void unpackKernel(ade::Graph &,
const ade::NodeHandle &,
const cv::GKernelImpl &) override {
// Do nothing here
}
virtual EPtr compile(const ade::Graph& graph,
const cv::GCompileArgs&,
const std::vector<ade::NodeHandle>& nodes,
const std::vector<cv::gimpl::Data>&,
const std::vector<cv::gimpl::Data>&) const override {
return EPtr{new GraphMetaExecutable(graph, nodes)};
}
};
cv::gapi::GBackend graph_meta_backend() {
static cv::gapi::GBackend this_backend(std::make_shared<GraphMetaBackendImpl>());
return this_backend;
}
struct InGraphMetaKernel final: public cv::detail::KernelTag {
using API = cv::gapi::streaming::detail::GMeta;
static cv::gapi::GBackend backend() { return graph_meta_backend(); }
static int kernel() { return 42; }
};
} // anonymous namespace
cv::gapi::GKernelPackage cv::gimpl::meta::kernels() {
return cv::gapi::kernels<InGraphMetaKernel>();
}

View File

@ -0,0 +1,16 @@
#ifndef OPENCV_GAPI_SRC_COMMON_META_BACKEND_HPP
#define OPENCV_GAPI_SRC_COMMON_META_BACKEND_HPP
#include <opencv2/gapi/gkernel.hpp>
namespace cv {
namespace gimpl {
namespace meta {
cv::gapi::GKernelPackage kernels();
} // namespace meta
} // namespace gimpl
} // namespace cv
#endif // OPENCV_GAPI_SRC_COMMON_META_BACKEND_HPP

View File

@ -35,6 +35,7 @@
#include "executor/gexecutor.hpp"
#include "executor/gstreamingexecutor.hpp"
#include "backends/common/gbackend.hpp"
#include "backends/common/gmetabackend.hpp"
// <FIXME:>
#if !defined(GAPI_STANDALONE)
@ -58,7 +59,8 @@ namespace
for (const auto &b : pkg.backends()) {
aux_pkg = combine(aux_pkg, b.priv().auxiliaryKernels());
}
return combine(pkg, aux_pkg);
// Always include built-in meta<> implementation
return combine(pkg, aux_pkg, cv::gimpl::meta::kernels());
};
auto has_use_only = cv::gapi::getCompileArg<cv::gapi::use_only>(args);

View File

@ -357,26 +357,21 @@ void GIslandExecutable::run(GIslandExecutable::IInput &in, GIslandExecutable::IO
for (auto &&it: ade::util::zip(ade::util::toRange(in_desc),
ade::util::toRange(in_vector)))
{
// FIXME: Not every Island expects a cv::Mat instead of own::Mat on input
// This kludge should go as a result of de-ownification
const cv::GRunArg& in_data_orig = std::get<1>(it);
cv::GRunArg in_data;
#if !defined(GAPI_STANDALONE)
switch (in_data_orig.index())
{
case cv::GRunArg::index_of<cv::Mat>():
in_data = cv::GRunArg{cv::make_rmat<cv::gimpl::RMatAdapter>(cv::util::get<cv::Mat>(in_data_orig))};
break;
case cv::GRunArg::index_of<cv::Scalar>():
in_data = cv::GRunArg{(cv::util::get<cv::Scalar>(in_data_orig))};
// FIXME: This whole construct is ugly, from
// its writing to a need in this in general
in_data = cv::GRunArg{ cv::make_rmat<cv::gimpl::RMatAdapter>(cv::util::get<cv::Mat>(in_data_orig))
, in_data_orig.meta
};
break;
default:
in_data = in_data_orig;
break;
}
#else
in_data = in_data_orig;
#endif // GAPI_STANDALONE
in_objs.emplace_back(std::get<0>(it), std::move(in_data));
}
for (auto &&it: ade::util::indexed(ade::util::toRange(out_desc)))
@ -385,9 +380,27 @@ void GIslandExecutable::run(GIslandExecutable::IInput &in, GIslandExecutable::IO
out.get(ade::util::checked_cast<int>(ade::util::index(it))));
}
run(std::move(in_objs), std::move(out_objs));
// Propagate in-graph meta down to the graph
// Note: this is not a complete implementation! Mainly this is a stub
// and the proper implementation should come later.
//
// Propagating the meta information here has its pros and cons.
// Pros: it works here uniformly for both regular and streaming cases,
// also for the majority of old-fashioned (synchronous) backends
// Cons: backends implementing the asynchronous run(IInput,IOutput)
// won't get it out of the box
cv::GRunArg::Meta stub_meta;
for (auto &&in_arg : in_vector)
{
stub_meta.insert(in_arg.meta.begin(), in_arg.meta.end());
}
// Report output objects as "ready" to the executor, also post
// calculated in-graph meta for the objects
for (auto &&it: out_objs)
{
out.post(std::move(it.second)); // report output objects as "ready" to the executor
out.meta(it.second, stub_meta);
out.post(std::move(it.second));
}
}

View File

@ -172,6 +172,10 @@ struct GIslandExecutable::IOutput: public GIslandExecutable::IODesc {
virtual GRunArgP get(int idx) = 0; // Allocate (wrap) a new data object for output idx
virtual void post(GRunArgP&&) = 0; // Release the object back to the framework (mark available)
virtual void post(EndOfStream&&) = 0; // Post end-of-stream marker back to the framework
// Assign accumulated metadata to the given output object.
// This method can only be called after get() and before post().
virtual void meta(const GRunArgP&, const GRunArg::Meta &) = 0;
};
// GIslandEmitter - a backend-specific thing which feeds data into

View File

@ -12,6 +12,8 @@
#include <ade/util/zip_range.hpp>
#include <opencv2/gapi/opencv_includes.hpp>
#include "api/gproto_priv.hpp" // ptr(GRunArgP)
#include "executor/gexecutor.hpp"
#include "compiler/passes/passes.hpp"
@ -105,6 +107,9 @@ void bindInArgExec(Mag& mag, const RcDesc &rc, const GRunArg &arg)
mag_rmat = util::get<cv::RMat>(arg); break;
default: util::throw_error(std::logic_error("content type of the runtime argument does not match to resource description ?"));
}
// FIXME: has to take extra care about meta here for this particuluar
// case, just because this function exists at all
mag.meta<cv::RMat>()[rc.id] = arg.meta;
}
void bindOutArgExec(Mag& mag, const RcDesc &rc, const GRunArgP &arg)
@ -131,7 +136,7 @@ cv::GRunArgP getObjPtrExec(Mag& mag, const RcDesc &rc)
{
return getObjPtr(mag, rc);
}
return GRunArgP(&mag.template slot<cv::RMat>()[rc.id]);
return GRunArgP(&mag.slot<cv::RMat>()[rc.id]);
}
void writeBackExec(const Mag& mag, const RcDesc &rc, GRunArgP &g_arg)
@ -155,6 +160,25 @@ void writeBackExec(const Mag& mag, const RcDesc &rc, GRunArgP &g_arg)
default: util::throw_error(std::logic_error("content type of the runtime argument does not match to resource description ?"));
}
}
void assignMetaStubExec(Mag& mag, const RcDesc &rc, const cv::GRunArg::Meta &meta) {
switch (rc.shape)
{
case GShape::GARRAY: mag.meta<cv::detail::VectorRef>()[rc.id] = meta; break;
case GShape::GOPAQUE: mag.meta<cv::detail::OpaqueRef>()[rc.id] = meta; break;
case GShape::GSCALAR: mag.meta<cv::Scalar>()[rc.id] = meta; break;
case GShape::GFRAME: mag.meta<cv::MediaFrame>()[rc.id] = meta; break;
case GShape::GMAT:
mag.meta<cv::Mat>() [rc.id] = meta;
mag.meta<cv::RMat>()[rc.id] = meta;
#if !defined(GAPI_STANDALONE)
mag.meta<cv::UMat>()[rc.id] = meta;
#endif
break;
default: util::throw_error(std::logic_error("Unsupported GShape type")); break;
}
}
} // anonymous namespace
}}} // namespace cv::gimpl::magazine
@ -231,11 +255,28 @@ public:
class cv::gimpl::GExecutor::Output final: public cv::gimpl::GIslandExecutable::IOutput
{
cv::gimpl::Mag &mag;
virtual GRunArgP get(int idx) override { return magazine::getObjPtrExec(mag, desc()[idx]); }
virtual void post(GRunArgP&&) override { } // Do nothing here
virtual void post(EndOfStream&&) override {} // Do nothing here too
std::unordered_map<const void*, int> out_idx;
GRunArgP get(int idx) override
{
auto r = magazine::getObjPtrExec(mag, desc()[idx]);
// Remember the output port for this output object
out_idx[cv::gimpl::proto::ptr(r)] = idx;
return r;
}
void post(GRunArgP&&) override { } // Do nothing here
void post(EndOfStream&&) override {} // Do nothing here too
void meta(const GRunArgP &out, const GRunArg::Meta &m) override
{
const auto idx = out_idx.at(cv::gimpl::proto::ptr(out));
magazine::assignMetaStubExec(mag, desc()[idx], m);
}
public:
Output(cv::gimpl::Mag &m, const std::vector<RcDesc> &rcs) : mag(m) { set(rcs); }
Output(cv::gimpl::Mag &m, const std::vector<RcDesc> &rcs)
: mag(m)
{
set(rcs);
}
};
void cv::gimpl::GExecutor::run(cv::gimpl::GRuntimeArgs &&args)
@ -330,7 +371,7 @@ void cv::gimpl::GExecutor::run(cv::gimpl::GRuntimeArgs &&args)
// Run the script
for (auto &op : m_ops)
{
// (5)
// (5), (6)
Input i{m_res, op.in_objects};
Output o{m_res, op.out_objects};
op.isl_exec->run(i, o);

View File

@ -350,16 +350,14 @@ bool QueueReader::getInputVector(std::vector<Q*> &in_queues,
// value-initialized scalar)
// It can also hold a constant value received with
// Stop::Kind::CNST message (see above).
// FIXME: Variant move problem
isl_inputs[id] = const_cast<const cv::GRunArg&>(in_constants[id]);
isl_inputs[id] = in_constants[id];
continue;
}
q->pop(m_cmd[id]);
if (!cv::util::holds_alternative<Stop>(m_cmd[id]))
{
// FIXME: Variant move problem
isl_inputs[id] = const_cast<const cv::GRunArg &>(cv::util::get<cv::GRunArg>(m_cmd[id]));
isl_inputs[id] = cv::util::get<cv::GRunArg>(m_cmd[id]);
}
else // A Stop sign
{
@ -382,7 +380,7 @@ bool QueueReader::getInputVector(std::vector<Q*> &in_queues,
// NEXT time (on a next call to getInputVector()), the
// "q==nullptr" check above will be triggered, but now
// we need to make it manually:
isl_inputs[id] = const_cast<const cv::GRunArg&>(in_constants[id]);
isl_inputs[id] = in_constants[id];
}
else
{
@ -666,8 +664,7 @@ class StreamingOutput final: public cv::gimpl::GIslandExecutable::IOutput
Cmd cmd;
if (cv::util::holds_alternative<cv::GRunArg>(post_iter->data))
{
// FIXME: That ugly VARIANT problem
cmd = Cmd{const_cast<const cv::GRunArg&>(cv::util::get<cv::GRunArg>(post_iter->data))};
cmd = Cmd{cv::util::get<cv::GRunArg>(post_iter->data)};
}
else
{
@ -677,8 +674,7 @@ class StreamingOutput final: public cv::gimpl::GIslandExecutable::IOutput
}
for (auto &&q : m_out_queues[out_idx])
{
// FIXME: This ugly VARIANT problem
q->push(const_cast<const Cmd&>(cmd));
q->push(cmd);
}
post_iter = m_postings[out_idx].erase(post_iter);
}
@ -708,6 +704,15 @@ class StreamingOutput final: public cv::gimpl::GIslandExecutable::IOutput
}
}
}
void meta(const cv::GRunArgP &out, const cv::GRunArg::Meta &m) override
{
const auto it = m_postIdx.find(cv::gimpl::proto::ptr(out));
GAPI_Assert(it != m_postIdx.end());
const auto out_iter = it->second.second;
cv::util::get<cv::GRunArg>(out_iter->data).meta = m;
}
public:
explicit StreamingOutput(const cv::GMetaArgs &metas,
std::vector< std::vector<Q*> > &out_queues,
@ -769,6 +774,7 @@ void islandActorThread(std::vector<cv::gimpl::RcDesc> in_rcs, //
void collectorThread(std::vector<Q*> in_queues,
std::vector<int> in_mapping,
const std::size_t out_size,
const bool handle_stop,
Q& out_queue)
{
// These flags are static now: regardless if the sync or
@ -783,9 +789,14 @@ void collectorThread(std::vector<Q*> in_queues,
while (true)
{
cv::GRunArgs this_result(out_size);
if (!qr.getResultsVector(in_queues, in_mapping, out_size, this_result))
const bool ok = qr.getResultsVector(in_queues, in_mapping, out_size, this_result);
if (!ok)
{
out_queue.push(Cmd{Stop{}});
if (handle_stop)
{
out_queue.push(Cmd{Stop{}});
}
// Terminate the thread anyway
return;
}
out_queue.push(Cmd{Result{std::move(this_result), flags}});
@ -1263,12 +1274,22 @@ void cv::gimpl::GStreamingExecutor::setSource(GRunArgs &&ins)
// If there are desynchronized parts in the graph, there may be
// multiple theads polling every separate (desynchronized)
// branch in the graph individually.
const bool has_main_path = m_sink_sync.end() !=
std::find(m_sink_sync.begin(), m_sink_sync.end(), -1);
for (auto &&info : m_collector_map) {
m_threads.emplace_back(collectorThread,
info.second.queues,
info.second.mapping,
m_sink_queues.size(),
has_main_path ? info.first == -1 : true, // see below (*)
std::ref(m_out_queue));
// (*) - there may be a problem with desynchronized paths when those work
// faster than the main path. In this case, the desync paths get "Stop" message
// earlier and thus broadcast it down to pipeline gets stopped when there is
// some "main path" data to process. This new collectorThread's flag regulates it:
// - desync paths should never post Stop message if there is a main path.
// - if there is no main path, than any desync path can terminate the execution.
}
state = State::READY;
}

View File

@ -0,0 +1,195 @@
// 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) 2020 Intel Corporation
#include <tuple>
#include <unordered_set>
#include "test_precomp.hpp"
#include "opencv2/gapi/streaming/meta.hpp"
#include "opencv2/gapi/streaming/cap.hpp"
namespace opencv_test {
namespace {
void initTestDataPath() {
#ifndef WINRT
static bool initialized = false;
if (!initialized)
{
// Since G-API has no own test data (yet), it is taken from the common space
const char* testDataPath = getenv("OPENCV_TEST_DATA_PATH");
if (testDataPath != nullptr) {
cvtest::addDataSearchPath(testDataPath);
initialized = true;
}
}
#endif // WINRT
}
} // anonymous namespace
TEST(GraphMeta, Trad_AccessInput) {
cv::GMat in;
cv::GMat out1 = cv::gapi::blur(in, cv::Size(3,3));
cv::GOpaque<int> out2 = cv::gapi::streaming::meta<int>(in, "foo");
cv::GComputation graph(cv::GIn(in), cv::GOut(out1, out2));
cv::Mat in_mat = cv::Mat::eye(cv::Size(64, 64), CV_8UC1);
cv::Mat out_mat;
int out_meta = 0;
// manually set metadata in the input fields
auto inputs = cv::gin(in_mat);
inputs[0].meta["foo"] = 42;
graph.apply(std::move(inputs), cv::gout(out_mat, out_meta));
EXPECT_EQ(42, out_meta);
}
TEST(GraphMeta, Trad_AccessTmp) {
cv::GMat in;
cv::GMat tmp = cv::gapi::blur(in, cv::Size(3,3));
cv::GMat out1 = tmp+1;
cv::GOpaque<float> out2 = cv::gapi::streaming::meta<float>(tmp, "bar");
cv::GComputation graph(cv::GIn(in), cv::GOut(out1, out2));
cv::Mat in_mat = cv::Mat::eye(cv::Size(64, 64), CV_8UC1);
cv::Mat out_mat;
float out_meta = 0.f;
// manually set metadata in the input fields
auto inputs = cv::gin(in_mat);
inputs[0].meta["bar"] = 1.f;
graph.apply(std::move(inputs), cv::gout(out_mat, out_meta));
EXPECT_EQ(1.f, out_meta);
}
TEST(GraphMeta, Trad_AccessOutput) {
cv::GMat in;
cv::GMat out1 = cv::gapi::blur(in, cv::Size(3,3));
cv::GOpaque<std::string> out2 = cv::gapi::streaming::meta<std::string>(out1, "baz");
cv::GComputation graph(cv::GIn(in), cv::GOut(out1, out2));
cv::Mat in_mat = cv::Mat::eye(cv::Size(64, 64), CV_8UC1);
cv::Mat out_mat;
std::string out_meta;
// manually set metadata in the input fields
auto inputs = cv::gin(in_mat);
// NOTE: Assigning explicitly an std::string is important,
// otherwise a "const char*" will be stored and won't be
// translated properly by util::any since std::string is
// used within the graph.
inputs[0].meta["baz"] = std::string("opencv");
graph.apply(std::move(inputs), cv::gout(out_mat, out_meta));
EXPECT_EQ("opencv", out_meta);
}
TEST(GraphMeta, Streaming_AccessInput) {
initTestDataPath();
cv::GMat in;
cv::GMat out1 = cv::gapi::blur(in, cv::Size(3,3));
cv::GOpaque<int64_t> out2 = cv::gapi::streaming::seq_id(in);
cv::GComputation graph(cv::GIn(in), cv::GOut(out1, out2));
auto ccomp = graph.compileStreaming();
ccomp.setSource<cv::gapi::wip::GCaptureSource>(findDataFile("cv/video/768x576.avi", false));
ccomp.start();
cv::Mat out_mat;
int64_t out_meta = 0;
int64_t expected_counter = 0;
while (ccomp.pull(cv::gout(out_mat, out_meta))) {
EXPECT_EQ(expected_counter, out_meta);
++expected_counter;
}
}
TEST(GraphMeta, Streaming_AccessOutput) {
initTestDataPath();
cv::GMat in;
cv::GMat out1 = cv::gapi::blur(in, cv::Size(3,3));
cv::GOpaque<int64_t> out2 = cv::gapi::streaming::seq_id(out1);
cv::GOpaque<int64_t> out3 = cv::gapi::streaming::timestamp(out1);
cv::GComputation graph(cv::GIn(in), cv::GOut(out1, out2, out3));
auto ccomp = graph.compileStreaming();
ccomp.setSource<cv::gapi::wip::GCaptureSource>(findDataFile("cv/video/768x576.avi", false));
ccomp.start();
cv::Mat out_mat;
int64_t out_meta = 0;
int64_t out_timestamp = 0;
int64_t expected_counter = 0;
int64_t prev_timestamp = -1;
while (ccomp.pull(cv::gout(out_mat, out_meta, out_timestamp))) {
EXPECT_EQ(expected_counter, out_meta);
++expected_counter;
EXPECT_NE(prev_timestamp, out_timestamp);
prev_timestamp = out_timestamp;
}
}
TEST(GraphMeta, Streaming_AccessDesync) {
initTestDataPath();
cv::GMat in;
cv::GOpaque<int64_t> out1 = cv::gapi::streaming::seq_id(in);
cv::GOpaque<int64_t> out2 = cv::gapi::streaming::timestamp(in);
cv::GMat out3 = cv::gapi::blur(in, cv::Size(3,3));
cv::GMat tmp = cv::gapi::streaming::desync(in);
cv::GScalar mean = cv::gapi::mean(tmp);
cv::GOpaque<int64_t> out4 = cv::gapi::streaming::seq_id(mean);
cv::GOpaque<int64_t> out5 = cv::gapi::streaming::timestamp(mean);
cv::GComputation graph(cv::GIn(in), cv::GOut(out1, out2, out3, out4, out5));
auto ccomp = graph.compileStreaming();
ccomp.setSource<cv::gapi::wip::GCaptureSource>(findDataFile("cv/video/768x576.avi", false));
ccomp.start();
cv::optional<int64_t> out_sync_id;
cv::optional<int64_t> out_sync_ts;
cv::optional<cv::Mat> out_sync_mat;
cv::optional<int64_t> out_desync_id;
cv::optional<int64_t> out_desync_ts;
std::unordered_set<int64_t> sync_ids;
std::unordered_set<int64_t> desync_ids;
while (ccomp.pull(cv::gout(out_sync_id, out_sync_ts, out_sync_mat,
out_desync_id, out_desync_ts))) {
if (out_sync_id.has_value()) {
CV_Assert(out_sync_ts.has_value());
CV_Assert(out_sync_mat.has_value());
sync_ids.insert(out_sync_id.value());
}
if (out_desync_id.has_value()) {
CV_Assert(out_desync_ts.has_value());
desync_ids.insert(out_desync_id.value());
}
}
// Visually report that everything is really ok
std::cout << sync_ids.size() << " vs " << desync_ids.size() << std::endl;
// Desync path should generate less objects than the synchronized one
EXPECT_GE(sync_ids.size(), desync_ids.size());
// ..but all desynchronized IDs must be present in the synchronized set
for (auto &&d_id : desync_ids) {
EXPECT_TRUE(sync_ids.count(d_id) > 0);
}
}
} // namespace opencv_test