rs-dnn-vino.cpp
Go to the documentation of this file.
1 // License: Apache 2.0. See LICENSE file in root directory.
2 // Copyright(c) 2019 Intel Corporation. All Rights Reserved.
3 
4 #include <librealsense2/rs.hpp> // Include RealSense Cross Platform API
5 
6 #include "cv-helpers.hpp" // frame_to_mat
7 #include <opencv2/core/utils/filesystem.hpp> // glob
8 namespace fs = cv::utils::fs;
9 
12 
13 #include <easylogging++.h>
14 #ifdef BUILD_SHARED_LIBS
15 // With static linkage, ELPP is initialized by librealsense, so doing it here will
16 // create errors. When we're using the shared .so/.dll, the two are separate and we have
17 // to initialize ours if we want to use the APIs!
19 #endif
20 
22 namespace openvino = InferenceEngine;
23 
24 #include <chrono>
25 using namespace std::chrono;
26 
27 
28 /*
29  Enable loading multiple detectors at once, so we can switch at runtime.
30  Each detector has its associated labels.
31 */
33 {
34  std::shared_ptr< openvino_helpers::object_detection > detector;
35  std::vector< std::string > labels;
36 
37  detector_and_labels( std::string const & path_to_xml )
38  : detector( std::make_shared< openvino_helpers::object_detection >( path_to_xml, 0.5 ) )
39  {
40  }
41 
42  openvino_helpers::object_detection * operator->() { return detector.get(); }
43 
44  void load_labels()
45  {
46  try
47  {
48  labels = openvino_helpers::read_labels( openvino_helpers::remove_ext( detector->pathToModel ) + ".labels" );
49  }
50  catch( const std::exception & e )
51  {
52  // If we have no labels, warn and continue... we can continue without them
53  LOG(WARNING) << "Failed to load labels: " << e.what();
54  }
55  }
56 };
57 
58 
59 /*
60  Populate a collection of detectors from those we find on disk (*.xml), load
61  their labels, add them to the engine & device, etc.
62 
63  The detectors are loaded with all default values.
64 */
66  std::vector< detector_and_labels > & detectors,
67  openvino::Core & engine,
68  std::string const & device_name
69 )
70 {
71  std::vector< std::string > xmls;
72  fs::glob_relative( ".", "*.xml", xmls );
73  for( auto path_to_xml : xmls )
74  {
75  detector_and_labels detector { path_to_xml };
76  try
77  {
78  detector->load_into( engine, device_name ); // May throw!
79  detector.load_labels();
80  detectors.push_back( detector );
81  LOG(INFO) << " ... press '" << char( '0' + detectors.size() ) << "' to switch to it";
82  }
83  catch( const std::exception & e )
84  {
85  // The model files should have been downloaded automatically by CMake into build/wrappers/openvino/dnn,
86  // which is also where Visual Studio runs the sample from. However, you may need to copy these files:
87  // *.bin
88  // *.xml
89  // *.labels [optional]
90  // Into the local directory where you run from (or change the path given in the ctor above)
91  LOG(ERROR) << "Failed to load model: " << e.what();
92  }
93  }
94 }
95 
96 
97 /*
98  Main detection code:
99 
100  Detected objects are placed into 'objects'. Each new object is assigned 'next_id', which is then incremented.
101  The 'labels' are optional, and used to give labels to each object.
102 
103  Some basic effort is made to keep the creation of new objects to a minimum: previous objects (passed in via
104  'objects') are compared with new detections to see if the new are simply new positions for the old. An
105  "intersection over union" (IoU) quotient is calculated and, if over a threshold, an existing object is moved
106  rather than a new one created.
107 */
109  cv::Mat const & image,
110  std::vector< openvino_helpers::object_detection::Result > const & results,
111  std::vector< std::string > & labels,
112  size_t & next_id,
114 )
115 {
116  openvino_helpers::detected_objects prev_objects{ std::move( objects ) };
117  objects.clear();
118  for( auto const & result : results )
119  {
120  if( result.label <= 0 )
121  continue; // ignore "background", though not clear why we'd get it
122  cv::Rect rect = result.location;
123  rect = rect & cv::Rect( 0, 0, image.cols, image.rows );
124  auto object_ptr = openvino_helpers::find_object( rect, prev_objects );
125  if( ! object_ptr )
126  {
127  // New object
129  if( result.label < labels.size() )
130  label = labels[result.label];
131  object_ptr = std::make_shared< openvino_helpers::detected_object >( next_id++, label, rect );
132  }
133  else
134  {
135  // Existing face; just update its parameters
136  object_ptr->move( rect );
137  }
138  objects.push_back( object_ptr );
139  }
140 }
141 
142 
143 /*
144  Draws the detected objects with a distance calculated at the center pixel of each face
145 */
147  cv::Mat & image,
149  openvino_helpers::detected_objects const & objects
150 )
151 {
152  cv::Scalar const green( 0, 255, 0 ); // BGR
153  cv::Scalar const white( 255, 255, 255 ); // BGR
154 
155  for( auto && object : objects )
156  {
157  auto r = object->get_location();
158  cv::rectangle( image, r, green );
159 
160  // Output the distance to the center
161  auto center_x = r.x + r.width / 2;
162  auto center_y = r.y + r.height / 2;
163  auto d = depth_frame.get_distance( center_x, center_y );
164  if( d )
165  {
166  std::ostringstream ss;
167  ss << object->get_label() << " ";
168  ss << std::setprecision( 2 ) << d;
169  ss << " meters away";
170  cv::putText( image, ss.str(), cv::Point( r.x + 5, r.y + r.height - 5 ), cv::FONT_HERSHEY_SIMPLEX, 0.4, white );
171  }
172  }
173 }
174 
175 
176 /*
177  When the user switches betweem models we show the detector number for 1 second as an
178  overlay over the image, centered.
179 */
181  cv::Mat & image,
182  size_t current_detector,
183  high_resolution_clock::time_point switch_time
184 )
185 {
186  auto ms_since_switch = duration_cast< milliseconds >( high_resolution_clock::now() - switch_time ).count();
187  if( ms_since_switch > 1000 )
188  ms_since_switch = 1000;
189  double alpha = ( 1000 - ms_since_switch ) / 1000.;
190  std::string str( 1, char( '1' + current_detector ) );
191  auto size = cv::getTextSize( str, cv::FONT_HERSHEY_SIMPLEX, 3, 1, nullptr );
192  cv::Point center{ image.cols / 2, image.rows / 2 };
193  cv::Rect r{ center.x - size.width, center.y - size.height, size.width * 2, size.height * 2 };
194  cv::Mat roi = image( r );
195  cv::Mat overlay( roi.size(), CV_8UC3, cv::Scalar( 32, 32, 32 ) );
196  cv::putText( overlay, str, cv::Point{ r.width / 2 - size.width / 2, r.height / 2 + size.height / 2 }, cv::FONT_HERSHEY_SIMPLEX, 3, cv::Scalar{ 255, 255, 255 } );
197  cv::addWeighted( overlay, alpha, roi, 1 - alpha, 0, roi ); // roi = overlay * alpha + roi * (1-alpha) + 0
198 }
199 
200 
201 int main(int argc, char * argv[]) try
202 {
203  el::Configurations conf;
204  conf.set( el::Level::Global, el::ConfigurationType::Format, "[%level] %msg" );
205  //conf.set( el::Level::Debug, el::ConfigurationType::Enabled, "false" );
206  el::Loggers::reconfigureLogger( "default", conf );
207  rs2::log_to_console( RS2_LOG_SEVERITY_WARN ); // only warnings (and above) should come through
208 
209  // Declare RealSense pipeline, encapsulating the actual device and sensors
211  pipe.start();
213 
214  // Start the inference engine, needed to accomplish anything. We also add a CPU extension, allowing
215  // us to run the inference on the CPU. A GPU solution may be possible but, at least without a GPU,
216  // a CPU-bound process is faster. To change to GPU, use "GPU" instead (and remove AddExtension()):
217  openvino::Core engine;
218  openvino_helpers::error_listener error_listener;
219  engine.SetLogCallback( error_listener );
220  std::string const device_name { "CPU" };
221 
222 #ifdef OPENVINO2019
223  engine.AddExtension(std::make_shared< openvino::Extensions::Cpu::CpuExtensions >(), device_name);
224 #endif
225 
226  std::vector< detector_and_labels > detectors;
227  load_detectors_into( detectors, engine, device_name );
228  if( detectors.empty() )
229  {
230  LOG(ERROR) << "No detectors available in: " << fs::getcwd();
231  return EXIT_FAILURE;
232  }
233  // Look for the mobilenet-ssd so it always starts the same... otherwise default to the first detector we found
234  size_t current_detector = 0;
235  for( size_t i = 1; i < detectors.size(); ++i )
236  {
237  if( detectors[i]->pathToModel == "mobilenet-ssd.xml" )
238  {
239  current_detector = i;
240  break;
241  }
242  }
243  auto p_detector = detectors[current_detector].detector;
244  LOG(INFO) << "Current detector set to (" << current_detector+1 << ") \"" << openvino_helpers::remove_ext( p_detector->pathToModel ) << "\"";
245  auto p_labels = &detectors[current_detector].labels;
246 
247  const auto window_name = "OpenVINO DNN sample";
248  cv::namedWindow( window_name, cv::WINDOW_AUTOSIZE );
249 
250  cv::Mat prev_image;
252  size_t id = 0;
253  uint64 last_frame_number = 0;
254  high_resolution_clock::time_point switch_time = high_resolution_clock::now();
255 
256  while( cv::getWindowProperty( window_name, cv::WND_PROP_AUTOSIZE ) >= 0 )
257  {
258  // Wait for the next set of frames
259  auto frames = pipe.wait_for_frames();
260  // Make sure the frames are spatially aligned
261  frames = align_to.process( frames );
262 
263  auto color_frame = frames.get_color_frame();
264  auto depth_frame = frames.get_depth_frame();
265  if( ! color_frame || ! depth_frame )
266  continue;
267 
268  // If we only received a new depth frame, but the color did not update, continue
269  if( color_frame.get_frame_number() == last_frame_number )
270  continue;
271  last_frame_number = color_frame.get_frame_number();
272 
273  auto image = frame_to_mat( color_frame );
274 
275  // We process the previous frame so if this is our first then queue it and continue
276  if( ! p_detector->_request )
277  {
278  p_detector->enqueue( image );
279  p_detector->submit_request();
280  prev_image = image;
281  continue;
282  }
283 
284  // Wait for the results of the previous frame we enqueued: we're going to process these
285  p_detector->wait();
286  auto const results = p_detector->fetch_results();
287 
288  // Enqueue the current frame so we'd get the results when the next frame comes along!
289  p_detector->enqueue( image );
290  p_detector->submit_request();
291 
292  // MAIN DETECTION
293  detect_objects( image, results, *p_labels, id, objects );
294 
295  // Keep it alive so we can actually process pieces of it once we have the results
296  prev_image = image;
297 
298  // Display the results (from the last frame) as rectangles on top (of the current frame)
299  draw_objects( image, depth_frame, objects );
300  draw_detector_overlay( image, current_detector, switch_time );
301  imshow( window_name, image );
302 
303  // Handle the keyboard before moving to the next frame
304  const int key = cv::waitKey( 1 );
305  if( key == 27 )
306  break; // escape
307  if( key >= '1' && key < '1' + detectors.size() )
308  {
309  size_t detector_index = key - '1';
310  if( detector_index != current_detector )
311  {
312  current_detector = detector_index;
313  p_detector = detectors[current_detector].detector;
314  p_labels = &detectors[current_detector].labels;
315  objects.clear();
316  LOG(INFO) << "Current detector set to (" << current_detector+1 << ") \"" << openvino_helpers::remove_ext( p_detector->pathToModel ) << "\"";
317  }
318  switch_time = high_resolution_clock::now();
319  }
320  }
321 
322  return EXIT_SUCCESS;
323 }
324 catch (const rs2::error & e)
325 {
326  LOG(ERROR) << "Caught RealSense exception from " << e.get_failed_function() << "(" << e.get_failed_args() << "):\n " << e.what();
327  return EXIT_FAILURE;
328 }
329 catch (const std::exception& e)
330 {
331  LOG(ERROR) << "Unknown exception caught: " << e.what();
332  return EXIT_FAILURE;
333 }
334 
frameset wait_for_frames(unsigned int timeout_ms=RS2_DEFAULT_TIMEOUT) const
static const ImVec4 white
Definition: model-views.h:45
static Logger * reconfigureLogger(Logger *logger, const Configurations &configurations)
Reconfigures specified logger with new configurations.
GLfloat green
int main(int argc, char *argv[])
std::string remove_ext(const std::string &filepath)
std::list< detected_object::ptr > detected_objects
void set(Level level, ConfigurationType configurationType, const std::string &value)
Sets value of configuration for specified level.
LOG(INFO)<< "Log message to default logger"
GLsizei const GLchar *const * string
d
Definition: rmse.py:171
e
Definition: rmse.py:177
GLenum GLenum GLsizei void * image
GLuint64 key
Definition: glext.h:8966
const std::string & get_failed_args() const
Definition: rs_types.hpp:117
Determines format of logging corresponding level and logger.
GLfloat GLfloat GLfloat alpha
GLsizeiptr size
#define INITIALIZE_EASYLOGGINGPP
GLdouble GLdouble r
frameset process(frameset frames)
void draw_detector_overlay(cv::Mat &image, size_t current_detector, high_resolution_clock::time_point switch_time)
void load_detectors_into(std::vector< detector_and_labels > &detectors, openvino::Core &engine, std::string const &device_name)
Definition: rs-dnn-vino.cpp:65
openvino_helpers::object_detection * operator->()
Definition: rs-dnn-vino.cpp:42
void draw_objects(cv::Mat &image, rs2::depth_frame depth_frame, openvino_helpers::detected_objects const &objects)
std::vector< std::string > read_labels(std::string const &filename)
void log_to_console(rs2_log_severity min_severity)
Definition: rs.hpp:19
Thread-safe Configuration repository.
Definition: example.hpp:70
float get_distance(int x, int y) const
Definition: rs_frame.hpp:833
GLuint GLsizei const GLchar * label
detected_object::ptr find_object(cv::Rect rect, detected_objects const &objects)
GLint GLsizei count
typename::boost::move_detail::remove_reference< T >::type && move(T &&t) BOOST_NOEXCEPT
detector_and_labels(std::string const &path_to_xml)
Definition: rs-dnn-vino.cpp:37
Generic level that represents all the levels. Useful when setting global configuration for all levels...
int i
#define INFO(msg)
Definition: catch.hpp:17429
pipeline_profile start()
std::vector< std::string > labels
Definition: rs-dnn-vino.cpp:35
void detect_objects(cv::Mat const &image, std::vector< openvino_helpers::object_detection::Result > const &results, std::vector< std::string > &labels, size_t &next_id, openvino_helpers::detected_objects &objects)
GLuint64EXT * result
Definition: glext.h:10921
static cv::Mat frame_to_mat(const rs2::frame &f)
Definition: cv-helpers.hpp:11
const std::string & get_failed_function() const
Definition: rs_types.hpp:112
std::shared_ptr< openvino_helpers::object_detection > detector
Definition: rs-dnn-vino.cpp:34
::geometry_msgs::Point_< std::allocator< void > > Point
Definition: Point.h:57


librealsense2
Author(s): Sergey Dorodnicov , Doron Hirshberg , Mark Horn , Reagan Lopez , Itay Carpis
autogenerated on Mon May 3 2021 02:47:40