提交 b083c20e 编写于 作者: A AsyaPronina

Enable stateful kernels in G-API OCV Backend

上级 c3e8a82c
......@@ -17,6 +17,7 @@
#include <opencv2/gapi/gcommon.hpp>
#include <opencv2/gapi/gkernel.hpp>
#include <opencv2/gapi/garg.hpp>
#include <opencv2/gapi/gmetaarg.hpp>
#include <opencv2/gapi/util/compiler_hints.hpp> //suppress_unused_warning
#include <opencv2/gapi/util/util.hpp>
......@@ -109,11 +110,17 @@ public:
return outOpaqueRef(output).wref<T>();
}
GArg state()
{
return m_state;
}
protected:
detail::VectorRef& outVecRef(int output);
detail::OpaqueRef& outOpaqueRef(int output);
std::vector<GArg> m_args;
GArg m_state;
//FIXME: avoid conversion of arguments from internal representation to OpenCV one on each call
//to OCV kernel. (This can be achieved by a two single time conversions in GCPUExecutable::run,
......@@ -127,16 +134,18 @@ protected:
class GAPI_EXPORTS GCPUKernel
{
public:
// This function is kernel's execution entry point (does the processing work)
using F = std::function<void(GCPUContext &)>;
// This function is a kernel's execution entry point (does the processing work)
using RunF = std::function<void(GCPUContext &)>;
// This function is a stateful kernel's setup routine (configures state)
using SetupF = std::function<void(const GMetaArgs &, const GArgs &, GArg &)>;
GCPUKernel();
explicit GCPUKernel(const F& f);
GCPUKernel(const RunF& runF, const SetupF& setupF = nullptr);
void apply(GCPUContext &ctx);
RunF m_runF = nullptr;
SetupF m_setupF = nullptr;
protected:
F m_f;
bool m_isStateful = false;
};
// FIXME: This is an ugly ad-hoc implementation. TODO: refactor
......@@ -269,12 +278,38 @@ template<typename U> struct get_out<cv::GOpaque<U>>
}
};
template<typename, typename>
struct OCVSetupHelper;
template<typename Impl, typename... Ins>
struct OCVSetupHelper<Impl, std::tuple<Ins...>>
{
template<int... IIs>
static void setup_impl(const GMetaArgs &metaArgs, const GArgs &args, GArg &state,
detail::Seq<IIs...>)
{
// TODO: unique_ptr <-> shared_ptr conversion ?
// To check: Conversion is possible only if the state which should be passed to
// 'setup' user callback isn't required to have previous value
std::shared_ptr<typename Impl::State> stPtr;
Impl::setup(detail::get_in_meta<Ins>(metaArgs, args, IIs)..., stPtr);
state = GArg(stPtr);
}
static void setup(const GMetaArgs &metaArgs, const GArgs &args, GArg& state)
{
setup_impl(metaArgs, args, state,
typename detail::MkSeq<sizeof...(Ins)>::type());
}
};
// OCVCallHelper is a helper class to call stateless OCV kernels and OCV kernel functors.
template<typename, typename, typename>
struct OCVCallHelper;
// FIXME: probably can be simplified with std::apply or analogue.
template<typename Impl, typename... Ins, typename... Outs>
struct OCVCallHelper<Impl, std::tuple<Ins...>, std::tuple<Outs...> >
struct OCVCallHelper<Impl, std::tuple<Ins...>, std::tuple<Outs...>>
{
template<typename... Inputs>
struct call_and_postprocess
......@@ -302,19 +337,16 @@ struct OCVCallHelper<Impl, std::tuple<Ins...>, std::tuple<Outs...> >
//by comparing it's state (data ptr) before and after the call.
//This is done by converting each output Mat into tracked_cv_mat object, and binding
//them to parameters of ad-hoc function
//Convert own::Scalar to cv::Scalar before call kernel and run kernel
//convert cv::Scalar to own::Scalar after call kernel and write back results
call_and_postprocess<decltype(get_in<Ins>::get(ctx, IIs))...>
::call(get_in<Ins>::get(ctx, IIs)...,
get_out<Outs>::get(ctx, OIs)...);
::call(get_in<Ins>::get(ctx, IIs)..., get_out<Outs>::get(ctx, OIs)...);
}
template<int... IIs, int... OIs>
static void call_impl(cv::GCPUContext &ctx, Impl& impl, detail::Seq<IIs...>, detail::Seq<OIs...>)
static void call_impl(cv::GCPUContext &ctx, Impl& impl,
detail::Seq<IIs...>, detail::Seq<OIs...>)
{
call_and_postprocess<decltype(cv::detail::get_in<Ins>::get(ctx, IIs))...>
::call(impl, cv::detail::get_in<Ins>::get(ctx, IIs)...,
cv::detail::get_out<Outs>::get(ctx, OIs)...);
call_and_postprocess<decltype(get_in<Ins>::get(ctx, IIs))...>
::call(impl, get_in<Ins>::get(ctx, IIs)..., get_out<Outs>::get(ctx, OIs)...);
}
static void call(GCPUContext &ctx)
......@@ -335,23 +367,78 @@ struct OCVCallHelper<Impl, std::tuple<Ins...>, std::tuple<Outs...> >
}
};
// OCVStCallHelper is a helper class to call stateful OCV kernels.
template<typename, typename, typename>
struct OCVStCallHelper;
template<typename Impl, typename... Ins, typename... Outs>
struct OCVStCallHelper<Impl, std::tuple<Ins...>, std::tuple<Outs...>> :
OCVCallHelper<Impl, std::tuple<Ins...>, std::tuple<Outs...>>
{
template<typename... Inputs>
struct call_and_postprocess
{
template<typename... Outputs>
static void call(typename Impl::State& st, Inputs&&... ins, Outputs&&... outs)
{
Impl::run(std::forward<Inputs>(ins)..., outs..., st);
postprocess(outs...);
}
};
template<int... IIs, int... OIs>
static void call_impl(GCPUContext &ctx, detail::Seq<IIs...>, detail::Seq<OIs...>)
{
auto& st = *ctx.state().get<std::shared_ptr<typename Impl::State>>();
call_and_postprocess<decltype(get_in<Ins>::get(ctx, IIs))...>
::call(st, get_in<Ins>::get(ctx, IIs)..., get_out<Outs>::get(ctx, OIs)...);
}
static void call(GCPUContext &ctx)
{
call_impl(ctx,
typename detail::MkSeq<sizeof...(Ins)>::type(),
typename detail::MkSeq<sizeof...(Outs)>::type());
}
};
} // namespace detail
template<class Impl, class K>
class GCPUKernelImpl: public cv::detail::OCVCallHelper<Impl, typename K::InArgs, typename K::OutArgs>,
public cv::detail::KernelTag
class GCPUKernelImpl: public cv::detail::KernelTag
{
using CallHelper = detail::OCVCallHelper<Impl, typename K::InArgs, typename K::OutArgs>;
public:
using API = K;
static cv::gapi::GBackend backend() { return cv::gapi::cpu::backend(); }
static cv::GCPUKernel kernel() { return GCPUKernel(&CallHelper::call); }
};
template<class Impl, class K, class S>
class GCPUStKernelImpl: public cv::detail::KernelTag
{
using P = detail::OCVCallHelper<Impl, typename K::InArgs, typename K::OutArgs>;
using StSetupHelper = detail::OCVSetupHelper<Impl, typename K::InArgs>;
using StCallHelper = detail::OCVStCallHelper<Impl, typename K::InArgs, typename K::OutArgs>;
public:
using API = K;
using State = S;
static cv::gapi::GBackend backend() { return cv::gapi::cpu::backend(); }
static cv::GCPUKernel kernel() { return GCPUKernel(&P::call); }
static cv::gapi::GBackend backend() { return cv::gapi::cpu::backend(); }
static cv::GCPUKernel kernel() { return GCPUKernel(&StCallHelper::call,
&StSetupHelper::setup); }
};
#define GAPI_OCV_KERNEL(Name, API) struct Name: public cv::GCPUKernelImpl<Name, API>
// TODO: Reuse Anatoliy's logic for support of types with commas in macro.
// Retrieve the common part from Anatoliy's logic to the separate place.
#define GAPI_OCV_KERNEL_ST(Name, API, State) \
struct Name:public cv::GCPUStKernelImpl<Name, API, State> \
class gapi::cpu::GOCVFunctor : public gapi::GFunctor
{
public:
......
......@@ -208,6 +208,19 @@ public:
// FIXME: Why it requires compile args?
void reshape(const GMetaArgs& inMetas, const GCompileArgs& args);
/**
* @brief Prepare inner kernels states for a new video-stream.
*
* GCompiled objects may be used to process video streams frame by frame.
* In this case, a GCompiled is called on every image frame individually.
* Starting OpenCV 4.4, some kernels in the graph may have their internal
* states (see GAPI_OCV_KERNEL_ST for the OpenCV backend).
* In this case, if user starts processing another video stream with
* this GCompiled, this method needs to be called to let kernels re-initialize
* their internal states to a new video stream.
*/
void prepareForNewStream();
protected:
/// @private
std::shared_ptr<Priv> m_priv;
......
......@@ -33,13 +33,13 @@
//
// If not, we need to introduce that!
using GCPUModel = ade::TypedGraph
< cv::gimpl::Unit
< cv::gimpl::CPUUnit
, cv::gimpl::Protocol
>;
// FIXME: Same issue with Typed and ConstTyped
using GConstGCPUModel = ade::ConstTypedGraph
< cv::gimpl::Unit
< cv::gimpl::CPUUnit
, cv::gimpl::Protocol
>;
......@@ -53,7 +53,7 @@ namespace
{
GCPUModel gm(graph);
auto cpu_impl = cv::util::any_cast<cv::GCPUKernel>(impl.opaque);
gm.metadata(op_node).set(cv::gimpl::Unit{cpu_impl});
gm.metadata(op_node).set(cv::gimpl::CPUUnit{cpu_impl});
}
virtual EPtr compile(const ade::Graph &graph,
......@@ -78,11 +78,23 @@ cv::gimpl::GCPUExecutable::GCPUExecutable(const ade::Graph &g,
{
// Convert list of operations (which is topologically sorted already)
// into an execution script.
GConstGCPUModel gcm(m_g);
for (auto &nh : nodes)
{
switch (m_gm.metadata(nh).get<NodeType>().t)
{
case NodeType::OP: m_script.push_back({nh, GModel::collectOutputMeta(m_gm, nh)}); break;
case NodeType::OP:
{
m_script.push_back({nh, GModel::collectOutputMeta(m_gm, nh)});
// If kernel is stateful then prepare storage for its state.
GCPUKernel k = gcm.metadata(nh).get<CPUUnit>().k;
if (k.m_isStateful)
{
m_nodesToStates[nh] = GArg{ };
}
break;
}
case NodeType::DATA:
{
m_dataNodes.push_back(nh);
......@@ -104,6 +116,9 @@ cv::gimpl::GCPUExecutable::GCPUExecutable(const ade::Graph &g,
default: util::throw_error(std::logic_error("Unsupported NodeType type"));
}
}
// For each stateful kernel call 'setup' user callback to initialize state.
setupKernelStates();
}
// FIXME: Document what it does
......@@ -140,6 +155,26 @@ cv::GArg cv::gimpl::GCPUExecutable::packArg(const GArg &arg)
}
}
void cv::gimpl::GCPUExecutable::setupKernelStates()
{
GConstGCPUModel gcm(m_g);
for (auto& nodeToState : m_nodesToStates)
{
auto& kernelNode = nodeToState.first;
auto& kernelState = nodeToState.second;
const GCPUKernel& kernel = gcm.metadata(kernelNode).get<CPUUnit>().k;
kernel.m_setupF(GModel::collectInputMeta(m_gm, kernelNode),
m_gm.metadata(kernelNode).get<Op>().args,
kernelState);
}
}
void cv::gimpl::GCPUExecutable::handleNewStream()
{
m_newStreamStarted = true;
}
void cv::gimpl::GCPUExecutable::run(std::vector<InObj> &&input_objs,
std::vector<OutObj> &&output_objs)
{
......@@ -167,6 +202,14 @@ void cv::gimpl::GCPUExecutable::run(std::vector<InObj> &&input_objs,
}
}
// In case if new video-stream happens - for each stateful kernel
// call 'setup' user callback to re-initialize state.
if (m_newStreamStarted)
{
setupKernelStates();
m_newStreamStarted = false;
}
// OpenCV backend execution is not a rocket science at all.
// Simply invoke our kernels in the proper order.
GConstGCPUModel gcm(m_g);
......@@ -176,7 +219,7 @@ void cv::gimpl::GCPUExecutable::run(std::vector<InObj> &&input_objs,
// Obtain our real execution unit
// TODO: Should kernels be copyable?
GCPUKernel k = gcm.metadata(op_info.nh).get<Unit>().k;
GCPUKernel k = gcm.metadata(op_info.nh).get<CPUUnit>().k;
// Initialize kernel's execution context:
// - Input parameters
......@@ -185,8 +228,8 @@ void cv::gimpl::GCPUExecutable::run(std::vector<InObj> &&input_objs,
using namespace std::placeholders;
ade::util::transform(op.args,
std::back_inserter(context.m_args),
std::bind(&GCPUExecutable::packArg, this, _1));
std::back_inserter(context.m_args),
std::bind(&GCPUExecutable::packArg, this, _1));
// - Output parameters.
// FIXME: pre-allocate internal Mats, etc, according to the known meta
......@@ -198,8 +241,14 @@ void cv::gimpl::GCPUExecutable::run(std::vector<InObj> &&input_objs,
context.m_results[out_port] = magazine::getObjPtr(m_res, out_desc);
}
// For stateful kernel add state to its execution context
if (k.m_isStateful)
{
context.m_state = m_nodesToStates.at(op_info.nh);
}
// Now trigger the executable unit
k.apply(context);
k.m_runF(context);
//As Kernels are forbidden to allocate memory for (Mat) outputs,
//this code seems redundant, at least for Mats
......
......@@ -23,7 +23,7 @@
namespace cv { namespace gimpl {
struct Unit
struct CPUUnit
{
static const char *name() { return "HostKernel"; }
GCPUKernel k;
......@@ -48,6 +48,13 @@ class GCPUExecutable final: public GIslandExecutable
// Actual data of all resources in graph (both internal and external)
Mag m_res;
GArg packArg(const GArg &arg);
void setupKernelStates();
// TODO: Check that it is thread-safe
std::unordered_map<ade::NodeHandle, GArg,
ade::HandleHasher<ade::Node>> m_nodesToStates;
bool m_newStreamStarted = false;
public:
GCPUExecutable(const ade::Graph &graph,
......@@ -62,6 +69,8 @@ public:
util::throw_error(std::logic_error("GCPUExecutable::reshape() should never be called"));
}
virtual void handleNewStream() override;
virtual void run(std::vector<InObj> &&input_objs,
std::vector<OutObj> &&output_objs) override;
};
......
......@@ -45,13 +45,7 @@ cv::GCPUKernel::GCPUKernel()
{
}
cv::GCPUKernel::GCPUKernel(const GCPUKernel::F &f)
: m_f(f)
cv::GCPUKernel::GCPUKernel(const GCPUKernel::RunF &runF, const GCPUKernel::SetupF &setupF)
: m_runF(runF), m_setupF(setupF), m_isStateful(m_setupF != nullptr)
{
}
void cv::GCPUKernel::apply(GCPUContext &ctx)
{
GAPI_Assert(m_f);
m_f(ctx);
}
......@@ -33,13 +33,13 @@
//
// If not, we need to introduce that!
using GOCLModel = ade::TypedGraph
< cv::gimpl::Unit
< cv::gimpl::OCLUnit
, cv::gimpl::Protocol
>;
// FIXME: Same issue with Typed and ConstTyped
using GConstGOCLModel = ade::ConstTypedGraph
< cv::gimpl::Unit
< cv::gimpl::OCLUnit
, cv::gimpl::Protocol
>;
......@@ -53,7 +53,7 @@ namespace
{
GOCLModel gm(graph);
auto ocl_impl = cv::util::any_cast<cv::GOCLKernel>(impl.opaque);
gm.metadata(op_node).set(cv::gimpl::Unit{ocl_impl});
gm.metadata(op_node).set(cv::gimpl::OCLUnit{ocl_impl});
}
virtual EPtr compile(const ade::Graph &graph,
......@@ -198,7 +198,7 @@ void cv::gimpl::GOCLExecutable::run(std::vector<InObj> &&input_objs,
// Obtain our real execution unit
// TODO: Should kernels be copyable?
GOCLKernel k = gcm.metadata(op_info.nh).get<Unit>().k;
GOCLKernel k = gcm.metadata(op_info.nh).get<OCLUnit>().k;
// Initialize kernel's execution context:
// - Input parameters
......
......@@ -23,7 +23,7 @@
namespace cv { namespace gimpl {
struct Unit
struct OCLUnit
{
static const char *name() { return "OCLKernel"; }
GOCLKernel k;
......
......@@ -93,7 +93,7 @@ void cv::gimpl::render::ocv::GRenderExecutable::run(std::vector<InObj> &&input_
context.m_args.emplace_back(m_ftpr.get());
k.apply(context);
k.m_runF(context);
for (auto &it : output_objs) magazine::writeBack(m_res, it.first, it.second);
}
......
......@@ -72,6 +72,12 @@ void cv::GCompiled::Priv::reshape(const GMetaArgs& inMetas, const GCompileArgs&
m_metas = inMetas;
}
void cv::GCompiled::Priv::prepareForNewStream()
{
GAPI_Assert(m_exec);
m_exec->prepareForNewStream();
}
const cv::gimpl::GModel::Graph& cv::GCompiled::Priv::model() const
{
GAPI_Assert(nullptr != m_exec);
......@@ -155,3 +161,8 @@ void cv::GCompiled::reshape(const GMetaArgs& inMetas, const GCompileArgs& args)
{
m_priv->reshape(inMetas, args);
}
void cv::GCompiled::prepareForNewStream()
{
m_priv->prepareForNewStream();
}
......@@ -48,6 +48,7 @@ public:
bool canReshape() const;
void reshape(const GMetaArgs& inMetas, const GCompileArgs &args);
void prepareForNewStream();
void run(cv::gimpl::GRuntimeArgs &&args);
const GMetaArgs& metas() const;
......
......@@ -265,3 +265,11 @@ void cv::gimpl::GExecutor::reshape(const GMetaArgs& inMetas, const GCompileArgs&
passes::inferMeta(ctx, true);
m_ops[0].isl_exec->reshape(g, args);
}
void cv::gimpl::GExecutor::prepareForNewStream()
{
for (auto &op : m_ops)
{
op.isl_exec->handleNewStream();
}
}
......@@ -91,6 +91,8 @@ public:
bool canReshape() const;
void reshape(const GMetaArgs& inMetas, const GCompileArgs& args);
void prepareForNewStream();
const GModel::Graph& model() const; // FIXME: make it ConstGraph?
};
......
......@@ -800,6 +800,7 @@ void cv::gimpl::GStreamingExecutor::setSource(GRunArgs &&ins)
}
}
};
bool islandsRecompiled = false;
const auto new_meta = cv::descr_of(ins); // 0
if (gm.metadata().contains<OriginalInputMeta>()) // (1)
{
......@@ -821,6 +822,8 @@ void cv::gimpl::GStreamingExecutor::setSource(GRunArgs &&ins)
}
update_int_metas(); // (7)
m_reshapable = util::make_optional(is_reshapable);
islandsRecompiled = true;
}
else // (8)
{
......@@ -929,7 +932,15 @@ void cv::gimpl::GStreamingExecutor::setSource(GRunArgs &&ins)
for (auto &&out_eh : op.nh->outNodes()) {
out_queues.push_back(reader_queues(*m_island_graph, out_eh));
}
op.isl_exec->handleNewStream();
// If Island Executable is recompiled, all its stuff including internal kernel states
// are recreated and re-initialized automatically.
// But if not, we should notify Island Executable about new started stream to let it update
// its internal variables.
if (!islandsRecompiled)
{
op.isl_exec->handleNewStream();
}
m_threads.emplace_back(islandActorThread,
op.in_objects,
......
// 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_OCV_STATEFUL_KERNEL_TESTS_UTILS_HPP
#define OPENCV_GAPI_OCV_STATEFUL_KERNEL_TESTS_UTILS_HPP
#include "../test_precomp.hpp"
// TODO: Reuse Anatoliy's logic for support of types with commas in macro.
// Retrieve the common part from Anatoliy's logic to the separate place.
#define DEFINE_INITIALIZER(Name, StateType, ...) \
struct Name \
{ \
static StateType value() \
{ \
return __VA_ARGS__; \
} \
} \
namespace opencv_test
{
namespace
{
struct UserStruct
{
UserStruct() = default;
UserStruct(short myShortVal, float myFloatVal):
_myShortVal(myShortVal),
_myFloatVal(myFloatVal) { }
bool operator==(const UserStruct& rhs) const
{
return ((_myShortVal == rhs._myShortVal) &&
(_myFloatVal == rhs._myFloatVal));
}
private:
short _myShortVal;
float _myFloatVal;
};
} // anonymous namespace
} // opencv_test
#endif // OPENCV_GAPI_OCV_STATEFUL_KERNEL_TESTS_UTILS_HPP
// 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 "gapi_ocv_stateful_kernel_test_utils.hpp"
#include <opencv2/gapi/cpu/core.hpp>
#include <opencv2/gapi/streaming/cap.hpp>
namespace opencv_test
{
//TODO: test OT, Background Subtractor, Kalman with 3rd version of API
//----------------------------------------------- Simple tests ------------------------------------------------
namespace
{
inline 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");
GAPI_Assert(testDataPath != nullptr);
cvtest::addDataSearchPath(testDataPath);
initialized = true;
}
#endif // WINRT
}
G_TYPED_KERNEL(GCountCalls, <cv::GOpaque<int>(GMat)>, "org.opencv.test.count_calls")
{
static GOpaqueDesc outMeta(GMatDesc /* in */) { return empty_gopaque_desc(); }
};
GAPI_OCV_KERNEL_ST(GOCVCountCalls, GCountCalls, int)
{
static void setup(const cv::GMatDesc &/* in */, std::shared_ptr<int> &state)
{
state.reset(new int{ });
}
static void run(const cv::Mat &/* in */, int &out, int& state)
{
out = ++state;
}
};
G_TYPED_KERNEL(GIsStateUpToDate, <cv::GOpaque<bool>(GMat)>,
"org.opencv.test.is_state_up-to-date")
{
static GOpaqueDesc outMeta(GMatDesc /* in */) { return empty_gopaque_desc(); }
};
GAPI_OCV_KERNEL_ST(GOCVIsStateUpToDate, GIsStateUpToDate, cv::Size)
{
static void setup(const cv::GMatDesc &in,
std::shared_ptr<cv::Size> &state)
{
state.reset(new cv::Size(in.size));
}
static void run(const cv::Mat &in , bool &out, cv::Size& state)
{
out = in.size() == state;
}
};
G_TYPED_KERNEL(GStInvalidResize, <GMat(GMat,Size,double,double,int)>, "org.opencv.test.st_invalid_resize")
{
static GMatDesc outMeta(GMatDesc in, Size, double, double, int) { return in; }
};
GAPI_OCV_KERNEL_ST(GOCVStInvalidResize, GStInvalidResize, int)
{
static void setup(const cv::GMatDesc, cv::Size, double, double, int,
std::shared_ptr<int> &/* state */)
{ }
static void run(const cv::Mat& in, cv::Size sz, double fx, double fy, int interp,
cv::Mat &out, int& /* state */)
{
cv::resize(in, out, sz, fx, fy, interp);
}
};
};
TEST(StatefulKernel, StateIsMutableInRuntime)
{
constexpr int expectedCallsCount = 10;
cv::Mat dummyIn { 1, 1, CV_8UC1 };
int actualCallsCount = 0;
// Declaration of G-API expression
GMat in;
GOpaque<int> out = GCountCalls::on(in);
cv::GComputation comp(cv::GIn(in), cv::GOut(out));
const auto pkg = cv::gapi::kernels<GOCVCountCalls>();
// Compilation of G-API expression
auto callsCounter = comp.compile(cv::descr_of(dummyIn), cv::compile_args(pkg));
// Simulating video stream: call GCompiled multiple times
for (int i = 0; i < expectedCallsCount; i++)
{
callsCounter(cv::gin(dummyIn), cv::gout(actualCallsCount));
EXPECT_EQ(i + 1, actualCallsCount);
}
// End of "video stream"
EXPECT_EQ(expectedCallsCount, actualCallsCount);
// User asks G-API to prepare for a new stream
callsCounter.prepareForNewStream();
callsCounter(cv::gin(dummyIn), cv::gout(actualCallsCount));
EXPECT_EQ(1, actualCallsCount);
}
TEST(StatefulKernel, StateIsAutoResetForNewStream)
{
initTestDataPath();
cv::GMat in;
GOpaque<bool> out = GIsStateUpToDate::on(in);
cv::GComputation c(cv::GIn(in), cv::GOut(out));
const auto pkg = cv::gapi::kernels<GOCVIsStateUpToDate>();
// Compilation & testing
auto ccomp = c.compileStreaming(cv::compile_args(pkg));
ccomp.setSource(gapi::wip::make_src<cv::gapi::wip::GCaptureSource>
(findDataFile("cv/video/768x576.avi")));
ccomp.start();
EXPECT_TRUE(ccomp.running());
// Process the full video
bool isStateUpToDate = false;
while (ccomp.pull(cv::gout(isStateUpToDate))) {
EXPECT_TRUE(isStateUpToDate);
}
EXPECT_FALSE(ccomp.running());
ccomp.setSource(gapi::wip::make_src<cv::gapi::wip::GCaptureSource>
(findDataFile("cv/video/1920x1080.avi")));
ccomp.start();
EXPECT_TRUE(ccomp.running());
while (ccomp.pull(cv::gout(isStateUpToDate))) {
EXPECT_TRUE(isStateUpToDate);
}
EXPECT_FALSE(ccomp.running());
}
TEST(StatefulKernel, InvalidReallocatingKernel)
{
cv::GMat in, out;
cv::Mat in_mat(500, 500, CV_8UC1), out_mat;
out = GStInvalidResize::on(in, cv::Size(300, 300), 0.0, 0.0, cv::INTER_LINEAR);
const auto pkg = cv::gapi::kernels<GOCVStInvalidResize>();
cv::GComputation comp(cv::GIn(in), cv::GOut(out));
EXPECT_THROW(comp.apply(in_mat, out_mat, cv::compile_args(pkg)), std::logic_error);
}
//-------------------------------------------------------------------------------------------------------------
//------------------------------------------- Typed tests on setup() ------------------------------------------
namespace
{
template<typename Tuple>
struct SetupStateTypedTest : public ::testing::Test
{
using StateT = typename std::tuple_element<0, Tuple>::type;
using SetupT = typename std::tuple_element<1, Tuple>::type;
G_TYPED_KERNEL(GReturnState, <cv::GOpaque<StateT>(GMat)>, "org.opencv.test.return_state")
{
static GOpaqueDesc outMeta(GMatDesc /* in */) { return empty_gopaque_desc(); }
};
GAPI_OCV_KERNEL_ST(GOCVReturnState, GReturnState, StateT)
{
static void setup(const cv::GMatDesc &/* in */, std::shared_ptr<StateT> &state)
{
// Don't use input cv::GMatDesc intentionally
state.reset(new StateT(SetupT::value()));
}
static void run(const cv::Mat &/* in */, StateT &out, StateT& state)
{
out = state;
}
};
};
TYPED_TEST_CASE_P(SetupStateTypedTest);
} // namespace
TYPED_TEST_P(SetupStateTypedTest, ReturnInitializedState)
{
using StateType = typename TestFixture::StateT;
using SetupType = typename TestFixture::SetupT;
cv::Mat dummyIn { 1, 1, CV_8UC1 };
StateType retState { };
GMat in;
auto out = TestFixture::GReturnState::on(in);
cv::GComputation comp(cv::GIn(in), cv::GOut(out));
const auto pkg = cv::gapi::kernels<typename TestFixture::GOCVReturnState>();
comp.apply(cv::gin(dummyIn), cv::gout(retState), cv::compile_args(pkg));
EXPECT_EQ(SetupType::value(), retState);
}
REGISTER_TYPED_TEST_CASE_P(SetupStateTypedTest,
ReturnInitializedState);
DEFINE_INITIALIZER(CharValue, char, 'z');
DEFINE_INITIALIZER(IntValue, int, 7);
DEFINE_INITIALIZER(FloatValue, float, 42.f);
DEFINE_INITIALIZER(UcharPtrValue, uchar*, nullptr);
namespace
{
using Std3IntArray = std::array<int, 3>;
}
DEFINE_INITIALIZER(StdArrayValue, Std3IntArray, { 1, 2, 3 });
DEFINE_INITIALIZER(UserValue, UserStruct, { 5, 7.f });
using TypesToVerify = ::testing::Types<std::tuple<char, CharValue>,
std::tuple<int, IntValue>,
std::tuple<float, FloatValue>,
std::tuple<uchar*, UcharPtrValue>,
std::tuple<std::array<int, 3>, StdArrayValue>,
std::tuple<UserStruct, UserValue>>;
INSTANTIATE_TYPED_TEST_CASE_P(SetupStateTypedInst, SetupStateTypedTest, TypesToVerify);
//-------------------------------------------------------------------------------------------------------------
} // opencv_test
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册