mirror of https://github.com/opencv/opencv.git
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 testspull/18868/head
parent
b5c162175b
commit
b866d0dc38
16 changed files with 681 additions and 55 deletions
@ -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
|
@ -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; |
||||
} |
@ -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>(); |
||||
} |
@ -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
|
@ -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
|
Loading…
Reference in new issue