// 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 #include #include #include #ifdef HAVE_OPENCV_VIDEO #include #endif namespace opencv_test { struct BackSubStateParams { std::string method; }; } // namespace opencv_test namespace cv { namespace detail { template<> struct CompileArgTag { static const char* tag() { return "org.opencv.test.background_substractor_state_params"; } }; } // namespace detail } // namespace cv namespace opencv_test { //TODO: test OT, Background Subtractor, Kalman with 3rd version of API //----------------------------------------------- Simple tests ------------------------------------------------ namespace { G_TYPED_KERNEL(GCountCalls, (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 &state) { state.reset(new int{ }); } static void run(const cv::Mat &/* in */, int &out, int& state) { out = ++state; } }; G_TYPED_KERNEL(GIsStateUpToDate, (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 &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, , "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 &/* 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); } }; G_TYPED_KERNEL(GBackSub, , "org.opencv.test.background_substractor") { static GMatDesc outMeta(GMatDesc in) { return in.withType(CV_8U, 1); } }; #ifdef HAVE_OPENCV_VIDEO GAPI_OCV_KERNEL_ST(GOCVBackSub, GBackSub, cv::BackgroundSubtractor) { static void setup(const cv::GMatDesc &/* desc */, std::shared_ptr &state, const cv::GCompileArgs &compileArgs) { auto sbParams = cv::gapi::getCompileArg(compileArgs) .value_or(BackSubStateParams { }); if (sbParams.method == "knn") state = createBackgroundSubtractorKNN(); else if (sbParams.method == "mog2") state = createBackgroundSubtractorMOG2(); GAPI_Assert(state); } static void run(const cv::Mat& in, cv::Mat &out, BackgroundSubtractor& state) { state.apply(in, out, -1); } }; #endif }; 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 out = GCountCalls::on(in); cv::GComputation comp(cv::GIn(in), cv::GOut(out)); const auto pkg = cv::gapi::kernels(); // 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) { cv::GMat in; cv::GOpaque out = GIsStateUpToDate::on(in); cv::GComputation c(cv::GIn(in), cv::GOut(out)); const auto pkg = cv::gapi::kernels(); // Compilation & testing auto ccomp = c.compileStreaming(cv::compile_args(pkg)); auto path = findDataFile("cv/video/768x576.avi"); try { ccomp.setSource(gapi::wip::make_src(path)); } catch(...) { throw SkipTestException("Video file can not be opened"); } 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()); path = findDataFile("cv/video/1920x1080.avi"); try { ccomp.setSource(gapi::wip::make_src(path)); } catch(...) { throw SkipTestException("Video file can not be opened"); } 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(); cv::GComputation comp(cv::GIn(in), cv::GOut(out)); EXPECT_THROW(comp.apply(in_mat, out_mat, cv::compile_args(pkg)), std::logic_error); } #ifdef HAVE_OPENCV_VIDEO namespace { void compareBackSubResults(const cv::Mat &actual, const cv::Mat &expected, const int diffPercent) { GAPI_Assert(actual.size() == expected.size()); int allowedNumDiffPixels = actual.size().area() * diffPercent / 100; cv::Mat diff; cv::absdiff(actual, expected, diff); cv::Mat hist(256, 1, CV_32FC1, cv::Scalar(0)); const float range[] { 0, 256 }; const float *histRange { range }; calcHist(&diff, 1, 0, Mat(), hist, 1, &hist.rows, &histRange, true, false); for (int i = 2; i < hist.rows; ++i) { hist.at(i) += hist.at(i - 1); } int numDiffPixels = static_cast(hist.at(255)); EXPECT_GT(allowedNumDiffPixels, numDiffPixels); } } // anonymous namespace TEST(StatefulKernel, StateIsInitViaCompArgs) { cv::Mat frame(1080, 1920, CV_8UC3), gapiForeground, ocvForeground; cv::randu(frame, cv::Scalar(0, 0, 0), cv::Scalar(255, 255, 255)); // G-API code cv::GMat in; cv::GMat out = GBackSub::on(in); cv::GComputation c(cv::GIn(in), cv::GOut(out)); const auto pkg = cv::gapi::kernels(); auto gapiBackSub = c.compile(cv::descr_of(frame), cv::compile_args(pkg, BackSubStateParams { "knn" })); gapiBackSub(cv::gin(frame), cv::gout(gapiForeground)); // OpenCV code auto pOcvBackSub = createBackgroundSubtractorKNN(); pOcvBackSub->apply(frame, ocvForeground); // Comparison // Allowing 1% difference of all pixels between G-API and OpenCV results compareBackSubResults(gapiForeground, ocvForeground, 1); // Additionally, test the case where state is resetted gapiBackSub.prepareForNewStream(); gapiBackSub(cv::gin(frame), cv::gout(gapiForeground)); pOcvBackSub->apply(frame, ocvForeground); compareBackSubResults(gapiForeground, ocvForeground, 1); } #endif #ifdef HAVE_OPENCV_VIDEO namespace { void testBackSubInStreaming(cv::GStreamingCompiled gapiBackSub, const int diffPercent) { cv::Mat frame, gapiForeground, ocvForeground; gapiBackSub.start(); EXPECT_TRUE(gapiBackSub.running()); // OpenCV reference substractor auto pOCVBackSub = createBackgroundSubtractorKNN(); // Comparison of G-API and OpenCV substractors std::size_t frames = 0u; while (gapiBackSub.pull(cv::gout(frame, gapiForeground))) { pOCVBackSub->apply(frame, ocvForeground, -1); compareBackSubResults(gapiForeground, ocvForeground, diffPercent); frames++; } EXPECT_LT(0u, frames); EXPECT_FALSE(gapiBackSub.running()); } } // anonymous namespace TEST(StatefulKernel, StateIsInitViaCompArgsInStreaming) { // G-API graph declaration cv::GMat in; cv::GMat out = GBackSub::on(in); // Preserving 'in' in output to have possibility to compare with OpenCV reference cv::GComputation c(cv::GIn(in), cv::GOut(cv::gapi::copy(in), out)); // G-API compilation of graph for streaming mode const auto pkg = cv::gapi::kernels(); auto gapiBackSub = c.compileStreaming( cv::compile_args(pkg, BackSubStateParams { "knn" })); // Testing G-API Background Substractor in streaming mode auto path = findDataFile("cv/video/768x576.avi"); try { gapiBackSub.setSource(gapi::wip::make_src(path)); } catch(...) { throw SkipTestException("Video file can not be opened"); } // Allowing 1% difference of all pixels between G-API and reference OpenCV results testBackSubInStreaming(gapiBackSub, 1); path = findDataFile("cv/video/1920x1080.avi"); try { // Additionally, test the case when the new stream happens gapiBackSub.setSource(gapi::wip::make_src(path)); } catch(...) { throw SkipTestException("Video file can not be opened"); } // Allowing 5% difference of all pixels between G-API and reference OpenCV results testBackSubInStreaming(gapiBackSub, 5); } #endif //------------------------------------------------------------------------------------------------------------- //------------------------------------------- Typed tests on setup() ------------------------------------------ namespace { template 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, (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 &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(); 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; } DEFINE_INITIALIZER(StdArrayValue, Std3IntArray, { 1, 2, 3 }); DEFINE_INITIALIZER(UserValue, UserStruct, { 5, 7.f }); using TypesToVerify = ::testing::Types, std::tuple, std::tuple, std::tuple, std::tuple, StdArrayValue>, std::tuple>; INSTANTIATE_TYPED_TEST_CASE_P(SetupStateTypedInst, SetupStateTypedTest, TypesToVerify); //------------------------------------------------------------------------------------------------------------- } // opencv_test