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