diff --git a/demos/cascade_detection_demo/cpp_gapi/CMakeLists.txt b/demos/cascade_detection_demo/cpp_gapi/CMakeLists.txt new file mode 100644 index 00000000000..c524661e910 --- /dev/null +++ b/demos/cascade_detection_demo/cpp_gapi/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# + +file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) +file(GLOB_RECURSE HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp) + +add_demo(NAME cascade_detection_demo_gapi + SOURCES ${SOURCES} + HEADERS ${HEADERS} + INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/include" + DEPENDENCIES monitors utils_gapi + OPENCV_VERSION_REQUIRED 4.5.5) diff --git a/demos/cascade_detection_demo/cpp_gapi/cascade_detection_demo_gapi.hpp b/demos/cascade_detection_demo/cpp_gapi/cascade_detection_demo_gapi.hpp new file mode 100644 index 00000000000..8c329787c0a --- /dev/null +++ b/demos/cascade_detection_demo/cpp_gapi/cascade_detection_demo_gapi.hpp @@ -0,0 +1,102 @@ +// Copyright (C) 2022 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include +#include + +DEFINE_INPUT_FLAGS +DEFINE_OUTPUT_FLAGS + +static const char help_message[] = "Print a usage message."; +static const char det_model_message[] = "Required. Path to an .xml file with a detection trained model."; +static const char cls_model_message[] = "Required. Path to an .xml file with a recognition trained model."; +static const char det_labels_message[] = "Required. Path to detection model labels file.\n" + " Labels file should be of the following format:\n" + " cat\n" + " dog\n"; + +static const char cls_labels_message[] = "Required. Path to classification model labels file.\n" + " Labels file should be of the following format:\n" + " cat\n" + " dog\n"; + +static const char num_classes_message[] = ""; + +static const char detection_device_message[] = "Optional. Target device for detection network (the list of available devices is shown below). " + "The demo will look for a suitable plugin for a specified device. Default value is \"CPU\"."; +static const char classifier_device_message[] = "Optional. Target device for recognition network (the list of available devices is shown below). " + "The demo will look for a suitable plugin for a specified device. Default value is \"CPU\"."; + +static const char det_nireq_message[] = "Optional. Number of infer requests. If this option is omitted, number of infer requests is determined automatically."; +static const char det_num_threads_message[] = "Optional. Number of threads."; +static const char det_num_streams_message[] = "Optional. Number of streams to use for inference on the CPU or/and GPU in " +"throughput mode (for HETERO and MULTI device cases use format " +":,: or just )"; + +static const char cls_nireq_message[] = "Optional. Number of infer requests. If this option is omitted, number of infer requests is determined automatically."; +static const char cls_num_threads_message[] = "Optional. Number of threads."; +static const char cls_num_streams_message[] = "Optional. Number of streams to use for inference on the CPU or/and GPU in " +"throughput mode (for HETERO and MULTI device cases use format " +":,: or just )"; + +static const char parser_message[] = "Optional. Parser kind for detector. Possible values: ssd, yolo"; +static const char no_show_message[] = "Optional. Don't show output."; +static const char utilization_monitors_message[] = "Optional. List of monitors to show initially."; + +DEFINE_bool(h, false, help_message); +DEFINE_string(dm, "", det_model_message); +DEFINE_string(cm, "", cls_model_message); + +DEFINE_string(det_labels, "", det_labels_message); +DEFINE_string(cls_labels, "", cls_labels_message); + +DEFINE_uint32(num_classes, 0, num_classes_message); + +DEFINE_uint32(det_nireq, 1, det_nireq_message); +DEFINE_uint32(det_nthreads, 0, det_num_threads_message); +DEFINE_string(det_nstreams, "", det_num_streams_message); + +DEFINE_uint32(cls_nireq, 1, cls_nireq_message); +DEFINE_uint32(cls_nthreads, 0, cls_num_threads_message); +DEFINE_string(cls_nstreams, "", cls_num_streams_message); + +DEFINE_string(ddm, "CPU", detection_device_message); +DEFINE_string(dcm, "CPU", classifier_device_message); +DEFINE_string(parser, "ssd", parser_message); +DEFINE_bool(no_show, false, no_show_message); +DEFINE_string(u, "", utilization_monitors_message); + +/** +* \brief This function shows a help message +*/ + +static void showUsage() { + std::cout << std::endl; + std::cout << "background_subtraction_demo_gapi [OPTION]" << std::endl; + std::cout << "Options:" << std::endl; + std::cout << std::endl; + std::cout << " -h " << help_message << std::endl; + std::cout << " -i " << input_message << std::endl; + std::cout << " -loop " << loop_message << std::endl; + std::cout << " -o \"\" " << output_message << std::endl; + std::cout << " -limit \"\" " << limit_message << std::endl; + std::cout << " -dm \"\" " << det_model_message << std::endl; + std::cout << " -cm \"\" " << cls_model_message << std::endl; + std::cout << " -det_labels \"\" " << det_labels_message << std::endl; + std::cout << " -cls_labels \"\" " << cls_labels_message << std::endl; + std::cout << " -num_classes \"\" " << num_classes_message << std::endl; + std::cout << " -ddm \"\" " << detection_device_message << std::endl; + std::cout << " -cdm \"\" " << classifier_device_message << std::endl; + std::cout << " -det_nireq \"\" " << det_nireq_message << std::endl; + std::cout << " -det_nthreads \"\" " << det_num_threads_message << std::endl; + std::cout << " -det_nstreams " << det_num_streams_message << std::endl; + std::cout << " -cls_nireq \"\" " << cls_nireq_message << std::endl; + std::cout << " -cls_nthreads \"\" " << cls_num_threads_message << std::endl; + std::cout << " -cls_nstreams " << cls_num_streams_message << std::endl; + std::cout << " -no_show " << no_show_message << std::endl; + std::cout << " -u " << utilization_monitors_message << std::endl; +} diff --git a/demos/cascade_detection_demo/cpp_gapi/main.cpp b/demos/cascade_detection_demo/cpp_gapi/main.cpp new file mode 100644 index 00000000000..67caa4f8a7d --- /dev/null +++ b/demos/cascade_detection_demo/cpp_gapi/main.cpp @@ -0,0 +1,324 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cascade_detection_demo_gapi.hpp" + +#include +#include +#include +#include +#include + +using GDetections = cv::GArray; +using GLabelsIds = cv::GArray; +using GSize = cv::GOpaque; +using GProbs = cv::GArray; +using GPrims = cv::GArray; +using Labels = std::vector; + +static std::string getLabel(const int id, const std::vector& labels) { + std::string out_label; + if (id > -1) { + if (!labels.empty()) { + out_label = labels[id]; + } else { + out_label = std::to_string(id); + } + } + return out_label; +}; + +G_API_OP(FilterOutOfBounds, (GDetections,GLabelsIds,GSize)>, + "sample.custom.filter_out_of_bounds") { + static std::tuple outMeta(cv::GArrayDesc, + cv::GArrayDesc, + cv::GOpaqueDesc) { + return std::make_tuple(cv::empty_array_desc(), cv::empty_array_desc()); + } +}; + +GAPI_OCV_KERNEL(OCVFilterOutOfBounds, FilterOutOfBounds) { + static void run(const std::vector& in_rcts, + const std::vector& in_det_ids, + const cv::Size& in_size, + std::vector& out_rcts, + std::vector& out_det_ids) { + cv::Rect surface({0, 0}, in_size); + for (uint32_t i = 0; i < in_rcts.size(); ++i) { + const auto rc = in_rcts[i]; + // NOTE: IE adds one more row or column to the ROI in case if ROI + // has odd height or width and original image has NV12 format. + auto adj_rc = rc; + adj_rc.width += adj_rc.width % 2; + adj_rc.height += adj_rc.height % 2; + + auto clipped_rc = adj_rc & surface; + + if (clipped_rc.area() != adj_rc.area()) + { + continue; + } + + out_rcts.push_back(rc); + out_det_ids.push_back(in_det_ids[i]); + } + } +}; + +G_API_OP(ParseProbs, , + "sample.custom.parse_probs") { + static cv::GArrayDesc outMeta(cv::GArrayDesc, const int, const float) { + return cv::empty_array_desc(); + } +}; + +GAPI_OCV_KERNEL(OCVParseProbs, ParseProbs) { + static void run(const std::vector& samples_probs, const int num_classes, + const float threshold, std::vector& out_classes) { + out_classes.resize(samples_probs.size()); + for (uint32_t i = 0; i < samples_probs.size(); ++i) { + const auto probs = samples_probs[i]; + const float* probs_data = probs.ptr(); + const float* id = std::max_element(probs_data, probs_data + num_classes); + + out_classes[i] = (*id) >= threshold ? id - probs_data : -1; + } + } +}; + + +G_API_OP(LabeledBoxes, , + "sample.custom.labeled_boxes") { + static cv::GArrayDesc outMeta(cv::GArrayDesc, + cv::GArrayDesc, + cv::GArrayDesc, + Labels, + Labels) { + return cv::empty_array_desc(); + } +}; + +GAPI_OCV_KERNEL(OCVLabeledBoxes, LabeledBoxes) { + // Converts rectangles, labels into G-API's rendering primitives + static void run(const std::vector& in_rcs, + const std::vector& in_det_ids, + const std::vector& in_cls_ids, + const Labels& det_labels, + const Labels& cls_labels, + std::vector& out_prims) { + out_prims.clear(); + + for (uint32_t i = 0; i < in_rcs.size(); ++i) { + out_prims.emplace_back(cv::gapi::wip::draw::Rect(in_rcs[i], CV_RGB(0, 255, 0), 2)); + + const auto detection_str = getLabel(in_det_ids[i], det_labels); + out_prims.emplace_back(cv::gapi::wip::draw::Text + { detection_str, + in_rcs[i].tl() + cv::Point(3, 20), cv::FONT_HERSHEY_SIMPLEX, + 0.7, CV_RGB(0, 255, 0), 2, 8, false }); + + const auto detection_str_h = getTextSize(detection_str, cv::FONT_HERSHEY_SIMPLEX, + 0.7, 2, nullptr).height; + out_prims.emplace_back(cv::gapi::wip::draw::Text + { getLabel(in_cls_ids[i], cls_labels), + in_rcs[i].tl() + cv::Point(3, 25 + detection_str_h), + cv::FONT_HERSHEY_SIMPLEX, + 0.7, CV_RGB(230, 216, 173), 2, 8, false }); + } + } +}; + +G_API_NET(Detector, , "sample.detector"); +G_API_NET(Classifier, , "sample.classifier"); + +namespace util { +bool ParseAndCheckCommandLine(int argc, char *argv[]) { + /** ---------- Parsing and validating input arguments ----------**/ + gflags::ParseCommandLineNonHelpFlags(&argc, &argv, true); + if (FLAGS_h) { + showUsage(); + showAvailableDevices(); + return false; + } + if (FLAGS_i.empty()) + throw std::logic_error("Parameter -i is not set"); + if (FLAGS_dm.empty()) + throw std::logic_error("Parameter -dm is not set"); + if (FLAGS_cm.empty()) { + throw std::logic_error("Parameter -cm is not set"); + } + return true; +} +static std::vector readLabelsFromFile(const std::string& file_path) { + std::vector out_labels; + + std::ifstream is; + is.open(file_path, std::ios::in); + + if (!is.is_open()) { + throw std::logic_error(std::string("Could not open ") + file_path); + } + + std::string label; + while (std::getline(is, label)) { + // simple trimming from the end: + label.erase(std::find_if(label.rbegin(), label.rend(), [](unsigned char ch) { + return !std::isspace(ch); + }).base(), label.end()); + + out_labels.push_back(label); + } + return out_labels; +} + +} // namespace util + +int main(int argc, char* argv[]) { + PerformanceMetrics metrics; + + /** Get OpenVINO runtime version **/ + slog::info << ov::get_openvino_version() << slog::endl; + // ---------- Parsing and validating of input arguments ---------- + if (!util::ParseAndCheckCommandLine(argc, argv)) { + return 0; + } + + std::vector detector_labels; + std::vector classifier_labels; + if (!FLAGS_det_labels.empty()) { + detector_labels = util::readLabelsFromFile(FLAGS_det_labels); + } + auto num_classes = FLAGS_num_classes; + if (!FLAGS_cls_labels.empty()) { + classifier_labels = util::readLabelsFromFile(FLAGS_cls_labels); + num_classes = classifier_labels.size(); + } + + /** Get information about frame **/ + std::shared_ptr cap = openImagesCapture(FLAGS_i, FLAGS_loop, read_type::safe, 0, + std::numeric_limits::max()); + const auto tmp = cap->read(); + cv::Size frame_size = cv::Size{tmp.cols, tmp.rows}; + + /** ---------------- Main graph of demo ---------------- **/ + cv::GComputation comp([&]{ + cv::GMat in; + cv::GMat detections = cv::gapi::infer(in); + + auto im_size = cv::gapi::streaming::size(in); + + cv::GArray objs; + cv::GArray det_ids; + if (FLAGS_parser == "yolo") { + std::tie(objs, det_ids) = cv::gapi::streaming::parseYolo(detections, im_size, 0.5f, 0.5f); + } + else { + std::tie(objs, det_ids) = cv::gapi::streaming::parseSSD(detections, im_size, 0.6f, -1); + } + + // Filter out of bounds projections + cv::GArray filtered_objs; + cv::GArray filtered_det_ids; + std::tie(filtered_objs, filtered_det_ids) = FilterOutOfBounds::on(objs, + det_ids, + im_size); + + // Run Inference for classifier on the passed ROIs of the frame + cv::GArray filtered_probs = cv::gapi::infer(filtered_objs, in); + // Run custom operation to project probabilities to the labels identifiers + cv::GArray filtered_cls_ids = ParseProbs::on(filtered_probs, num_classes, 0.5f); + + auto prims = LabeledBoxes::on(filtered_objs, + filtered_det_ids, + filtered_cls_ids, + detector_labels, + classifier_labels); + + auto rendered = cv::gapi::wip::draw::render3ch(in, prims); + return cv::GComputation(cv::GIn(in), cv::GOut(rendered)); + }); + + auto det_config = ConfigFactory::getUserConfig(FLAGS_ddm, FLAGS_det_nireq, + FLAGS_det_nstreams, FLAGS_det_nthreads); + const auto detector = cv::gapi::ie::Params { + FLAGS_dm, // path to topology IR + fileNameNoExt(FLAGS_dm) + ".bin", // path to weights + FLAGS_ddm // device specifier + }.cfgNumRequests(det_config.maxAsyncRequests) + .pluginConfig(det_config.getLegacyConfig()); + slog::info << "The detection model " << FLAGS_dm << " is loaded to " << FLAGS_ddm << " device." << slog::endl; + + auto cls_config = ConfigFactory::getUserConfig(FLAGS_dcm, FLAGS_cls_nireq, + FLAGS_cls_nstreams, FLAGS_cls_nthreads); + const auto classifier = cv::gapi::ie::Params { + FLAGS_cm, // path to topology IR + fileNameNoExt(FLAGS_cm) + ".bin", // path to weights + FLAGS_dcm // device specifier + }.cfgNumRequests(cls_config.maxAsyncRequests) + .pluginConfig(cls_config.getLegacyConfig()); + slog::info << "The classification model " << FLAGS_cm << " is loaded to " << FLAGS_dcm << " device." << slog::endl; + + auto pipeline = comp.compileStreaming( + cv::compile_args(cv::gapi::kernels(), + cv::gapi::networks(detector, classifier))); + + /** Output container for result **/ + cv::Mat output; + + /** ---------------- The execution part ---------------- **/ + cap = openImagesCapture(FLAGS_i, FLAGS_loop, read_type::safe, 0, + std::numeric_limits::max()); + + pipeline.setSource(cap); + std::string windowName = "Cascade detection demo G-API"; + int delay = 1; + + cv::Size graphSize{static_cast(frame_size.width / 4), 60}; + Presenter presenter(FLAGS_u, frame_size.height - graphSize.height - 10, graphSize); + + LazyVideoWriter videoWriter{FLAGS_o, cap->fps(), FLAGS_limit}; + + bool isStart = true; + const auto startTime = std::chrono::steady_clock::now(); + pipeline.start(); + + while(pipeline.pull(cv::gout(output))) { + presenter.drawGraphs(output); + if (isStart) { + metrics.update(startTime, output, { 10, 22 }, cv::FONT_HERSHEY_COMPLEX, + 0.65, { 200, 10, 10 }, 2, PerformanceMetrics::MetricTypes::FPS); + isStart = false; + } + else { + metrics.update({}, output, { 10, 22 }, cv::FONT_HERSHEY_COMPLEX, + 0.65, { 200, 10, 10 }, 2, PerformanceMetrics::MetricTypes::FPS); + } + + videoWriter.write(output); + + if (!FLAGS_no_show) { + cv::imshow(windowName, output); + int key = cv::waitKey(delay); + /** Press 'Esc' to quit **/ + if (key == 27) { + break; + } else { + presenter.handleKey(key); + } + } + } + slog::info << "Metrics report:" << slog::endl; + slog::info << "\tFPS: " << std::fixed << std::setprecision(1) << metrics.getTotal().fps << slog::endl; + slog::info << presenter.reportMeans() << slog::endl; + + return 0; +}