1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-30 07:43:03 +04:00

G-API: Introduce streaming::desync and infer(ROI)

- desync() is a new (and for now, the only one) intrinsic
  which splits the graph execution into asynchronous parts
  when running in Streaming mode;
- desync() makes no effect when compiling in Traditional mode;
- Added tests on desync() working in various scenarios;
- Extended GStreamingExecutor to support desync(); also extended
  GStreamingCompiled() with a new version of pull() returning a
  vector of optional values;
- Fixed various issues with storing the type information & proper
  construction callbacks for GArray<> and GOpaque;

- Introduced a new infer(Roi,GMat) overload with a sample;

- Introduced an internal API for Islands to control fusion
  procedure (to fuse or not to fuse);
- Introduced handleStopStream() callback for island executables;
- Added GCompileArgs to metadata of the graph (required for other
  features).
This commit is contained in:
Dmitry Matveev
2020-03-18 02:38:24 +03:00
parent f345ed564a
commit ca8bb8d053
40 changed files with 2827 additions and 195 deletions
@@ -2,7 +2,7 @@
// 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) 2018 Intel Corporation
// Copyright (C) 2018-2020 Intel Corporation
#include "../test_precomp.hpp"
@@ -29,7 +29,9 @@ namespace
, ""
, nullptr
, { GShape::GMAT }
, { D::OpaqueKind::CV_UNKNOWN } }).pass(m).yield(0);
, { D::OpaqueKind::CV_UNKNOWN }
, { cv::detail::HostCtor{cv::util::monostate{}} }
}).pass(m).yield(0);
}
cv::GMat binaryOp(cv::GMat m1, cv::GMat m2)
@@ -38,7 +40,9 @@ namespace
, ""
, nullptr
, { GShape::GMAT }
, { D::OpaqueKind::CV_UNKNOWN, D::OpaqueKind::CV_UNKNOWN } }).pass(m1, m2).yield(0);
, { D::OpaqueKind::CV_UNKNOWN, D::OpaqueKind::CV_UNKNOWN }
, { cv::detail::HostCtor{cv::util::monostate{}} }
}).pass(m1, m2).yield(0);
}
std::vector<ade::NodeHandle> collectOperations(const cv::gimpl::GModel::Graph& gr)
@@ -513,7 +513,65 @@ TEST(IslandFusion, Regression_ShouldFuseAll)
EXPECT_EQ(1u, isl_nhs.size()); // 1 island
}
// FIXME: add more tests on mixed (hetero) graphs
TEST(IslandFusion, Test_Desync_NoFuse)
{
cv::GMat in;
cv::GMat tmp1 = in*0.5f;
cv::GMat tmp2 = tmp1 + in;
cv::GMat tmp3 = cv::gapi::streaming::desync(tmp1);
cv::GMat tmp4 = tmp3*0.1f;
const auto in_meta = cv::GMetaArg(cv::GMatDesc{CV_8U,1,cv::Size(32,32)});
cv::GComputation comp(cv::GIn(in), cv::GOut(tmp2, tmp4));
//////////////////////////////////////////////////////////////////
// Compile the graph in "regular" mode, it should produce a single island
{
using namespace cv::gimpl;
GCompiler compiler(comp, {in_meta}, cv::compile_args());
GCompiler::GPtr graph = compiler.generateGraph();
compiler.runPasses(*graph);
auto isl_model = GModel::ConstGraph(*graph).metadata()
.get<IslandModel>().model;
GIslandModel::ConstGraph gim(*isl_model);
const auto is_island = [&](ade::NodeHandle nh) {
return (NodeKind::ISLAND == gim.metadata(nh).get<NodeKind>().k);
};
const auto num_isl = std::count_if(gim.nodes().begin(),
gim.nodes().end(),
is_island);
EXPECT_EQ(1, num_isl);
}
//////////////////////////////////////////////////////////////////
// Now compile the graph in the streaming mode.
// It has to produce two islands
{
using namespace cv::gimpl;
GCompiler compiler(comp, {in_meta}, cv::compile_args());
GCompiler::GPtr graph = compiler.generateGraph();
GModel::Graph(*graph).metadata().set(Streaming{});
compiler.runPasses(*graph);
auto isl_model = GModel::ConstGraph(*graph).metadata()
.get<IslandModel>().model;
GIslandModel::ConstGraph gim(*isl_model);
const auto is_island = [&](ade::NodeHandle nh) {
return (NodeKind::ISLAND == gim.metadata(nh).get<NodeKind>().k);
};
const auto num_isl = std::count_if(gim.nodes().begin(),
gim.nodes().end(),
is_island);
EXPECT_EQ(2, num_isl);
}
}
// Fixme: add more tests on mixed (hetero) graphs
// ADE-222, ADE-223
// FIXME: add test on combination of user-specified island
@@ -2,11 +2,14 @@
// 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) 2018 Intel Corporation
// Copyright (C) 2018 - 2020 Intel Corporation
#include "../test_precomp.hpp"
#include <ade/graph.hpp>
#include <ade/typed_graph.hpp>
#include "compiler/transactions.hpp"
namespace opencv_test
@@ -33,10 +36,11 @@ struct SimpleGraph
enum { node_nums = 5 };
ade::Graph graph;
ade::NodeHandle fused_nh; /* For check that fusion node is connected to the
inputs of the prod and the outputs of the cons */
ade::NodeHandle fused_nh; // For check that fusion node is connected to the
// inputs of the prod and the outputs of the cons
std::array<ade::NodeHandle, node_nums> nhs;
std::array<ade::EdgeHandle, node_nums - 1> ehs;
using Change = ChangeT<>;
Change::List changes;
SimpleGraph()
@@ -192,8 +196,6 @@ TEST_F(Transactions, DropNode_Commit)
TEST_F(Transactions, Fusion_Commit)
{
namespace C = Change;
fuse();
commit();
@@ -204,8 +206,6 @@ TEST_F(Transactions, Fusion_Commit)
TEST_F(Transactions, Fusion_RollBack)
{
namespace C = Change;
fuse();
rollback();
@@ -219,4 +219,151 @@ TEST_F(Transactions, Fusion_RollBack)
}
}
namespace
{
struct MetaInt {
static const char *name() { return "int_meta"; }
int x;
};
struct MetaStr {
static const char *name() { return "string_meta"; }
std::string s;
};
}
TEST(PreservedMeta, TestMetaCopy_Full)
{
ade::Graph g;
ade::TypedGraph<MetaInt, MetaStr> tg(g);
auto src_nh = tg.createNode();
tg.metadata(src_nh).set(MetaInt{42});
tg.metadata(src_nh).set(MetaStr{"hi"});
auto dst_nh = tg.createNode();
EXPECT_FALSE(tg.metadata(dst_nh).contains<MetaInt>());
EXPECT_FALSE(tg.metadata(dst_nh).contains<MetaStr>());
// Here we specify all the meta types we know about the src node
// Assume Preserved copies its all for us
Preserved<ade::NodeHandle, MetaInt, MetaStr>(g, src_nh).copyTo(g, dst_nh);
ASSERT_TRUE(tg.metadata(dst_nh).contains<MetaInt>());
ASSERT_TRUE(tg.metadata(dst_nh).contains<MetaStr>());
EXPECT_EQ(42, tg.metadata(dst_nh).get<MetaInt>().x);
EXPECT_EQ("hi", tg.metadata(dst_nh).get<MetaStr>().s);
}
TEST(PreservedMeta, TestMetaCopy_Partial_Dst)
{
ade::Graph g;
ade::TypedGraph<MetaInt, MetaStr> tg(g);
auto tmp_nh1 = tg.createNode();
auto tmp_nh2 = tg.createNode();
auto src_eh = tg.link(tmp_nh1, tmp_nh2);
tg.metadata(src_eh).set(MetaInt{42});
tg.metadata(src_eh).set(MetaStr{"hi"});
auto tmp_nh3 = tg.createNode();
auto tmp_nh4 = tg.createNode();
auto dst_eh = tg.link(tmp_nh3, tmp_nh4);
EXPECT_FALSE(tg.metadata(dst_eh).contains<MetaInt>());
EXPECT_FALSE(tg.metadata(dst_eh).contains<MetaStr>());
// Here we specify just a single meta type for the src node
// Assume Preserved copies only this type and nothing else
Preserved<ade::EdgeHandle, MetaStr>(g, src_eh).copyTo(g, dst_eh);
ASSERT_FALSE(tg.metadata(dst_eh).contains<MetaInt>());
ASSERT_TRUE (tg.metadata(dst_eh).contains<MetaStr>());
EXPECT_EQ("hi", tg.metadata(dst_eh).get<MetaStr>().s);
}
TEST(PreservedMeta, TestMetaCopy_Partial_Src)
{
ade::Graph g;
ade::TypedGraph<MetaInt, MetaStr> tg(g);
auto src_nh = tg.createNode();
tg.metadata(src_nh).set(MetaInt{42});
auto dst_nh = tg.createNode();
EXPECT_FALSE(tg.metadata(dst_nh).contains<MetaInt>());
EXPECT_FALSE(tg.metadata(dst_nh).contains<MetaStr>());
// Here we specify all the meta types we know about the src node
// but the src node has just one of them.
// A valid situation, only MetaInt to be copied.
Preserved<ade::NodeHandle, MetaInt, MetaStr>(g, src_nh).copyTo(g, dst_nh);
ASSERT_TRUE (tg.metadata(dst_nh).contains<MetaInt>());
ASSERT_FALSE(tg.metadata(dst_nh).contains<MetaStr>());
EXPECT_EQ(42, tg.metadata(dst_nh).get<MetaInt>().x);
}
TEST(PreservedMeta, TestMetaCopy_Nothing)
{
ade::Graph g;
ade::TypedGraph<MetaInt, MetaStr> tg(g);
auto src_nh = tg.createNode();
auto dst_nh = tg.createNode();
EXPECT_FALSE(tg.metadata(src_nh).contains<MetaInt>());
EXPECT_FALSE(tg.metadata(src_nh).contains<MetaStr>());
EXPECT_FALSE(tg.metadata(dst_nh).contains<MetaInt>());
EXPECT_FALSE(tg.metadata(dst_nh).contains<MetaStr>());
// Here we specify all the meta types we know about the src node
// but the src node has none of those. See how it works now
Preserved<ade::NodeHandle, MetaInt, MetaStr>(g, src_nh).copyTo(g, dst_nh);
ASSERT_FALSE(tg.metadata(dst_nh).contains<MetaInt>());
ASSERT_FALSE(tg.metadata(dst_nh).contains<MetaStr>());
}
TEST(PreservedMeta, DropEdge)
{
ade::Graph g;
ade::TypedGraph<MetaInt, MetaStr> tg(g);
auto nh1 = tg.createNode();
auto nh2 = tg.createNode();
auto eh = tg.link(nh1, nh2);
tg.metadata(eh).set(MetaInt{42});
tg.metadata(eh).set(MetaStr{"hi"});
// Drop an edge using the transaction API
using Change = ChangeT<MetaInt, MetaStr>;
Change::List changes;
changes.enqueue<Change::DropLink>(g, nh1, eh);
EXPECT_EQ(0u, nh1->outNodes().size());
EXPECT_EQ(nullptr, eh);
// Now restore the edge and check if it's meta was restored
changes.rollback(g);
ASSERT_EQ(1u, nh1->outNodes().size());
eh = *nh1->outEdges().begin();
ASSERT_TRUE(tg.metadata(eh).contains<MetaInt>());
ASSERT_TRUE(tg.metadata(eh).contains<MetaStr>());
EXPECT_EQ(42, tg.metadata(eh).get<MetaInt>().x);
EXPECT_EQ("hi", tg.metadata(eh).get<MetaStr>().s);
}
} // opencv_test
+3 -3
View File
@@ -55,7 +55,7 @@ TEST(ConcQueue, Clear)
EXPECT_FALSE(q.try_pop(x));
}
// In this test, every writer thread produce its own range of integer
// In this test, every writer thread produces its own range of integer
// numbers, writing those to a shared queue.
//
// Every reader thread pops elements from the queue (until -1 is
@@ -64,12 +64,12 @@ TEST(ConcQueue, Clear)
// Finally, the master thread waits for completion of all other
// threads and verifies that all the necessary data is
// produced/obtained.
namespace
{
using StressParam = std::tuple<int // Num writer threads
,int // Num elements per writer
,int // Num reader threads
,std::size_t>; // Queue capacity
namespace
{
constexpr int STOP_SIGN = -1;
constexpr int BASE = 1000;
}
@@ -0,0 +1,156 @@
// 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 "../test_precomp.hpp"
#include <unordered_set>
#include <thread>
#include "executor/last_value.hpp"
namespace opencv_test {
using namespace cv::gapi;
TEST(LastValue, PushPop) {
own::last_written_value<int> v;
for (int i = 0; i < 100; i++) {
v.push(i);
int x = 1;
v.pop(x);
EXPECT_EQ(x, i);
}
}
TEST(LastValue, TryPop) {
own::last_written_value<int> v;
int x = 0;
EXPECT_FALSE(v.try_pop(x));
v.push(1);
EXPECT_TRUE(v.try_pop(x));
EXPECT_EQ(1, x);
}
TEST(LastValue, Clear) {
own::last_written_value<int> v;
v.push(42);
v.clear();
int x = 0;
EXPECT_FALSE(v.try_pop(x));
}
TEST(LastValue, Overwrite) {
own::last_written_value<int> v;
v.push(42);
v.push(0);
int x = -1;
EXPECT_TRUE(v.try_pop(x));
EXPECT_EQ(0, x);
}
// In this test, every writer thread produces its own range of integer
// numbers, writing those to a shared queue.
//
// Every reader thread pops elements from the queue (until -1 is
// reached) and stores those in its own associated set.
//
// Finally, the master thread waits for completion of all other
// threads and verifies that all the necessary data is
// produced/obtained.
namespace {
using StressParam = std::tuple<int // Num writer threads
,int // Num elements per writer
,int>; // Num reader threads
constexpr int STOP_SIGN = -1;
constexpr int BASE = 1000;
}
struct LastValue_: public ::testing::TestWithParam<StressParam> {
using V = own::last_written_value<int>;
using S = std::unordered_set<int>;
static void writer(int base, int writes, V& v) {
for (int i = 0; i < writes; i++) {
if (i % 2) {
std::this_thread::sleep_for(std::chrono::milliseconds{1});
}
v.push(base + i);
}
v.push(STOP_SIGN);
}
static void reader(V& v, S& s) {
int x = 0;
while (true) {
v.pop(x);
if (x == STOP_SIGN) {
// If this thread was lucky enough to read this STOP_SIGN,
// push it back to v to make other possible readers able
// to read it again (note due to the last_written_value
// semantic, those STOP_SIGN could be simply lost i.e.
// overwritten.
v.push(STOP_SIGN);
return;
}
s.insert(x);
}
}
};
TEST_P(LastValue_, Test)
{
int num_writers = 0;
int num_writes = 0;
int num_readers = 0;
std::tie(num_writers, num_writes, num_readers) = GetParam();
CV_Assert(num_writers < 20);
CV_Assert(num_writes < BASE);
V v;
// Start reader threads
std::vector<S> storage(num_readers);
std::vector<std::thread> readers;
for (S& s : storage) {
readers.emplace_back(reader, std::ref(v), std::ref(s));
}
// Start writer threads, also pre-generate reference numbers
S reference;
std::vector<std::thread> writers;
for (int w = 0; w < num_writers; w++) {
writers.emplace_back(writer, w*BASE, num_writes, std::ref(v));
for (int r = 0; r < num_writes; r++) {
reference.insert(w*BASE + r);
}
}
// Wait for completions
for (auto &t : readers) t.join();
for (auto &t : writers) t.join();
// Validate the result. Some values are read, and the values are
// correct (i.e. such values have been written)
std::size_t num_values_read = 0u;
for (const auto &s : storage) {
num_values_read += s.size();
for (auto &x : s) {
EXPECT_TRUE(reference.count(x) > 0);
}
}
// NOTE: Some combinations may end-up in 0 values read
// it is normal, the main thing is that the test shouldn't hang!
EXPECT_LE(0u, num_values_read);
}
INSTANTIATE_TEST_CASE_P(LastValueStress, LastValue_,
Combine( Values(1, 2, 4, 8, 16) // writers
, Values(32, 96, 256) // writes
, Values(1, 2, 10))); // readers
} // namespace opencv_test
@@ -2,11 +2,13 @@
// 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
// Copyright (C) 2019-2020 Intel Corporation
#include "../test_precomp.hpp"
#include <thread> // sleep_for (Delay)
#include <opencv2/gapi/cpu/core.hpp>
#include <opencv2/gapi/cpu/imgproc.hpp>
@@ -18,6 +20,7 @@
#include <opencv2/gapi/ocl/imgproc.hpp>
#include <opencv2/gapi/streaming/cap.hpp>
#include <opencv2/gapi/streaming/desync.hpp>
namespace opencv_test
{
@@ -100,6 +103,16 @@ struct GAPI_Streaming: public ::testing::TestWithParam<KernelPackage> {
}
};
G_API_OP(Delay, <cv::GMat(cv::GMat, int)>, "org.opencv.test.delay") {
static cv::GMatDesc outMeta(const cv::GMatDesc &in, int) { return in; }
};
GAPI_OCV_KERNEL(OCVDelay, Delay) {
static void run(const cv::Mat &in, int ms, cv::Mat &out) {
std::this_thread::sleep_for(std::chrono::milliseconds{ms});
in.copyTo(out);
}
};
} // anonymous namespace
TEST_P(GAPI_Streaming, SmokeTest_ConstInput_GMat)
@@ -794,6 +807,104 @@ TEST(GAPI_Streaming_Types, OutputVector)
EXPECT_LT(0u, num_frames);
}
G_API_OP(DimsChans,
<std::tuple<cv::GArray<int>, cv::GOpaque<int>>(cv::GMat)>,
"test.streaming.dims_chans") {
static std::tuple<cv::GArrayDesc, cv::GOpaqueDesc> outMeta(const cv::GMatDesc &) {
return std::make_tuple(cv::empty_array_desc(),
cv::empty_gopaque_desc());
}
};
GAPI_OCV_KERNEL(OCVDimsChans, DimsChans) {
static void run(const cv::Mat &in, std::vector<int> &ov, int &oi) {
ov = {in.cols, in.rows};
oi = in.channels();
}
};
struct GAPI_Streaming_TemplateTypes: ::testing::Test {
// There was a problem in GStreamingExecutor
// when outputs were formally not used by the graph
// but still should be in place as operation need
// to produce them, and host data type constructors
// were missing for GArray and GOpaque in this case.
// This test tests exactly this.
GAPI_Streaming_TemplateTypes() {
// Prepare everything for the test:
// Graph itself
blur = cv::gapi::boxFilter(in, -1, cv::Size(3,3));
cv::GMat blur_d = cv::gapi::streaming::desync(blur);
std::tie(vec, opq) = DimsChans::on(blur_d);
// Kernel package
pkg = cv::gapi::kernels<OCVDimsChans>();
// Input mat
in_mat = cv::Mat::eye(cv::Size(320,240), CV_8UC3);
}
cv::GMat in;
cv::GMat blur;
cv::GArray<int> vec;
cv::GOpaque<int> opq;
cv::gapi::GKernelPackage pkg;
cv::Mat in_mat;
};
TEST_F(GAPI_Streaming_TemplateTypes, UnusedVectorIsOK)
{
// Declare graph without listing vec as output
auto sc = cv::GComputation(cv::GIn(in), cv::GOut(blur, opq))
.compileStreaming(cv::compile_args(pkg));
sc.setSource(cv::gin(in_mat));
sc.start();
cv::optional<cv::Mat> out_mat;
cv::optional<int> out_int;
int counter = 0;
while (sc.pull(cv::gout(out_mat, out_int))) {
if (counter++ == 10) {
// Stop the test after 10 iterations
sc.stop();
break;
}
GAPI_Assert(out_mat || out_int);
if (out_int) {
EXPECT_EQ( 3, out_int.value());
}
}
}
TEST_F(GAPI_Streaming_TemplateTypes, UnusedOpaqueIsOK)
{
// Declare graph without listing opq as output
auto sc = cv::GComputation(cv::GIn(in), cv::GOut(blur, vec))
.compileStreaming(cv::compile_args(pkg));
sc.setSource(cv::gin(in_mat));
sc.start();
cv::optional<cv::Mat> out_mat;
cv::optional<std::vector<int> > out_vec;
int counter = 0;
while (sc.pull(cv::gout(out_mat, out_vec))) {
if (counter++ == 10) {
// Stop the test after 10 iterations
sc.stop();
break;
}
GAPI_Assert(out_mat || out_vec);
if (out_vec) {
EXPECT_EQ(320, out_vec.value()[0]);
EXPECT_EQ(240, out_vec.value()[1]);
}
}
}
struct GAPI_Streaming_Unit: public ::testing::Test {
cv::Mat m;
@@ -882,7 +993,7 @@ TEST_F(GAPI_Streaming_Unit, StartStopStart_NoSetSource)
EXPECT_NO_THROW(sc.setSource(cv::gin(m, m)));
EXPECT_NO_THROW(sc.start());
EXPECT_NO_THROW(sc.stop());
EXPECT_ANY_THROW(sc.start()); // Should fails since setSource was not called
EXPECT_ANY_THROW(sc.start()); // Should fail since setSource was not called
}
TEST_F(GAPI_Streaming_Unit, StartStopStress_Const)
@@ -1018,4 +1129,380 @@ TEST(Streaming, Python_Pull_Overload)
EXPECT_FALSE(ccomp.running());
}
TEST(GAPI_Streaming_Desync, SmokeTest_Regular)
{
cv::GMat in;
cv::GMat tmp1 = cv::gapi::boxFilter(in, -1, cv::Size(3,3));
cv::GMat out1 = cv::gapi::Canny(tmp1, 32, 128, 3);
// FIXME: Unary desync should not require tie!
cv::GMat tmp2 = cv::gapi::streaming::desync(tmp1);
cv::GMat out2 = tmp2 / cv::gapi::Sobel(tmp2, CV_8U, 1, 1);;
cv::Mat test_in = cv::Mat::eye(cv::Size(32,32), CV_8UC3);
cv::Mat test_out1, test_out2;
cv::GComputation(cv::GIn(in), cv::GOut(out1, out2))
.apply(cv::gin(test_in), cv::gout(test_out1, test_out2));
}
TEST(GAPI_Streaming_Desync, SmokeTest_Streaming)
{
initTestDataPath();
cv::GMat in;
cv::GMat tmp1 = cv::gapi::boxFilter(in, -1, cv::Size(3,3));
cv::GMat out1 = cv::gapi::Canny(tmp1, 32, 128, 3);
cv::GMat tmp2 = cv::gapi::streaming::desync(tmp1);
cv::GMat out2 = Delay::on(tmp2,10) / cv::gapi::Sobel(tmp2, CV_8U, 1, 1);
auto sc = cv::GComputation(cv::GIn(in), cv::GOut(out1, out2))
.compileStreaming(cv::compile_args(cv::gapi::kernels<OCVDelay>()));
auto sc_file = findDataFile("cv/video/768x576.avi");
auto sc_src = gapi::wip::make_src<cv::gapi::wip::GCaptureSource>(sc_file);
sc.setSource(cv::gin(sc_src));
sc.start();
std::size_t out1_hits = 0u;
std::size_t out2_hits = 0u;
cv::optional<cv::Mat> test_out1, test_out2;
while (sc.pull(cv::gout(test_out1, test_out2))) {
GAPI_Assert(test_out1 || test_out2);
if (test_out1) out1_hits++;
if (test_out2) out2_hits++;
}
EXPECT_EQ(100u, out1_hits); // out1 must be available for all frames
EXPECT_LE(out2_hits, out1_hits); // out2 must appear less times than out1
std::cout << "Got " << out1_hits << " out1's and " << out2_hits << " out2's" << std::endl;
}
TEST(GAPI_Streaming_Desync, SmokeTest_Streaming_TwoParts)
{
initTestDataPath();
cv::GMat in;
cv::GMat tmp1 = cv::gapi::boxFilter(in, -1, cv::Size(3,3));
cv::GMat out1 = cv::gapi::Canny(tmp1, 32, 128, 3);
// Desynchronized path 1
cv::GMat tmp2 = cv::gapi::streaming::desync(tmp1);
cv::GMat out2 = tmp2 / cv::gapi::Sobel(tmp2, CV_8U, 1, 1);
// Desynchronized path 2
cv::GMat tmp3 = cv::gapi::streaming::desync(tmp1);
cv::GMat out3 = 0.5*tmp3 + 0.5*cv::gapi::medianBlur(tmp3, 7);
// The code should compile and execute well (desynchronized parts don't cross)
auto sc = cv::GComputation(cv::GIn(in), cv::GOut(out1, out2, out3))
.compileStreaming();
auto sc_file = findDataFile("cv/video/768x576.avi");
auto sc_src = gapi::wip::make_src<cv::gapi::wip::GCaptureSource>(sc_file);
sc.setSource(cv::gin(sc_src));
sc.start();
std::size_t test_frames = 0u;
cv::optional<cv::Mat> test_out1, test_out2, test_out3;
while (sc.pull(cv::gout(test_out1, test_out2, test_out3))) {
GAPI_Assert(test_out1 || test_out2 || test_out3);
if (test_out1) {
// count frames only for synchronized output
test_frames++;
}
}
EXPECT_EQ(100u, test_frames);
}
TEST(GAPI_Streaming_Desync, Negative_NestedDesync_Tier0)
{
cv::GMat in;
cv::GMat tmp1 = cv::gapi::boxFilter(in, -1, cv::Size(3,3));
// Desynchronized path 1
cv::GMat tmp2 = cv::gapi::streaming::desync(tmp1);
cv::GMat out1 = cv::gapi::medianBlur(tmp2, 3);
// Desynchronized path 2, nested from 1 (directly from desync)
cv::GMat tmp3 = cv::gapi::streaming::desync(tmp2);
cv::GMat out2 = 0.5*tmp3;
// This shouldn't compile
EXPECT_ANY_THROW(cv::GComputation(cv::GIn(in), cv::GOut(out1, out2))
.compileStreaming());
}
TEST(GAPI_Streaming_Desync, Negative_NestedDesync_Tier1)
{
cv::GMat in;
cv::GMat tmp1 = cv::gapi::boxFilter(in, -1, cv::Size(3,3));
// Desynchronized path 1
cv::GMat tmp2 = cv::gapi::streaming::desync(tmp1);
cv::GMat out1 = cv::gapi::medianBlur(tmp2, 3);
// Desynchronized path 2, nested from 1 (indirectly from desync)
cv::GMat tmp3 = cv::gapi::streaming::desync(out1);
cv::GMat out2 = 0.5*tmp3;
// This shouldn't compile
EXPECT_ANY_THROW(cv::GComputation(cv::GIn(in), cv::GOut(out1, out2))
.compileStreaming());
}
TEST(GAPI_Streaming_Desync, Negative_CrossMainPart_Tier0)
{
cv::GMat in;
cv::GMat tmp1 = cv::gapi::boxFilter(in, -1, cv::Size(3,3));
// Desynchronized path: depends on both tmp1 and tmp2
cv::GMat tmp2 = cv::gapi::streaming::desync(tmp1);
cv::GMat out1 = 0.5*tmp1 + 0.5*tmp2;
// This shouldn't compile
EXPECT_ANY_THROW(cv::GComputation(in, out1).compileStreaming());
}
TEST(GAPI_Streaming_Desync, Negative_CrossMainPart_Tier1)
{
cv::GMat in;
cv::GMat tmp1 = cv::gapi::boxFilter(in, -1, cv::Size(3,3));
// Desynchronized path: depends on both tmp1 and tmp2
cv::GMat tmp2 = cv::gapi::streaming::desync(tmp1);
cv::GMat out1 = 0.5*tmp1 + 0.5*cv::gapi::medianBlur(tmp2, 3);
// This shouldn't compile
EXPECT_ANY_THROW(cv::GComputation(in, out1).compileStreaming());
}
TEST(GAPI_Streaming_Desync, Negative_CrossOtherDesync_Tier0)
{
cv::GMat in;
cv::GMat tmp1 = cv::gapi::boxFilter(in, -1, cv::Size(3,3));
// Desynchronized path 1
cv::GMat tmp2 = cv::gapi::streaming::desync(tmp1);
cv::GMat out1 = 0.5*tmp2;
// Desynchronized path 2 (depends on 1)
cv::GMat tmp3 = cv::gapi::streaming::desync(tmp1);
cv::GMat out2 = 0.5*tmp3 + tmp2;
// This shouldn't compile
EXPECT_ANY_THROW(cv::GComputation(cv::GIn(in), cv::GOut(out1, out2))
.compileStreaming());
}
TEST(GAPI_Streaming_Desync, Negative_CrossOtherDesync_Tier1)
{
cv::GMat in;
cv::GMat tmp1 = cv::gapi::boxFilter(in, -1, cv::Size(3,3));
// Desynchronized path 1
cv::GMat tmp2 = cv::gapi::streaming::desync(tmp1);
cv::GMat out1 = 0.5*tmp2;
// Desynchronized path 2 (depends on 1)
cv::GMat tmp3 = cv::gapi::streaming::desync(tmp1);
cv::GMat out2 = 0.5*cv::gapi::medianBlur(tmp3,3) + 1.0*tmp2;
// This shouldn't compile
EXPECT_ANY_THROW(cv::GComputation(cv::GIn(in), cv::GOut(out1, out2))
.compileStreaming());
}
TEST(GAPI_Streaming_Desync, Negative_SynchronizedPull)
{
initTestDataPath();
cv::GMat in;
cv::GMat out1 = cv::gapi::boxFilter(in, -1, cv::Size(3,3));
cv::GMat tmp1 = cv::gapi::streaming::desync(out1);
cv::GMat out2 = 0.5*tmp1;
auto sc = cv::GComputation(cv::GIn(in), cv::GOut(out1, out2))
.compileStreaming();
auto sc_file = findDataFile("cv/video/768x576.avi");
auto sc_src = gapi::wip::make_src<cv::gapi::wip::GCaptureSource>(sc_file);
sc.setSource(cv::gin(sc_src));
sc.start();
cv::Mat o1, o2;
EXPECT_ANY_THROW(sc.pull(cv::gout(o1, o2)));
}
TEST(GAPI_Streaming_Desync, UseSpecialPull)
{
initTestDataPath();
cv::GMat in;
cv::GMat out1 = cv::gapi::boxFilter(in, -1, cv::Size(3,3));
cv::GMat tmp1 = cv::gapi::streaming::desync(out1);
cv::GMat out2 = 0.5*tmp1;
auto sc = cv::GComputation(cv::GIn(in), cv::GOut(out1, out2))
.compileStreaming();
auto sc_file = findDataFile("cv/video/768x576.avi");
auto sc_src = gapi::wip::make_src<cv::gapi::wip::GCaptureSource>(sc_file);
sc.setSource(cv::gin(sc_src));
sc.start();
cv::optional<cv::Mat> o1, o2;
std::size_t num_frames = 0u;
while (sc.pull(cv::gout(o1, o2))) {
if (o1) num_frames++;
}
EXPECT_EQ(100u, num_frames);
}
G_API_OP(ProduceVector, <cv::GArray<int>(cv::GMat)>, "test.desync.vector") {
static cv::GArrayDesc outMeta(const cv::GMatDesc &) {
return cv::empty_array_desc();
}
};
G_API_OP(ProduceOpaque, <cv::GOpaque<int>(cv::GMat)>, "test.desync.opaque") {
static cv::GOpaqueDesc outMeta(const cv::GMatDesc &) {
return cv::empty_gopaque_desc();
}
};
GAPI_OCV_KERNEL(OCVVector, ProduceVector) {
static void run(const cv::Mat& in, std::vector<int> &out) {
out = {in.cols, in.rows};
}
};
GAPI_OCV_KERNEL(OCVOpaque, ProduceOpaque) {
static void run(const cv::Mat &in, int &v) {
v = in.channels();
}
};
namespace {
cv::GStreamingCompiled desyncTestObject() {
cv::GMat in;
cv::GMat blur = cv::gapi::boxFilter(in, -1, cv::Size(3,3));
cv::GMat blur_d = cv::gapi::copy(cv::gapi::streaming::desync(blur));
cv::GMat d1 = Delay::on(blur_d, 10);
cv::GMat d2 = Delay::on(blur_d, 30);
cv::GArray<int> vec = ProduceVector::on(d1);
cv::GOpaque<int> opq = ProduceOpaque::on(d2);
auto pkg = cv::gapi::kernels<OCVDelay, OCVVector, OCVOpaque>();
return cv::GComputation(cv::GIn(in), cv::GOut(blur, vec, opq))
.compileStreaming(cv::compile_args(pkg));
}
} // anonymous namespace
TEST(GAPI_Streaming_Desync, MultipleDesyncOutputs_1) {
auto sc = desyncTestObject();
const cv::Mat in_mat = cv::Mat::eye(cv::Size(320,240), CV_8UC3);
sc.setSource(cv::gin(in_mat));
sc.start();
cv::optional<cv::Mat> out_mat;
cv::optional<std::vector<int> > out_vec;
cv::optional<int> out_int;
int counter = 0;
while (sc.pull(cv::gout(out_mat, out_vec, out_int))) {
if (counter++ == 1000) {
// Stop the test after 1000 iterations
sc.stop();
break;
}
GAPI_Assert(out_mat || out_vec || out_int);
// out_vec and out_int are on the same desynchronized path
// they MUST arrive together. If one is available, the other
// also must be available.
if (out_vec) { ASSERT_TRUE(out_int.has_value()); }
if (out_int) { ASSERT_TRUE(out_vec.has_value()); }
if (out_vec || out_int) {
EXPECT_EQ(320, out_vec.value()[0]);
EXPECT_EQ(240, out_vec.value()[1]);
EXPECT_EQ( 3, out_int.value());
}
}
}
TEST(GAPI_Streaming_Desync, StartStop_Stress) {
auto sc = desyncTestObject();
const cv::Mat in_mat = cv::Mat::eye(cv::Size(320,240), CV_8UC3);
cv::optional<cv::Mat> out_mat;
cv::optional<std::vector<int> > out_vec;
cv::optional<int> out_int;
for (int i = 0; i < 10; i++) {
sc.setSource(cv::gin(in_mat));
sc.start();
int counter = 0;
while (counter++ < 100) {
sc.pull(cv::gout(out_mat, out_vec, out_int));
GAPI_Assert(out_mat || out_vec || out_int);
if (out_vec) { ASSERT_TRUE(out_int.has_value()); }
if (out_int) { ASSERT_TRUE(out_vec.has_value()); }
}
sc.stop();
}
}
GAPI_FLUID_KERNEL(FluidCopy, cv::gapi::core::GCopy, false) {
static const int Window = 1;
static void run(const cv::gapi::fluid::View &in,
cv::gapi::fluid::Buffer &out) {
const uint8_t *in_ptr = in.InLineB(0);
uint8_t *out_ptr = out.OutLineB(0);
const auto in_type = CV_MAKETYPE(in.meta().depth, in.meta().chan);
const auto out_type = CV_MAKETYPE(out.meta().depth, out.meta().chan);
GAPI_Assert(in_type == out_type);
std::copy_n(in_ptr, in.length()*CV_ELEM_SIZE(in_type), out_ptr);
}
};
TEST(GAPI_Streaming_Desync, DesyncObjectConsumedByTwoIslandsViaSeparateDesync) {
// See comment in the implementation of cv::gapi::streaming::desync (.cpp)
cv::GMat in;
cv::GMat tmp = cv::gapi::boxFilter(in, -1, cv::Size(3,3));
cv::GMat tmp1 = cv::gapi::streaming::desync(tmp);
cv::GMat out1 = cv::gapi::copy(tmp1); // ran via Fluid backend
cv::GMat tmp2 = cv::gapi::streaming::desync(tmp);
cv::GMat out2 = tmp2 * 0.5; // ran via OCV backend
auto c = cv::GComputation(cv::GIn(in), cv::GOut(out1, out2));
auto p = cv::gapi::kernels<FluidCopy>();
EXPECT_NO_THROW(c.compileStreaming(cv::compile_args(p)));
}
TEST(GAPI_Streaming_Desync, DesyncObjectConsumedByTwoIslandsViaSameDesync) {
// See comment in the implementation of cv::gapi::streaming::desync (.cpp)
cv::GMat in;
cv::GMat tmp = cv::gapi::boxFilter(in, -1, cv::Size(3,3));
cv::GMat tmp1 = cv::gapi::streaming::desync(tmp);
cv::GMat out1 = cv::gapi::copy(tmp1); // ran via Fluid backend
cv::GMat out2 = out1 - 0.5*tmp1; // ran via OCV backend
auto c = cv::GComputation(cv::GIn(in), cv::GOut(out1, out2));
auto p = cv::gapi::kernels<FluidCopy>();
EXPECT_NO_THROW(c.compileStreaming(cv::compile_args(p)));
}
} // namespace opencv_test