From b0b77b3047356b2bd507d55ccd3a661b54d5a150 Mon Sep 17 00:00:00 2001 From: TolyaTalamanov Date: Sun, 2 Oct 2022 20:54:08 +0000 Subject: [PATCH] Add cfgOutputPrecision --- .../gapi/include/opencv2/gapi/infer/ie.hpp | 51 ++++++++- .../gapi/samples/pipeline_modeling_tool.cpp | 6 + .../pipeline_builder.hpp | 4 + modules/gapi/src/backends/ie/giebackend.cpp | 49 +++++++- .../gapi/test/infer/gapi_infer_ie_test.cpp | 105 ++++++++++++++++++ 5 files changed, 212 insertions(+), 3 deletions(-) diff --git a/modules/gapi/include/opencv2/gapi/infer/ie.hpp b/modules/gapi/include/opencv2/gapi/infer/ie.hpp index 204bd8f266..1e8fbc576c 100644 --- a/modules/gapi/include/opencv2/gapi/infer/ie.hpp +++ b/modules/gapi/include/opencv2/gapi/infer/ie.hpp @@ -88,6 +88,15 @@ struct ParamDesc { cv::optional vpl_preproc_device; cv::optional vpl_preproc_ctx; + + using precision_t = int; + using precision_map_t = std::unordered_map; + // NB: cv::util::monostate is default value that means precision wasn't specified. + using precision_variant_t = cv::util::variant; + precision_variant_t output_precision; + }; } // namespace detail @@ -132,6 +141,7 @@ public: , {} , {} , {} + , {} , {}} { }; @@ -156,6 +166,7 @@ public: , {} , {} , {} + , {} , {}} { }; @@ -351,6 +362,29 @@ public: return *this; } + /** @brief Specifies the output precision for model. + + The function is used to set an output precision for model. + + @param precision Precision in OpenCV format. + @return reference to this parameter structure. + */ + Params& cfgOutputPrecision(detail::ParamDesc::precision_t precision) { + desc.output_precision = precision; + return *this; + } + + /** @overload + + @param precision_map Map of pairs: name of corresponding output layer and its precision + @return reference to this parameter structure. + */ + Params& + cfgOutputPrecision(detail::ParamDesc::precision_map_t precision_map) { + desc.output_precision = precision_map; + return *this; + } + // BEGIN(G-API's network parametrization API) GBackend backend() const { return cv::gapi::ie::backend(); } std::string tag() const { return Net::tag(); } @@ -385,7 +419,7 @@ public: const std::string &device) : desc{ model, weights, device, {}, {}, {}, 0u, 0u, detail::ParamDesc::Kind::Load, true, {}, {}, {}, 1u, - {}, {}, {}, {}}, + {}, {}, {}, {}, {}}, m_tag(tag) { }; @@ -403,7 +437,7 @@ public: const std::string &device) : desc{ model, {}, device, {}, {}, {}, 0u, 0u, detail::ParamDesc::Kind::Import, true, {}, {}, {}, 1u, - {}, {}, {}, {}}, + {}, {}, {}, {}, {}}, m_tag(tag) { }; @@ -476,6 +510,19 @@ public: return *this; } + /** @see ie::Params::cfgOutputPrecision */ + Params& cfgOutputPrecision(detail::ParamDesc::precision_t precision) { + desc.output_precision = precision; + return *this; + } + + /** @overload */ + Params& + cfgOutputPrecision(detail::ParamDesc::precision_map_t precision_map) { + desc.output_precision = precision_map; + return *this; + } + // BEGIN(G-API's network parametrization API) GBackend backend() const { return cv::gapi::ie::backend(); } std::string tag() const { return m_tag; } diff --git a/modules/gapi/samples/pipeline_modeling_tool.cpp b/modules/gapi/samples/pipeline_modeling_tool.cpp index 60547a9c9b..540c8d7766 100644 --- a/modules/gapi/samples/pipeline_modeling_tool.cpp +++ b/modules/gapi/samples/pipeline_modeling_tool.cpp @@ -210,6 +210,12 @@ InferParams read(const cv::FileNode& fn) { params.input_layers = readList(fn, "input_layers", name); params.output_layers = readList(fn, "output_layers", name); params.config = readMap(fn["config"]); + + auto out_prec_str = readOpt(fn["output_precision"]); + if (out_prec_str.has_value()) { + params.out_precision = + cv::optional(strToPrecision(out_prec_str.value())); + } return params; } diff --git a/modules/gapi/samples/pipeline_modeling_tool/pipeline_builder.hpp b/modules/gapi/samples/pipeline_modeling_tool/pipeline_builder.hpp index a3f187249d..411a5b2e6d 100644 --- a/modules/gapi/samples/pipeline_modeling_tool/pipeline_builder.hpp +++ b/modules/gapi/samples/pipeline_modeling_tool/pipeline_builder.hpp @@ -258,6 +258,7 @@ struct InferParams { std::vector input_layers; std::vector output_layers; std::map config; + cv::util::optional out_precision; }; class PipelineBuilder { @@ -362,6 +363,9 @@ void PipelineBuilder::addInfer(const CallParams& call_params, } pp->pluginConfig(infer_params.config); + if (infer_params.out_precision) { + pp->cfgOutputPrecision(infer_params.out_precision.value()); + } m_state->networks += cv::gapi::networks(*pp); addCall(call_params, diff --git a/modules/gapi/src/backends/ie/giebackend.cpp b/modules/gapi/src/backends/ie/giebackend.cpp index eca07ce9df..b76a468496 100644 --- a/modules/gapi/src/backends/ie/giebackend.cpp +++ b/modules/gapi/src/backends/ie/giebackend.cpp @@ -197,6 +197,16 @@ inline IE::Blob::Ptr wrapIE(const cv::MediaFrame::View& view, template inline void copyFromIE(const IE::Blob::Ptr &blob, MatType &mat) { + const auto& desc = blob->getTensorDesc(); + const auto ie_type = toCV(desc.getPrecision()); + if (ie_type != mat.type()) { + std::stringstream ss; + ss << "Failed while copying blob from IE to OCV: " + << "Blobs have different data types.\n" + << "IE type: " << ie_type << "\n" + << "OCV type: " << mat.type() << std::endl; + throw std::logic_error(ss.str()); + } switch (blob->getTensorDesc().getPrecision()) { #define HANDLE(E,T) \ case IE::Precision::E: std::copy_n(blob->buffer().as(), \ @@ -365,6 +375,13 @@ struct IEUnit { cv::util::throw_error(std::logic_error("Unsupported ParamDesc::Kind")); } + if (params.kind == cv::gapi::ie::detail::ParamDesc::Kind::Import && + !cv::util::holds_alternative(params.output_precision)) { + cv::util::throw_error( + std::logic_error("Setting output precision isn't supported for imported network")); + } + + using namespace cv::gapi::wip::onevpl; if (params.vpl_preproc_device.has_value() && params.vpl_preproc_ctx.has_value()) { using namespace cv::gapi::wip; @@ -1122,6 +1139,32 @@ static IE::PreProcessInfo configurePreProcInfo(const IE::InputInfo::CPtr& ii, return info; } +using namespace cv::gapi::ie::detail; +static void configureOutputPrecision(const IE::OutputsDataMap &outputs_info, + const ParamDesc::precision_variant_t &output_precision) { + switch (output_precision.index()) { + case ParamDesc::precision_variant_t::index_of(): { + auto precision = toIE(cv::util::get(output_precision)); + for (auto it : outputs_info) { + it.second->setPrecision(precision); + } + break; + } + case ParamDesc::precision_variant_t::index_of(): { + const auto& precision_map = + cv::util::get(output_precision); + for (auto it : precision_map) { + outputs_info.at(it.first)->setPrecision(toIE(it.second)); + } + break; + } + case ParamDesc::precision_variant_t::index_of(): { + // Do nothing; + break; + } + } +} + // NB: This is a callback used by async infer // to post outputs blobs (cv::GMat's). static void PostOutputs(InferenceEngine::InferRequest &request, @@ -1241,7 +1284,7 @@ struct Infer: public cv::detail::KernelTag { GAPI_Assert(uu.params.input_names.size() == in_metas.size() && "Known input layers count doesn't match input meta count"); - // NB: Configuring input precision and network reshape must be done + // NB: Configuring input/output precision and network reshape must be done // only in the loadNetwork case. using namespace cv::gapi::ie::detail; if (uu.params.kind == ParamDesc::Kind::Load) { @@ -1275,6 +1318,7 @@ struct Infer: public cv::detail::KernelTag { if (!input_reshape_table.empty()) { const_cast(&uu.net)->reshape(input_reshape_table); } + configureOutputPrecision(uu.net.getOutputsInfo(), uu.params.output_precision); } else { GAPI_Assert(uu.params.kind == ParamDesc::Kind::Import); auto inputs = uu.this_network.GetInputsInfo(); @@ -1393,6 +1437,7 @@ struct InferROI: public cv::detail::KernelTag { const_cast(uu.net_input_params) .set_param(input_name, ii->getTensorDesc()); } + configureOutputPrecision(uu.net.getOutputsInfo(), uu.params.output_precision); } else { GAPI_Assert(uu.params.kind == cv::gapi::ie::detail::ParamDesc::Kind::Import); auto inputs = uu.this_network.GetInputsInfo(); @@ -1513,6 +1558,7 @@ struct InferList: public cv::detail::KernelTag { if (!input_reshape_table.empty()) { const_cast(&uu.net)->reshape(input_reshape_table); } + configureOutputPrecision(uu.net.getOutputsInfo(), uu.params.output_precision); } else { GAPI_Assert(uu.params.kind == cv::gapi::ie::detail::ParamDesc::Kind::Import); std::size_t idx = 1u; @@ -1667,6 +1713,7 @@ struct InferList2: public cv::detail::KernelTag { if (!input_reshape_table.empty()) { const_cast(&uu.net)->reshape(input_reshape_table); } + configureOutputPrecision(uu.net.getOutputsInfo(), uu.params.output_precision); } else { GAPI_Assert(uu.params.kind == cv::gapi::ie::detail::ParamDesc::Kind::Import); auto inputs = uu.this_network.GetInputsInfo(); diff --git a/modules/gapi/test/infer/gapi_infer_ie_test.cpp b/modules/gapi/test/infer/gapi_infer_ie_test.cpp index 3741438373..54c67c02d0 100644 --- a/modules/gapi/test/infer/gapi_infer_ie_test.cpp +++ b/modules/gapi/test/infer/gapi_infer_ie_test.cpp @@ -2956,6 +2956,111 @@ TEST(TestAgeGender, ThrowBlobAndInputPrecisionMismatchStreaming) } } +TEST(TestAgeGenderIE, ChangeOutputPrecision) +{ + initDLDTDataPath(); + + cv::gapi::ie::detail::ParamDesc params; + params.model_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); + params.weights_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); + params.device_id = "CPU"; + + cv::Mat in_mat(cv::Size(320, 240), CV_8UC3); + cv::randu(in_mat, 0, 255); + + cv::Mat gapi_age, gapi_gender; + + // Load & run IE network + IE::Blob::Ptr ie_age, ie_gender; + { + auto plugin = cv::gimpl::ie::wrap::getPlugin(params); + auto net = cv::gimpl::ie::wrap::readNetwork(params); + setNetParameters(net); + for (auto it : net.getOutputsInfo()) { + it.second->setPrecision(IE::Precision::U8); + } + auto this_network = cv::gimpl::ie::wrap::loadNetwork(plugin, net, params); + auto infer_request = this_network.CreateInferRequest(); + infer_request.SetBlob("data", cv::gapi::ie::util::to_ie(in_mat)); + infer_request.Infer(); + ie_age = infer_request.GetBlob("age_conv3"); + ie_gender = infer_request.GetBlob("prob"); + } + + // Configure & run G-API + using AGInfo = std::tuple; + G_API_NET(AgeGender, , "test-age-gender"); + + cv::GMat in; + cv::GMat age, gender; + std::tie(age, gender) = cv::gapi::infer(in); + cv::GComputation comp(cv::GIn(in), cv::GOut(age, gender)); + + auto pp = cv::gapi::ie::Params { + params.model_path, params.weights_path, params.device_id + }.cfgOutputLayers({ "age_conv3", "prob" }) + .cfgOutputPrecision(CV_8U); + comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + // Validate with IE itself (avoid DNN module dependency here) + normAssert(cv::gapi::ie::util::to_ocv(ie_age), gapi_age, "Test age output" ); + normAssert(cv::gapi::ie::util::to_ocv(ie_gender), gapi_gender, "Test gender output"); +} + +TEST(TestAgeGenderIE, ChangeSpecificOutputPrecison) +{ + initDLDTDataPath(); + + cv::gapi::ie::detail::ParamDesc params; + params.model_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); + params.weights_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); + params.device_id = "CPU"; + + cv::Mat in_mat(cv::Size(320, 240), CV_8UC3); + cv::randu(in_mat, 0, 255); + + cv::Mat gapi_age, gapi_gender; + + // Load & run IE network + IE::Blob::Ptr ie_age, ie_gender; + { + auto plugin = cv::gimpl::ie::wrap::getPlugin(params); + auto net = cv::gimpl::ie::wrap::readNetwork(params); + setNetParameters(net); + + // NB: Specify precision only for "prob" output. + net.getOutputsInfo().at("prob")->setPrecision(IE::Precision::U8); + + auto this_network = cv::gimpl::ie::wrap::loadNetwork(plugin, net, params); + auto infer_request = this_network.CreateInferRequest(); + infer_request.SetBlob("data", cv::gapi::ie::util::to_ie(in_mat)); + infer_request.Infer(); + ie_age = infer_request.GetBlob("age_conv3"); + ie_gender = infer_request.GetBlob("prob"); + } + + // Configure & run G-API + using AGInfo = std::tuple; + G_API_NET(AgeGender, , "test-age-gender"); + + cv::GMat in; + cv::GMat age, gender; + std::tie(age, gender) = cv::gapi::infer(in); + cv::GComputation comp(cv::GIn(in), cv::GOut(age, gender)); + + auto pp = cv::gapi::ie::Params { + params.model_path, params.weights_path, params.device_id + }.cfgOutputLayers({ "age_conv3", "prob" }) + .cfgOutputPrecision({{"prob", CV_8U}}); + comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + // Validate with IE itself (avoid DNN module dependency here) + normAssert(cv::gapi::ie::util::to_ocv(ie_age), gapi_age, "Test age output" ); + normAssert(cv::gapi::ie::util::to_ocv(ie_gender), gapi_gender, "Test gender output"); +} + } // namespace opencv_test #endif // HAVE_INF_ENGINE