You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

392 lines
15 KiB

// 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.f, int(h) / 2.f, 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.f, int(h) / 2.f, 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())
{
}