325 lines
11 KiB
C++
325 lines
11 KiB
C++
// 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) 2018-2022 Intel Corporation
|
|
|
|
|
|
#include "../test_precomp.hpp"
|
|
#include "../gapi_mock_kernels.hpp"
|
|
|
|
#include <opencv2/gapi/core.hpp>
|
|
|
|
namespace opencv_test
|
|
{
|
|
|
|
namespace
|
|
{
|
|
|
|
class GMockExecutable final: public cv::gimpl::GIslandExecutable
|
|
{
|
|
virtual inline bool canReshape() const override {
|
|
return m_priv->m_can_reshape;
|
|
}
|
|
|
|
virtual void reshape(ade::Graph&, const GCompileArgs&) override
|
|
{
|
|
m_priv->m_reshape_counter++;
|
|
}
|
|
virtual void handleNewStream() override { }
|
|
virtual void run(std::vector<InObj>&&, std::vector<OutObj>&&) override { }
|
|
virtual bool allocatesOutputs() const override
|
|
{
|
|
return true;
|
|
}
|
|
|
|
virtual cv::RMat allocate(const cv::GMatDesc&) const override
|
|
{
|
|
m_priv->m_allocate_counter++;
|
|
return cv::RMat();
|
|
}
|
|
|
|
// NB: GMockBackendImpl creates new unique_ptr<GMockExecutable>
|
|
// on every compile call. Need to share counters between instances in order
|
|
// to validate it in tests.
|
|
struct Priv
|
|
{
|
|
bool m_can_reshape;
|
|
int m_reshape_counter;
|
|
int m_allocate_counter;
|
|
};
|
|
|
|
std::shared_ptr<Priv> m_priv;
|
|
|
|
public:
|
|
GMockExecutable(bool can_reshape = true)
|
|
: m_priv(new Priv{can_reshape, 0, 0})
|
|
{
|
|
}
|
|
|
|
void setReshape(bool can_reshape) { m_priv->m_can_reshape = can_reshape; }
|
|
|
|
int getReshapeCounter() const { return m_priv->m_reshape_counter; }
|
|
int getAllocateCounter() const { return m_priv->m_allocate_counter; }
|
|
};
|
|
|
|
class GMockBackendImpl final: public cv::gapi::GBackend::Priv
|
|
{
|
|
virtual void unpackKernel(ade::Graph &,
|
|
const ade::NodeHandle &,
|
|
const cv::GKernelImpl &) override { }
|
|
|
|
virtual EPtr compile(const ade::Graph &,
|
|
const cv::GCompileArgs &,
|
|
const std::vector<ade::NodeHandle> &) const override
|
|
{
|
|
++m_compile_counter;
|
|
return EPtr{new GMockExecutable(m_exec)};
|
|
}
|
|
|
|
mutable int m_compile_counter = 0;
|
|
GMockExecutable m_exec;
|
|
|
|
virtual bool controlsMerge() const override {
|
|
return true;
|
|
}
|
|
|
|
virtual bool allowsMerge(const cv::gimpl::GIslandModel::Graph &,
|
|
const ade::NodeHandle &,
|
|
const ade::NodeHandle &,
|
|
const ade::NodeHandle &) const override {
|
|
return false;
|
|
}
|
|
|
|
public:
|
|
GMockBackendImpl(const GMockExecutable& exec) : m_exec(exec) { }
|
|
int getCompileCounter() const { return m_compile_counter; }
|
|
};
|
|
|
|
class GMockFunctor : public gapi::cpu::GOCVFunctor
|
|
{
|
|
public:
|
|
GMockFunctor(cv::gapi::GBackend backend,
|
|
const char* id,
|
|
const Meta &meta,
|
|
const Impl& impl)
|
|
: gapi::cpu::GOCVFunctor(id, meta, impl), m_backend(backend)
|
|
{
|
|
}
|
|
|
|
cv::gapi::GBackend backend() const override { return m_backend; }
|
|
|
|
private:
|
|
cv::gapi::GBackend m_backend;
|
|
};
|
|
|
|
template<typename K, typename Callable>
|
|
GMockFunctor mock_kernel(const cv::gapi::GBackend& backend, Callable c)
|
|
{
|
|
using P = cv::detail::OCVCallHelper<Callable, typename K::InArgs, typename K::OutArgs>;
|
|
return GMockFunctor{ backend
|
|
, K::id()
|
|
, &K::getOutMeta
|
|
, std::bind(&P::callFunctor, std::placeholders::_1, c)
|
|
};
|
|
}
|
|
|
|
void dummyFooImpl(const cv::Mat&, cv::Mat&) { }
|
|
void dummyBarImpl(const cv::Mat&, const cv::Mat&, cv::Mat&) { }
|
|
|
|
struct GExecutorReshapeTest: public ::testing::Test
|
|
{
|
|
GExecutorReshapeTest()
|
|
: comp([](){
|
|
cv::GMat in;
|
|
cv::GMat out = I::Bar::on(I::Foo::on(in), in);
|
|
return cv::GComputation(in, out);
|
|
})
|
|
{
|
|
backend_impl1 = std::make_shared<GMockBackendImpl>(island1);
|
|
backend1 = cv::gapi::GBackend{backend_impl1};
|
|
backend_impl2 = std::make_shared<GMockBackendImpl>(island2);
|
|
backend2 = cv::gapi::GBackend{backend_impl2};
|
|
auto kernel1 = mock_kernel<I::Foo>(backend1, dummyFooImpl);
|
|
auto kernel2 = mock_kernel<I::Bar>(backend2, dummyBarImpl);
|
|
pkg = cv::gapi::kernels(kernel1, kernel2);
|
|
in_mat1 = cv::Mat::eye(32, 32, CV_8UC1);
|
|
in_mat2 = cv::Mat::eye(64, 64, CV_8UC1);
|
|
}
|
|
|
|
cv::GComputation comp;
|
|
GMockExecutable island1;
|
|
std::shared_ptr<GMockBackendImpl> backend_impl1;
|
|
cv::gapi::GBackend backend1;
|
|
GMockExecutable island2;
|
|
std::shared_ptr<GMockBackendImpl> backend_impl2;
|
|
cv::gapi::GBackend backend2;
|
|
cv::GKernelPackage pkg;
|
|
cv::Mat in_mat1, in_mat2, out_mat;
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
// FIXME: avoid code duplication
|
|
// The below graph and config is taken from ComplexIslands test suite
|
|
TEST(GExecutor, SmokeTest)
|
|
{
|
|
cv::GMat in[2];
|
|
cv::GMat tmp[4];
|
|
cv::GScalar scl;
|
|
cv::GMat out[2];
|
|
|
|
tmp[0] = cv::gapi::bitwise_not(cv::gapi::bitwise_not(in[0]));
|
|
tmp[1] = cv::gapi::boxFilter(in[1], -1, cv::Size(3,3));
|
|
tmp[2] = tmp[0] + tmp[1]; // FIXME: handle tmp[2] = tmp[0]+tmp[2] typo
|
|
scl = cv::gapi::sum(tmp[1]);
|
|
tmp[3] = cv::gapi::medianBlur(tmp[1], 3);
|
|
out[0] = tmp[2] + scl;
|
|
out[1] = cv::gapi::boxFilter(tmp[3], -1, cv::Size(3,3));
|
|
|
|
// isl0 #internal1
|
|
// ........................... .........
|
|
// (in1) -> NotNot ->(tmp0) --> Add ---------> (tmp2) --> AddC -------> (out1)
|
|
// :.....................^...: :..^....:
|
|
// : :
|
|
// : :
|
|
// #internal0 : :
|
|
// .....................:......... :
|
|
// (in2) -> Blur -> (tmp1) ----'--> Sum ----> (scl0) ----'
|
|
// :..........:..................: isl1
|
|
// : ..............................
|
|
// `------------> Median -> (tmp3) --> Blur -------> (out2)
|
|
// :............................:
|
|
|
|
cv::gapi::island("isl0", cv::GIn(in[0], tmp[1]), cv::GOut(tmp[2]));
|
|
cv::gapi::island("isl1", cv::GIn(tmp[1]), cv::GOut(out[1]));
|
|
|
|
cv::Mat in_mat1 = cv::Mat::eye(32, 32, CV_8UC1);
|
|
cv::Mat in_mat2 = cv::Mat::eye(32, 32, CV_8UC1);
|
|
cv::Mat out_gapi[2];
|
|
|
|
// Run G-API:
|
|
cv::GComputation(cv::GIn(in[0], in[1]), cv::GOut(out[0], out[1]))
|
|
.apply(cv::gin(in_mat1, in_mat2), cv::gout(out_gapi[0], out_gapi[1]));
|
|
|
|
// Run OpenCV
|
|
cv::Mat out_ocv[2];
|
|
{
|
|
cv::Mat ocv_tmp0;
|
|
cv::Mat ocv_tmp1;
|
|
cv::Mat ocv_tmp2;
|
|
cv::Mat ocv_tmp3;
|
|
cv::Scalar ocv_scl;
|
|
|
|
ocv_tmp0 = in_mat1; // skip !(!)
|
|
cv::boxFilter(in_mat2, ocv_tmp1, -1, cv::Size(3,3));
|
|
ocv_tmp2 = ocv_tmp0 + ocv_tmp1;
|
|
ocv_scl = cv::sum(ocv_tmp1);
|
|
cv::medianBlur(ocv_tmp1, ocv_tmp3, 3);
|
|
out_ocv[0] = ocv_tmp2 + ocv_scl;
|
|
cv::boxFilter(ocv_tmp3, out_ocv[1], -1, cv::Size(3,3));
|
|
}
|
|
|
|
EXPECT_EQ(0, cvtest::norm(out_gapi[0], out_ocv[0], NORM_INF));
|
|
EXPECT_EQ(0, cvtest::norm(out_gapi[1], out_ocv[1], NORM_INF));
|
|
|
|
// FIXME: check that GIslandModel has more than 1 island (e.g. fusion
|
|
// with breakdown worked)
|
|
}
|
|
|
|
TEST_F(GExecutorReshapeTest, ReshapeInsteadOfRecompile)
|
|
{
|
|
// NB: Initial state
|
|
EXPECT_EQ(0, backend_impl1->getCompileCounter());
|
|
EXPECT_EQ(0, backend_impl2->getCompileCounter());
|
|
EXPECT_EQ(0, island1.getReshapeCounter());
|
|
EXPECT_EQ(0, island2.getReshapeCounter());
|
|
|
|
// NB: First compilation.
|
|
comp.apply(cv::gin(in_mat1), cv::gout(out_mat), cv::compile_args(pkg));
|
|
EXPECT_EQ(1, backend_impl1->getCompileCounter());
|
|
EXPECT_EQ(1, backend_impl2->getCompileCounter());
|
|
EXPECT_EQ(0, island1.getReshapeCounter());
|
|
EXPECT_EQ(0, island2.getReshapeCounter());
|
|
|
|
// NB: GMockBackendImpl implements "reshape" method,
|
|
// so it won't be recompiled if the meta is changed.
|
|
comp.apply(cv::gin(in_mat2), cv::gout(out_mat), cv::compile_args(pkg));
|
|
EXPECT_EQ(1, backend_impl1->getCompileCounter());
|
|
EXPECT_EQ(1, backend_impl2->getCompileCounter());
|
|
EXPECT_EQ(1, island1.getReshapeCounter());
|
|
EXPECT_EQ(1, island2.getReshapeCounter());
|
|
}
|
|
|
|
TEST_F(GExecutorReshapeTest, OneBackendNotReshapable)
|
|
{
|
|
// NB: Make first island not reshapable
|
|
island1.setReshape(false);
|
|
|
|
// NB: Initial state
|
|
EXPECT_EQ(0, backend_impl1->getCompileCounter());
|
|
EXPECT_EQ(0, island1.getReshapeCounter());
|
|
EXPECT_EQ(0, backend_impl2->getCompileCounter());
|
|
EXPECT_EQ(0, island2.getReshapeCounter());
|
|
|
|
// NB: First compilation.
|
|
comp.apply(cv::gin(in_mat1), cv::gout(out_mat), cv::compile_args(pkg));
|
|
EXPECT_EQ(1, backend_impl1->getCompileCounter());
|
|
EXPECT_EQ(1, backend_impl2->getCompileCounter());
|
|
EXPECT_EQ(0, island1.getReshapeCounter());
|
|
EXPECT_EQ(0, island2.getReshapeCounter());
|
|
|
|
// NB: Since one of islands isn't reshapable
|
|
// the entire graph isn't reshapable as well.
|
|
comp.apply(cv::gin(in_mat2), cv::gout(out_mat), cv::compile_args(pkg));
|
|
EXPECT_EQ(2, backend_impl1->getCompileCounter());
|
|
EXPECT_EQ(2, backend_impl2->getCompileCounter());
|
|
EXPECT_EQ(0, island1.getReshapeCounter());
|
|
EXPECT_EQ(0, island2.getReshapeCounter());
|
|
}
|
|
|
|
TEST_F(GExecutorReshapeTest, ReshapeCallAllocate)
|
|
{
|
|
// NB: Initial state
|
|
EXPECT_EQ(0, island1.getAllocateCounter());
|
|
EXPECT_EQ(0, island1.getReshapeCounter());
|
|
|
|
// NB: First compilation.
|
|
comp.apply(cv::gin(in_mat1), cv::gout(out_mat), cv::compile_args(pkg));
|
|
EXPECT_EQ(1, island1.getAllocateCounter());
|
|
EXPECT_EQ(0, island1.getReshapeCounter());
|
|
|
|
// NB: The entire graph is reshapable, so it won't be recompiled, but reshaped.
|
|
// Check that reshape call "allocate" to reallocate buffers.
|
|
comp.apply(cv::gin(in_mat2), cv::gout(out_mat), cv::compile_args(pkg));
|
|
EXPECT_EQ(2, island1.getAllocateCounter());
|
|
EXPECT_EQ(1, island1.getReshapeCounter());
|
|
}
|
|
|
|
TEST_F(GExecutorReshapeTest, CPUBackendIsReshapable)
|
|
{
|
|
comp = cv::GComputation([](){
|
|
cv::GMat in;
|
|
cv::GMat foo = I::Foo::on(in);
|
|
cv::GMat out = cv::gapi::bitwise_not(cv::gapi::bitwise_not(in));
|
|
return cv::GComputation(cv::GIn(in), cv::GOut(foo, out));
|
|
});
|
|
// NB: Initial state
|
|
EXPECT_EQ(0, island1.getReshapeCounter());
|
|
|
|
// NB: First compilation.
|
|
cv::Mat out_mat2;
|
|
comp.apply(cv::gin(in_mat1), cv::gout(out_mat, out_mat2), cv::compile_args(pkg));
|
|
EXPECT_EQ(0, island1.getReshapeCounter());
|
|
|
|
// NB: The entire graph is reshapable, so it won't be recompiled, but reshaped.
|
|
comp.apply(cv::gin(in_mat2), cv::gout(out_mat, out_mat2), cv::compile_args(pkg));
|
|
EXPECT_EQ(1, island1.getReshapeCounter());
|
|
EXPECT_EQ(0, cvtest::norm(out_mat2, in_mat2, NORM_INF));
|
|
}
|
|
|
|
// FIXME: Add explicit tests on GMat/GScalar/GArray<T> being connectors
|
|
// between executed islands
|
|
|
|
} // namespace opencv_test
|