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.
404 lines
14 KiB
404 lines
14 KiB
2 months ago
|
// 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
|
||
|
|
||
|
// This example will require several standard data-structures and algorithms:
|
||
|
#define _USE_MATH_DEFINES
|
||
|
#include <math.h>
|
||
|
#include <queue>
|
||
|
#include <unordered_set>
|
||
|
#include <map>
|
||
|
#include <thread>
|
||
|
#include <atomic>
|
||
|
#include <mutex>
|
||
|
|
||
|
using pixel = std::pair<int, int>;
|
||
|
|
||
|
// Distance 3D is used to calculate real 3D distance between two pixels
|
||
|
float dist_3d(const rs2::depth_frame& frame, pixel u, pixel v);
|
||
|
|
||
|
// Toggle helper class will be used to render the two buttons
|
||
|
// controlling the edges of our ruler
|
||
|
struct toggle
|
||
|
{
|
||
|
toggle() : x(0.f), y(0.f) {}
|
||
|
toggle(float xl, float yl)
|
||
|
: x(std::min(std::max(xl, 0.f), 1.f)),
|
||
|
y(std::min(std::max(yl, 0.f), 1.f))
|
||
|
{}
|
||
|
|
||
|
// Move from [0,1] space to pixel space of specific frame
|
||
|
pixel get_pixel(rs2::depth_frame frm) const
|
||
|
{
|
||
|
int px = static_cast<int>(x * frm.get_width());
|
||
|
int py = static_cast<int>(y * frm.get_height());
|
||
|
return{ px, py };
|
||
|
}
|
||
|
|
||
|
void render(const window& app)
|
||
|
{
|
||
|
glColor4f(0.f, 0.0f, 0.0f, 0.2f);
|
||
|
render_circle(app, 10);
|
||
|
render_circle(app, 8);
|
||
|
glColor4f(1.f, 0.9f, 1.0f, 1.f);
|
||
|
render_circle(app, 6);
|
||
|
}
|
||
|
|
||
|
void render_circle(const window& app, float r)
|
||
|
{
|
||
|
const float segments = 16;
|
||
|
glBegin(GL_TRIANGLE_STRIP);
|
||
|
for (auto i = 0; i <= segments; i++)
|
||
|
{
|
||
|
auto t = 2 * PI_FL * float(i) / segments;
|
||
|
|
||
|
glVertex2f(x * app.width() + cos(t) * r,
|
||
|
y * app.height() + sin(t) * r);
|
||
|
|
||
|
glVertex2f(x * app.width(),
|
||
|
y * app.height());
|
||
|
}
|
||
|
glEnd();
|
||
|
}
|
||
|
|
||
|
// This helper function is used to find the button
|
||
|
// closest to the mouse cursor
|
||
|
// Since we are only comparing this distance, sqrt can be safely skipped
|
||
|
float dist_2d(const toggle& other) const
|
||
|
{
|
||
|
return static_cast<float>(pow(x - other.x, 2) + pow(y - other.y, 2));
|
||
|
}
|
||
|
|
||
|
float x;
|
||
|
float y;
|
||
|
bool selected = false;
|
||
|
};
|
||
|
|
||
|
// Application state shared between the main-thread and GLFW events
|
||
|
struct state
|
||
|
{
|
||
|
bool mouse_down = false;
|
||
|
toggle ruler_start;
|
||
|
toggle ruler_end;
|
||
|
};
|
||
|
|
||
|
// Helper function to register to UI events
|
||
|
void register_glfw_callbacks(window& app, state& app_state);
|
||
|
|
||
|
// Distance rendering functions:
|
||
|
|
||
|
// Simple distance is the classic pythagorean distance between 3D points
|
||
|
// This distance ignores the topology of the object and can cut both through
|
||
|
// air and through solid
|
||
|
void render_simple_distance(const rs2::depth_frame& depth,
|
||
|
const state& s,
|
||
|
const window& app);
|
||
|
|
||
|
int main(int argc, char * argv[]) try
|
||
|
{
|
||
|
std::string serial;
|
||
|
if (!device_with_streams({ RS2_STREAM_COLOR,RS2_STREAM_DEPTH }, serial))
|
||
|
return EXIT_SUCCESS;
|
||
|
|
||
|
// OpenGL textures for the color and depth frames
|
||
|
texture depth_image, color_image;
|
||
|
|
||
|
// Colorizer is used to visualize depth data
|
||
|
rs2::colorizer color_map;
|
||
|
// Use black to white color map
|
||
|
color_map.set_option(RS2_OPTION_COLOR_SCHEME, 2.f);
|
||
|
// Decimation filter reduces the amount of data (while preserving best samples)
|
||
|
rs2::decimation_filter dec;
|
||
|
// If the demo is too slow, make sure you run in Release (-DCMAKE_BUILD_TYPE=Release)
|
||
|
// but you can also increase the following parameter to decimate depth more (reducing quality)
|
||
|
dec.set_option(RS2_OPTION_FILTER_MAGNITUDE, 2);
|
||
|
// Define transformations from and to Disparity domain
|
||
|
rs2::disparity_transform depth2disparity;
|
||
|
rs2::disparity_transform disparity2depth(false);
|
||
|
// Define spatial filter (edge-preserving)
|
||
|
rs2::spatial_filter spat;
|
||
|
// Enable hole-filling
|
||
|
// Hole filling is an agressive heuristic and it gets the depth wrong many times
|
||
|
// However, this demo is not built to handle holes
|
||
|
// (the shortest-path will always prefer to "cut" through the holes since they have zero 3D distance)
|
||
|
spat.set_option(RS2_OPTION_HOLES_FILL, 5); // 5 = fill all the zero pixels
|
||
|
// Define temporal filter
|
||
|
rs2::temporal_filter temp;
|
||
|
// Spatially align all streams to depth viewport
|
||
|
// We do this because:
|
||
|
// a. Usually depth has wider FOV, and we only really need depth for this demo
|
||
|
// b. We don't want to introduce new holes
|
||
|
rs2::align align_to(RS2_STREAM_DEPTH);
|
||
|
|
||
|
// Declare RealSense pipeline, encapsulating the actual device and sensors
|
||
|
rs2::pipeline pipe;
|
||
|
|
||
|
rs2::config cfg;
|
||
|
if (!serial.empty())
|
||
|
cfg.enable_device(serial);
|
||
|
cfg.enable_stream(RS2_STREAM_DEPTH); // Enable default depth
|
||
|
// For the color stream, set format to RGBA
|
||
|
// To allow blending of the color frame on top of the depth frame
|
||
|
cfg.enable_stream(RS2_STREAM_COLOR, RS2_FORMAT_RGBA8);
|
||
|
auto profile = pipe.start(cfg);
|
||
|
|
||
|
auto sensor = profile.get_device().first<rs2::depth_sensor>();
|
||
|
|
||
|
// Set the device to High Accuracy preset of the D400 stereoscopic cameras
|
||
|
if (sensor && sensor.is<rs2::depth_stereo_sensor>())
|
||
|
{
|
||
|
sensor.set_option(RS2_OPTION_VISUAL_PRESET, RS2_RS400_VISUAL_PRESET_HIGH_ACCURACY);
|
||
|
}
|
||
|
|
||
|
auto stream = profile.get_stream(RS2_STREAM_DEPTH).as<rs2::video_stream_profile>();
|
||
|
|
||
|
// Create a simple OpenGL window for rendering:
|
||
|
window app(stream.width(), stream.height(), "RealSense Measure Example");
|
||
|
|
||
|
// Define application state and position the ruler buttons
|
||
|
state app_state;
|
||
|
app_state.ruler_start = { 0.45f, 0.5f };
|
||
|
app_state.ruler_end = { 0.55f, 0.5f };
|
||
|
register_glfw_callbacks(app, app_state);
|
||
|
|
||
|
// After initial post-processing, frames will flow into this queue:
|
||
|
rs2::frame_queue postprocessed_frames;
|
||
|
|
||
|
// Alive boolean will signal the worker threads to finish-up
|
||
|
std::atomic_bool alive{ true };
|
||
|
|
||
|
// Video-processing thread will fetch frames from the camera,
|
||
|
// apply post-processing and send the result to the main thread for rendering
|
||
|
// It recieves synchronized (but not spatially aligned) pairs
|
||
|
// and outputs synchronized and aligned pairs
|
||
|
std::thread video_processing_thread([&]() {
|
||
|
while (alive)
|
||
|
{
|
||
|
// Fetch frames from the pipeline and send them for processing
|
||
|
rs2::frameset data;
|
||
|
if (pipe.poll_for_frames(&data))
|
||
|
{
|
||
|
// First make the frames spatially aligned
|
||
|
data = data.apply_filter(align_to);
|
||
|
|
||
|
// Decimation will reduce the resultion of the depth image,
|
||
|
// closing small holes and speeding-up the algorithm
|
||
|
data = data.apply_filter(dec);
|
||
|
|
||
|
// To make sure far-away objects are filtered proportionally
|
||
|
// we try to switch to disparity domain
|
||
|
data = data.apply_filter(depth2disparity);
|
||
|
|
||
|
// Apply spatial filtering
|
||
|
data = data.apply_filter(spat);
|
||
|
|
||
|
// Apply temporal filtering
|
||
|
data = data.apply_filter(temp);
|
||
|
|
||
|
// If we are in disparity domain, switch back to depth
|
||
|
data = data.apply_filter(disparity2depth);
|
||
|
|
||
|
//// Apply color map for visualization of depth
|
||
|
data = data.apply_filter(color_map);
|
||
|
|
||
|
// Send resulting frames for visualization in the main thread
|
||
|
postprocessed_frames.enqueue(data);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
rs2::frameset current_frameset;
|
||
|
while(app) // Application still alive?
|
||
|
{
|
||
|
// Fetch the latest available post-processed frameset
|
||
|
postprocessed_frames.poll_for_frame(¤t_frameset);
|
||
|
|
||
|
if (current_frameset)
|
||
|
{
|
||
|
auto depth = current_frameset.get_depth_frame();
|
||
|
auto color = current_frameset.get_color_frame();
|
||
|
auto colorized_depth = current_frameset.first(RS2_STREAM_DEPTH, RS2_FORMAT_RGB8);
|
||
|
|
||
|
glEnable(GL_BLEND);
|
||
|
// Use the Alpha channel for blending
|
||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||
|
|
||
|
// First render the colorized depth image
|
||
|
depth_image.render(colorized_depth, { 0, 0, app.width(), app.height() });
|
||
|
|
||
|
// Render the color frame (since we have selected RGBA format
|
||
|
// pixels out of FOV will appear transparent)
|
||
|
color_image.render(color, { 0, 0, app.width(), app.height() });
|
||
|
|
||
|
// Render the simple pythagorean distance
|
||
|
render_simple_distance(depth, app_state, app);
|
||
|
|
||
|
// Render the ruler
|
||
|
app_state.ruler_start.render(app);
|
||
|
app_state.ruler_end.render(app);
|
||
|
|
||
|
glColor3f(1.f, 1.f, 1.f);
|
||
|
glDisable(GL_BLEND);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Signal threads to finish and wait until they do
|
||
|
alive = false;
|
||
|
video_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;
|
||
|
}
|
||
|
|
||
|
float dist_3d(const rs2::depth_frame& frame, pixel u, pixel v)
|
||
|
{
|
||
|
float upixel[2]; // From pixel
|
||
|
float upoint[3]; // From point (in 3D)
|
||
|
|
||
|
float vpixel[2]; // To pixel
|
||
|
float vpoint[3]; // To point (in 3D)
|
||
|
|
||
|
// Copy pixels into the arrays (to match rsutil signatures)
|
||
|
upixel[0] = static_cast<float>(u.first);
|
||
|
upixel[1] = static_cast<float>(u.second);
|
||
|
vpixel[0] = static_cast<float>(v.first);
|
||
|
vpixel[1] = static_cast<float>(v.second);
|
||
|
|
||
|
// Query the frame for distance
|
||
|
// Note: this can be optimized
|
||
|
// It is not recommended to issue an API call for each pixel
|
||
|
// (since the compiler can't inline these)
|
||
|
// However, in this example it is not one of the bottlenecks
|
||
|
auto udist = frame.get_distance(static_cast<int>(upixel[0]), static_cast<int>(upixel[1]));
|
||
|
auto vdist = frame.get_distance(static_cast<int>(vpixel[0]), static_cast<int>(vpixel[1]));
|
||
|
|
||
|
// Deproject from pixel to point in 3D
|
||
|
rs2_intrinsics intr = frame.get_profile().as<rs2::video_stream_profile>().get_intrinsics(); // Calibration data
|
||
|
rs2_deproject_pixel_to_point(upoint, &intr, upixel, udist);
|
||
|
rs2_deproject_pixel_to_point(vpoint, &intr, vpixel, vdist);
|
||
|
|
||
|
// Calculate euclidean distance between the two points
|
||
|
return sqrt(pow(upoint[0] - vpoint[0], 2.f) +
|
||
|
pow(upoint[1] - vpoint[1], 2.f) +
|
||
|
pow(upoint[2] - vpoint[2], 2.f));
|
||
|
}
|
||
|
|
||
|
void draw_line(float x0, float y0, float x1, float y1, int width)
|
||
|
{
|
||
|
glPushAttrib(GL_ENABLE_BIT);
|
||
|
glLineStipple(1, 0x00ff);
|
||
|
glEnable(GL_LINE_STIPPLE);
|
||
|
glLineWidth(static_cast<float>(width));
|
||
|
glBegin(GL_LINE_STRIP);
|
||
|
glVertex2f(x0, y0);
|
||
|
glVertex2f(x1, y1);
|
||
|
glEnd();
|
||
|
glPopAttrib();
|
||
|
}
|
||
|
|
||
|
void render_simple_distance(const rs2::depth_frame& depth,
|
||
|
const state& s,
|
||
|
const window& app)
|
||
|
{
|
||
|
pixel center;
|
||
|
|
||
|
glColor4f(0.f, 0.0f, 0.0f, 0.2f);
|
||
|
draw_line(s.ruler_start.x * app.width(),
|
||
|
s.ruler_start.y * app.height(),
|
||
|
s.ruler_end.x * app.width(),
|
||
|
s.ruler_end.y * app.height(), 9);
|
||
|
|
||
|
glColor4f(0.f, 0.0f, 0.0f, 0.3f);
|
||
|
draw_line(s.ruler_start.x * app.width(),
|
||
|
s.ruler_start.y * app.height(),
|
||
|
s.ruler_end.x * app.width(),
|
||
|
s.ruler_end.y * app.height(), 7);
|
||
|
|
||
|
glColor4f(1.f, 1.0f, 1.0f, 1.f);
|
||
|
draw_line(s.ruler_start.x * app.width(),
|
||
|
s.ruler_start.y * app.height(),
|
||
|
s.ruler_end.x * app.width(),
|
||
|
s.ruler_end.y * app.height(), 3);
|
||
|
|
||
|
auto from_pixel = s.ruler_start.get_pixel(depth);
|
||
|
auto to_pixel = s.ruler_end.get_pixel(depth);
|
||
|
float air_dist = dist_3d(depth, from_pixel, to_pixel);
|
||
|
|
||
|
center.first = (from_pixel.first + to_pixel.first) / 2;
|
||
|
center.second = (from_pixel.second + to_pixel.second) / 2;
|
||
|
|
||
|
std::stringstream ss;
|
||
|
ss << int(air_dist * 100) << " cm";
|
||
|
auto str = ss.str();
|
||
|
auto x = (float(center.first) / depth.get_width()) * app.width() + 15;
|
||
|
auto y = (float(center.second) / depth.get_height()) * app.height() + 15;
|
||
|
|
||
|
auto w = stb_easy_font_width((char*)str.c_str());
|
||
|
|
||
|
// Draw dark background for the text label
|
||
|
glColor4f(0.f, 0.f, 0.f, 0.4f);
|
||
|
glBegin(GL_TRIANGLES);
|
||
|
glVertex2f(x - 3, y - 10);
|
||
|
glVertex2f(x + w + 2, y - 10);
|
||
|
glVertex2f(x + w + 2, y + 2);
|
||
|
glVertex2f(x + w + 2, y + 2);
|
||
|
glVertex2f(x - 3, y + 2);
|
||
|
glVertex2f(x - 3, y - 10);
|
||
|
glEnd();
|
||
|
|
||
|
// Draw white text label
|
||
|
glColor4f(1.f, 1.f, 1.f, 1.f);
|
||
|
draw_text(static_cast<int>(x), static_cast<int>(y), str.c_str());
|
||
|
}
|
||
|
|
||
|
// Implement drag & drop behavior for the buttons:
|
||
|
void register_glfw_callbacks(window& app, state& app_state)
|
||
|
{
|
||
|
app.on_left_mouse = [&](bool pressed)
|
||
|
{
|
||
|
app_state.mouse_down = pressed;
|
||
|
};
|
||
|
|
||
|
app.on_mouse_move = [&](double x, double y)
|
||
|
{
|
||
|
toggle cursor{ float(x) / app.width(), float(y) / app.height() };
|
||
|
std::vector<toggle*> toggles{
|
||
|
&app_state.ruler_start,
|
||
|
&app_state.ruler_end };
|
||
|
|
||
|
if (app_state.mouse_down)
|
||
|
{
|
||
|
toggle* best = toggles.front();
|
||
|
for (auto&& t : toggles)
|
||
|
{
|
||
|
if (t->dist_2d(cursor) < best->dist_2d(cursor))
|
||
|
{
|
||
|
best = t;
|
||
|
}
|
||
|
}
|
||
|
best->selected = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for (auto&& t : toggles) t->selected = false;
|
||
|
}
|
||
|
|
||
|
for (auto&& t : toggles)
|
||
|
{
|
||
|
if (t->selected) *t = cursor;
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|