forked from IntelRealSense/librealsense
-
Notifications
You must be signed in to change notification settings - Fork 2
/
rs-post-processing.cpp
391 lines (339 loc) · 14.9 KB
/
rs-post-processing.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
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2017 Intel Corporation. All Rights Reserved.
#include <librealsense2/rs.hpp> // Include RealSense Cross Platform API
#include "example.hpp" // Include short list of convenience functions for rendering
#include <map>
#include <string>
#include <thread>
#include <atomic>
#include <imgui.h>
#include "imgui_impl_glfw.h"
/**
Helper class for controlling the filter's GUI element
*/
struct filter_slider_ui
{
std::string name;
std::string label;
std::string description;
bool is_int;
float value;
rs2::option_range range;
bool render(const float3& location, bool enabled);
static bool is_all_integers(const rs2::option_range& range);
};
/**
Class to encapsulate a filter alongside its options
*/
class filter_options
{
public:
filter_options(const std::string name, rs2::filter& filter);
filter_options(filter_options&& other);
std::string filter_name; //Friendly name of the filter
rs2::filter& filter; //The filter in use
std::map<rs2_option, filter_slider_ui> supported_options; //maps from an option supported by the filter, to the corresponding slider
std::atomic_bool is_enabled; //A boolean controlled by the user that determines whether to apply the filter or not
};
// Helper functions for rendering the UI
void render_ui(float w, float h, std::vector<filter_options>& filters);
// Helper function for getting data from the queues and updating the view
void update_data(rs2::frame_queue& data, rs2::frame& depth, rs2::points& points, rs2::pointcloud& pc, glfw_state& view, rs2::colorizer& color_map);
int main(int argc, char * argv[]) try
{
// Create a simple OpenGL window for rendering:
window app(1280, 720, "RealSense Post Processing Example");
ImGui_ImplGlfw_Init(app, false);
// Construct objects to manage view state
glfw_state original_view_orientation{};
glfw_state filtered_view_orientation{};
// Declare pointcloud objects, for calculating pointclouds and texture mappings
rs2::pointcloud original_pc;
rs2::pointcloud filtered_pc;
// Declare RealSense pipeline, encapsulating the actual device and sensors
rs2::pipeline pipe;
rs2::config cfg;
// Use a configuration object to request only depth from the pipeline
cfg.enable_stream(RS2_STREAM_DEPTH, 640, 0, RS2_FORMAT_Z16, 30);
// Start streaming with the above configuration
pipe.start(cfg);
// Declare filters
rs2::decimation_filter dec_filter; // Decimation - reduces depth frame density
rs2::threshold_filter thr_filter; // Threshold - removes values outside recommended range
rs2::spatial_filter spat_filter; // Spatial - edge-preserving spatial smoothing
rs2::temporal_filter temp_filter; // Temporal - reduces temporal noise
// Declare disparity transform from depth to disparity and vice versa
const std::string disparity_filter_name = "Disparity";
rs2::disparity_transform depth_to_disparity(true);
rs2::disparity_transform disparity_to_depth(false);
// Initialize a vector that holds filters and their options
std::vector<filter_options> filters;
// The following order of emplacement will dictate the orders in which filters are applied
filters.emplace_back("Decimate", dec_filter);
filters.emplace_back("Threshold", thr_filter);
filters.emplace_back(disparity_filter_name, depth_to_disparity);
filters.emplace_back("Spatial", spat_filter);
filters.emplace_back("Temporal", temp_filter);
// Declaring two concurrent queues that will be used to enqueue and dequeue frames from different threads
rs2::frame_queue original_data;
rs2::frame_queue filtered_data;
// Declare depth colorizer for pretty visualization of depth data
rs2::colorizer color_map;
// Atomic boolean to allow thread safe way to stop the thread
std::atomic_bool stopped(false);
// Create a thread for getting frames from the device and process them
// to prevent UI thread from blocking due to long computations.
std::thread processing_thread([&]() {
while (!stopped) //While application is running
{
rs2::frameset data = pipe.wait_for_frames(); // Wait for next set of frames from the camera
rs2::frame depth_frame = data.get_depth_frame(); //Take the depth frame from the frameset
if (!depth_frame) // Should not happen but if the pipeline is configured differently
return; // it might not provide depth and we don't want to crash
rs2::frame filtered = depth_frame; // Does not copy the frame, only adds a reference
/* Apply filters.
The implemented flow of the filters pipeline is in the following order:
1. apply decimation filter
2. apply threshold filter
3. transform the scene into disparity domain
4. apply spatial filter
5. apply temporal filter
6. revert the results back (if step Disparity filter was applied
to depth domain (each post processing block is optional and can be applied independantly).
*/
bool revert_disparity = false;
for (auto&& filter : filters)
{
if (filter.is_enabled)
{
filtered = filter.filter.process(filtered);
if (filter.filter_name == disparity_filter_name)
{
revert_disparity = true;
}
}
}
if (revert_disparity)
{
filtered = disparity_to_depth.process(filtered);
}
// Push filtered & original data to their respective queues
// Note, pushing to two different queues might cause the application to display
// original and filtered pointclouds from different depth frames
// To make sure they are synchronized you need to push them together or add some
// synchronization mechanisms
filtered_data.enqueue(filtered);
original_data.enqueue(depth_frame);
}
});
// Declare objects that will hold the calculated pointclouds and colored frames
// We save the last set of data to minimize flickering of the view
rs2::frame colored_depth;
rs2::frame colored_filtered;
rs2::points original_points;
rs2::points filtered_points;
// Save the time of last frame's arrival
auto last_time = std::chrono::high_resolution_clock::now();
// Maximum angle for the rotation of the pointcloud
const double max_angle = 15.0;
// We'll use rotation_velocity to rotate the pointcloud for a better view of the filters effects
float rotation_velocity = 0.3f;
while (app)
{
float w = static_cast<float>(app.width());
float h = static_cast<float>(app.height());
// Render the GUI
render_ui(w, h, filters);
// Try to get new data from the queues and update the view with new texture
update_data(original_data, colored_depth, original_points, original_pc, original_view_orientation, color_map);
update_data(filtered_data, colored_filtered, filtered_points, filtered_pc, filtered_view_orientation, color_map);
draw_text(10, 50, "Original");
draw_text(static_cast<int>(w / 2), 50, "Filtered");
// Draw the pointclouds of the original and the filtered frames (if the are available already)
if (colored_depth && original_points)
{
glViewport(0, int(h) / 2, int(w) / 2, int(h) / 2);
draw_pointcloud(int(w) / 2, int(h) / 2, original_view_orientation, original_points);
}
if (colored_filtered && filtered_points)
{
glViewport(int(w) / 2, int(h) / 2, int(w) / 2, int(h) / 2);
draw_pointcloud(int(w) / 2, int(h) / 2, filtered_view_orientation, filtered_points);
}
// Update time of current frame's arrival
auto curr = std::chrono::high_resolution_clock::now();
// Time interval which must pass between movement of the pointcloud
const std::chrono::milliseconds rotation_delta(40);
// In order to calibrate the velocity of the rotation to the actual displaying speed, rotate
// pointcloud only when enough time has passed between frames
if (curr - last_time > rotation_delta)
{
if (fabs(filtered_view_orientation.yaw) > max_angle)
{
rotation_velocity = -rotation_velocity;
}
original_view_orientation.yaw += rotation_velocity;
filtered_view_orientation.yaw += rotation_velocity;
last_time = curr;
}
}
// Signal the processing thread to stop, and join
// (Not the safest way to join a thread, please wrap your threads in some RAII manner)
stopped = true;
processing_thread.join();
return EXIT_SUCCESS;
}
catch (const rs2::error & e)
{
std::cerr << "RealSense error calling " << e.get_failed_function() << "(" << e.get_failed_args() << "):\n " << e.what() << std::endl;
return EXIT_FAILURE;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
void update_data(rs2::frame_queue& data, rs2::frame& colorized_depth, rs2::points& points, rs2::pointcloud& pc, glfw_state& view, rs2::colorizer& color_map)
{
rs2::frame f;
if (data.poll_for_frame(&f)) // Try to take the depth and points from the queue
{
points = pc.calculate(f); // Generate pointcloud from the depth data
colorized_depth = color_map.process(f); // Colorize the depth frame with a color map
pc.map_to(colorized_depth); // Map the colored depth to the point cloud
view.tex.upload(colorized_depth); // and upload the texture to the view (without this the view will be B&W)
}
}
void render_ui(float w, float h, std::vector<filter_options>& filters)
{
// Flags for displaying ImGui window
static const int flags = ImGuiWindowFlags_NoCollapse
| ImGuiWindowFlags_NoScrollbar
| ImGuiWindowFlags_NoSavedSettings
| ImGuiWindowFlags_NoTitleBar
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoMove;
ImGui_ImplGlfw_NewFrame(1);
ImGui::SetNextWindowSize({ w, h });
ImGui::Begin("app", nullptr, flags);
// Using ImGui library to provide slide controllers for adjusting the filter options
const float offset_x = w / 4;
const int offset_from_checkbox = 120;
float offset_y = h / 2;
float elements_margin = 45;
for (auto& filter : filters)
{
// Draw a checkbox per filter to toggle if it should be applied
ImGui::SetCursorPos({ offset_x, offset_y });
ImGui::PushStyleColor(ImGuiCol_CheckMark, { 40 / 255.f, 170 / 255.f, 90 / 255.f, 1 });
bool tmp_value = filter.is_enabled;
ImGui::Checkbox(filter.filter_name.c_str(), &tmp_value);
filter.is_enabled = tmp_value;
ImGui::PopStyleColor();
if (filter.supported_options.size() == 0)
{
offset_y += elements_margin;
}
// Draw a slider for each of the filter's options
for (auto& option_slider_pair : filter.supported_options)
{
filter_slider_ui& slider = option_slider_pair.second;
if (slider.render({ offset_x + offset_from_checkbox, offset_y, w / 4 }, filter.is_enabled))
{
filter.filter.set_option(option_slider_pair.first, slider.value);
}
offset_y += elements_margin;
}
}
ImGui::End();
ImGui::Render();
}
bool filter_slider_ui::render(const float3& location, bool enabled)
{
bool value_changed = false;
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 12);
ImGui::PushStyleColor(ImGuiCol_SliderGrab, { 40 / 255.f, 170 / 255.f, 90 / 255.f, 1 });
ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, { 20 / 255.f, 150 / 255.f, 70 / 255.f, 1 });
ImGui::GetStyle().GrabRounding = 12;
if (!enabled)
{
ImGui::PushStyleColor(ImGuiCol_SliderGrab, { 0,0,0,0 });
ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, { 0,0,0,0 });
ImGui::PushStyleColor(ImGuiCol_Text, { 0.6f, 0.6f, 0.6f, 1 });
}
ImGui::PushItemWidth(location.z);
ImGui::SetCursorPos({ location.x, location.y + 3 });
ImGui::TextUnformatted(label.c_str());
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", description.c_str());
ImGui::SetCursorPos({ location.x + 170, location.y });
if (is_int)
{
int value_as_int = static_cast<int>(value);
value_changed = ImGui::SliderInt(("##" + name).c_str(), &value_as_int, static_cast<int>(range.min), static_cast<int>(range.max), "%.0f");
value = static_cast<float>(value_as_int);
}
else
{
value_changed = ImGui::SliderFloat(("##" + name).c_str(), &value, range.min, range.max, "%.3f", 1.0f);
}
ImGui::PopItemWidth();
if (!enabled)
{
ImGui::PopStyleColor(3);
}
ImGui::PopStyleVar();
ImGui::PopStyleColor(2);
return value_changed;
}
/**
Helper function for deciding on int ot float slider
*/
bool filter_slider_ui::is_all_integers(const rs2::option_range& range)
{
const auto is_integer = [](float f)
{
return (fabs(fmod(f, 1)) < std::numeric_limits<float>::min());
};
return is_integer(range.min) && is_integer(range.max) &&
is_integer(range.def) && is_integer(range.step);
}
/**
Constructor for filter_options, takes a name and a filter.
*/
filter_options::filter_options(const std::string name, rs2::filter& flt) :
filter_name(name),
filter(flt),
is_enabled(true)
{
const std::array<rs2_option, 5> possible_filter_options = {
RS2_OPTION_FILTER_MAGNITUDE,
RS2_OPTION_FILTER_SMOOTH_ALPHA,
RS2_OPTION_MIN_DISTANCE,
RS2_OPTION_MAX_DISTANCE,
RS2_OPTION_FILTER_SMOOTH_DELTA
};
//Go over each filter option and create a slider for it
for (rs2_option opt : possible_filter_options)
{
if (flt.supports(opt))
{
rs2::option_range range = flt.get_option_range(opt);
supported_options[opt].range = range;
supported_options[opt].value = range.def;
supported_options[opt].is_int = filter_slider_ui::is_all_integers(range);
supported_options[opt].description = flt.get_option_description(opt);
std::string opt_name = flt.get_option_name(opt);
supported_options[opt].name = name + "_" + opt_name;
std::string prefix = "Filter ";
supported_options[opt].label = opt_name;
}
}
}
filter_options::filter_options(filter_options&& other) :
filter_name(std::move(other.filter_name)),
filter(other.filter),
supported_options(std::move(other.supported_options)),
is_enabled(other.is_enabled.load())
{
}