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.
300 lines
9.6 KiB
300 lines
9.6 KiB
2 months ago
|
// License: Apache 2.0. See LICENSE file in root directory.
|
||
|
// Copyright(c) 2019 Intel Corporation. All Rights Reserved.
|
||
|
#include <librealsense2/rs.hpp>
|
||
|
#include <mutex>
|
||
|
#include "example.hpp" // Include short list of convenience functions for rendering
|
||
|
#include <cstring>
|
||
|
|
||
|
struct short3
|
||
|
{
|
||
|
uint16_t x, y, z;
|
||
|
};
|
||
|
|
||
|
#include "d435.h"
|
||
|
|
||
|
void draw_axes()
|
||
|
{
|
||
|
glLineWidth(2);
|
||
|
glBegin(GL_LINES);
|
||
|
// Draw x, y, z axes
|
||
|
glColor3f(1, 0, 0); glVertex3f(0, 0, 0); glVertex3f(-1, 0, 0);
|
||
|
glColor3f(0, 1, 0); glVertex3f(0, 0, 0); glVertex3f(0, -1, 0);
|
||
|
glColor3f(0, 0, 1); glVertex3f(0, 0, 0); glVertex3f(0, 0, 1);
|
||
|
glEnd();
|
||
|
|
||
|
glLineWidth(1);
|
||
|
}
|
||
|
|
||
|
void draw_floor()
|
||
|
{
|
||
|
glBegin(GL_LINES);
|
||
|
glColor4f(0.4f, 0.4f, 0.4f, 1.f);
|
||
|
// Render "floor" grid
|
||
|
for (int i = 0; i <= 8; i++)
|
||
|
{
|
||
|
glVertex3i(i - 4, 1, 0);
|
||
|
glVertex3i(i - 4, 1, 8);
|
||
|
glVertex3i(-4, 1, i);
|
||
|
glVertex3i(4, 1, i);
|
||
|
}
|
||
|
glEnd();
|
||
|
}
|
||
|
|
||
|
void render_scene(glfw_state app_state)
|
||
|
{
|
||
|
glClearColor(0.0, 0.0, 0.0, 1.0);
|
||
|
glColor3f(1.0, 1.0, 1.0);
|
||
|
|
||
|
glMatrixMode(GL_PROJECTION);
|
||
|
glLoadIdentity();
|
||
|
gluPerspective(60.0, 4.0 / 3.0, 1, 40);
|
||
|
|
||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||
|
glMatrixMode(GL_MODELVIEW);
|
||
|
|
||
|
glLoadIdentity();
|
||
|
gluLookAt(1, 0, 5, 1, 0, 0, 0, -1, 0);
|
||
|
|
||
|
glTranslatef(0, 0, +0.5f + app_state.offset_y*0.05f);
|
||
|
glRotated(app_state.pitch, -1, 0, 0);
|
||
|
glRotated(app_state.yaw, 0, 1, 0);
|
||
|
draw_floor();
|
||
|
}
|
||
|
|
||
|
class camera_renderer
|
||
|
{
|
||
|
std::vector<float3> positions, normals;
|
||
|
std::vector<short3> indexes;
|
||
|
public:
|
||
|
// Initialize renderer with data needed to draw the camera
|
||
|
camera_renderer()
|
||
|
{
|
||
|
uncompress_d435_obj(positions, normals, indexes);
|
||
|
}
|
||
|
|
||
|
// Takes the calculated angle as input and rotates the 3D camera model accordignly
|
||
|
void render_camera(float3 theta)
|
||
|
{
|
||
|
|
||
|
glEnable(GL_BLEND);
|
||
|
glBlendFunc(GL_ONE, GL_ONE);
|
||
|
|
||
|
glPushMatrix();
|
||
|
// Set the rotation, converting theta to degrees
|
||
|
glRotatef(theta.x * 180 / PI_FL, 0, 0, -1);
|
||
|
glRotatef(theta.y * 180 / PI_FL, 0, -1, 0);
|
||
|
glRotatef((theta.z - PI_FL / 2) * 180 / PI_FL, -1, 0, 0);
|
||
|
|
||
|
draw_axes();
|
||
|
|
||
|
// Scale camera drawing
|
||
|
glScalef(0.035f, 0.035f, 0.035f);
|
||
|
|
||
|
glBegin(GL_TRIANGLES);
|
||
|
// Draw the camera
|
||
|
for (auto& i : indexes)
|
||
|
{
|
||
|
glVertex3fv(&positions[i.x].x);
|
||
|
glVertex3fv(&positions[i.y].x);
|
||
|
glVertex3fv(&positions[i.z].x);
|
||
|
glColor4f(0.05f, 0.05f, 0.05f, 0.3f);
|
||
|
}
|
||
|
glEnd();
|
||
|
|
||
|
glPopMatrix();
|
||
|
|
||
|
glDisable(GL_BLEND);
|
||
|
glFlush();
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
class rotation_estimator
|
||
|
{
|
||
|
// theta is the angle of camera rotation in x, y and z components
|
||
|
float3 theta;
|
||
|
std::mutex theta_mtx;
|
||
|
/* alpha indicates the part that gyro and accelerometer take in computation of theta; higher alpha gives more weight to gyro, but too high
|
||
|
values cause drift; lower alpha gives more weight to accelerometer, which is more sensitive to disturbances */
|
||
|
float alpha = 0.98f;
|
||
|
bool firstGyro = true;
|
||
|
bool firstAccel = true;
|
||
|
// Keeps the arrival time of previous gyro frame
|
||
|
double last_ts_gyro = 0;
|
||
|
public:
|
||
|
// Function to calculate the change in angle of motion based on data from gyro
|
||
|
void process_gyro(rs2_vector gyro_data, double ts)
|
||
|
{
|
||
|
if (firstGyro) // On the first iteration, use only data from accelerometer to set the camera's initial position
|
||
|
{
|
||
|
firstGyro = false;
|
||
|
last_ts_gyro = ts;
|
||
|
return;
|
||
|
}
|
||
|
// Holds the change in angle, as calculated from gyro
|
||
|
float3 gyro_angle;
|
||
|
|
||
|
// Initialize gyro_angle with data from gyro
|
||
|
gyro_angle.x = gyro_data.x; // Pitch
|
||
|
gyro_angle.y = gyro_data.y; // Yaw
|
||
|
gyro_angle.z = gyro_data.z; // Roll
|
||
|
|
||
|
// Compute the difference between arrival times of previous and current gyro frames
|
||
|
double dt_gyro = (ts - last_ts_gyro) / 1000.0;
|
||
|
last_ts_gyro = ts;
|
||
|
|
||
|
// Change in angle equals gyro measures * time passed since last measurement
|
||
|
gyro_angle = gyro_angle * static_cast<float>(dt_gyro);
|
||
|
|
||
|
// Apply the calculated change of angle to the current angle (theta)
|
||
|
std::lock_guard<std::mutex> lock(theta_mtx);
|
||
|
theta.add(-gyro_angle.z, -gyro_angle.y, gyro_angle.x);
|
||
|
}
|
||
|
|
||
|
void process_accel(rs2_vector accel_data)
|
||
|
{
|
||
|
// Holds the angle as calculated from accelerometer data
|
||
|
float3 accel_angle;
|
||
|
|
||
|
// Calculate rotation angle from accelerometer data
|
||
|
accel_angle.z = atan2(accel_data.y, accel_data.z);
|
||
|
accel_angle.x = atan2(accel_data.x, sqrt(accel_data.y * accel_data.y + accel_data.z * accel_data.z));
|
||
|
|
||
|
// If it is the first iteration, set initial pose of camera according to accelerometer data (note the different handling for Y axis)
|
||
|
std::lock_guard<std::mutex> lock(theta_mtx);
|
||
|
if (firstAccel)
|
||
|
{
|
||
|
firstAccel = false;
|
||
|
theta = accel_angle;
|
||
|
// Since we can't infer the angle around Y axis using accelerometer data, we'll use PI as a convetion for the initial pose
|
||
|
theta.y = PI_FL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/*
|
||
|
Apply Complementary Filter:
|
||
|
- high-pass filter = theta * alpha: allows short-duration signals to pass through while filtering out signals
|
||
|
that are steady over time, is used to cancel out drift.
|
||
|
- low-pass filter = accel * (1- alpha): lets through long term changes, filtering out short term fluctuations
|
||
|
*/
|
||
|
theta.x = theta.x * alpha + accel_angle.x * (1 - alpha);
|
||
|
theta.z = theta.z * alpha + accel_angle.z * (1 - alpha);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Returns the current rotation angle
|
||
|
float3 get_theta()
|
||
|
{
|
||
|
std::lock_guard<std::mutex> lock(theta_mtx);
|
||
|
return theta;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
bool check_imu_is_supported()
|
||
|
{
|
||
|
bool found_gyro = false;
|
||
|
bool found_accel = false;
|
||
|
rs2::context ctx;
|
||
|
for (auto dev : ctx.query_devices())
|
||
|
{
|
||
|
// The same device should support gyro and accel
|
||
|
found_gyro = false;
|
||
|
found_accel = false;
|
||
|
for (auto sensor : dev.query_sensors())
|
||
|
{
|
||
|
for (auto profile : sensor.get_stream_profiles())
|
||
|
{
|
||
|
if (profile.stream_type() == RS2_STREAM_GYRO)
|
||
|
found_gyro = true;
|
||
|
|
||
|
if (profile.stream_type() == RS2_STREAM_ACCEL)
|
||
|
found_accel = true;
|
||
|
}
|
||
|
}
|
||
|
if (found_gyro && found_accel)
|
||
|
break;
|
||
|
}
|
||
|
return found_gyro && found_accel;
|
||
|
}
|
||
|
|
||
|
int main(int argc, char * argv[]) try
|
||
|
{
|
||
|
// Before running the example, check that a device supporting IMU is connected
|
||
|
if (!check_imu_is_supported())
|
||
|
{
|
||
|
std::cerr << "Device supporting IMU not found";
|
||
|
return EXIT_FAILURE;
|
||
|
}
|
||
|
|
||
|
// Initialize window for rendering
|
||
|
window app(1280, 720, "RealSense Motion Example");
|
||
|
// Construct an object to manage view state
|
||
|
glfw_state app_state(0.0, 0.0);
|
||
|
// Register callbacks to allow manipulation of the view state
|
||
|
register_glfw_callbacks(app, app_state);
|
||
|
|
||
|
// Declare RealSense pipeline, encapsulating the actual device and sensors
|
||
|
rs2::pipeline pipe;
|
||
|
// Create a configuration for configuring the pipeline with a non default profile
|
||
|
rs2::config cfg;
|
||
|
|
||
|
// Add streams of gyro and accelerometer to configuration
|
||
|
cfg.enable_stream(RS2_STREAM_ACCEL, RS2_FORMAT_MOTION_XYZ32F);
|
||
|
cfg.enable_stream(RS2_STREAM_GYRO, RS2_FORMAT_MOTION_XYZ32F);
|
||
|
|
||
|
// Declare object for rendering camera motion
|
||
|
camera_renderer camera;
|
||
|
// Declare object that handles camera pose calculations
|
||
|
rotation_estimator algo;
|
||
|
|
||
|
// Start streaming with the given configuration;
|
||
|
// Note that since we only allow IMU streams, only single frames are produced
|
||
|
auto profile = pipe.start(cfg, [&](rs2::frame frame)
|
||
|
{
|
||
|
// Cast the frame that arrived to motion frame
|
||
|
auto motion = frame.as<rs2::motion_frame>();
|
||
|
// If casting succeeded and the arrived frame is from gyro stream
|
||
|
if (motion && motion.get_profile().stream_type() == RS2_STREAM_GYRO && motion.get_profile().format() == RS2_FORMAT_MOTION_XYZ32F)
|
||
|
{
|
||
|
// Get the timestamp of the current frame
|
||
|
double ts = motion.get_timestamp();
|
||
|
// Get gyro measures
|
||
|
rs2_vector gyro_data = motion.get_motion_data();
|
||
|
// Call function that computes the angle of motion based on the retrieved measures
|
||
|
algo.process_gyro(gyro_data, ts);
|
||
|
}
|
||
|
// If casting succeeded and the arrived frame is from accelerometer stream
|
||
|
if (motion && motion.get_profile().stream_type() == RS2_STREAM_ACCEL && motion.get_profile().format() == RS2_FORMAT_MOTION_XYZ32F)
|
||
|
{
|
||
|
// Get accelerometer measures
|
||
|
rs2_vector accel_data = motion.get_motion_data();
|
||
|
// Call function that computes the angle of motion based on the retrieved measures
|
||
|
algo.process_accel(accel_data);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Main loop
|
||
|
while (app)
|
||
|
{
|
||
|
// Configure scene, draw floor, handle manipultation by the user etc.
|
||
|
render_scene(app_state);
|
||
|
// Draw the camera according to the computed theta
|
||
|
camera.render_camera(algo.get_theta());
|
||
|
}
|
||
|
// Stop the pipeline
|
||
|
pipe.stop();
|
||
|
|
||
|
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;
|
||
|
}
|