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.

345 lines
12 KiB

#include <opencv2/imgproc.hpp>
#include <opencv2/rgbd/kinfu.hpp>
#include <librealsense2/rs.hpp> // Include RealSense Cross Platform API
#include <example.hpp> // Include short list of convenience functions for rendering
#include <thread>
#include <queue>
#include <atomic>
#include <fstream>
using namespace cv;
using namespace cv::kinfu;
static float max_dist = 2.5;
static float min_dist = 0;
// Assigns an RGB value for each point in the pointcloud, based on the depth value
void colorize_pointcloud(const Mat points, Mat& color)
{
// Define a vector of 3 Mat arrays which will hold the channles of points
std::vector<Mat> channels(points.channels());
split(points, channels);
// Get the depth channel which we'll use to colorize the pointcloud
color = channels[2];
// Convert the depth matrix to unsigned char values
float min = min_dist;
float max = max_dist;
color.convertTo(color, CV_8UC1, 255 / (max - min), -255 * min / (max - min));
// Get an rgb value for each point
applyColorMap(color, color, COLORMAP_JET);
}
// Handles all the OpenGL calls needed to display the point cloud
void draw_kinfu_pointcloud(glfw_state& app_state, Mat points, Mat normals)
{
// Define new matrix which will later hold the coloring of the pointcloud
Mat color;
colorize_pointcloud(points, color);
// OpenGL commands that prep screen for the pointcloud
glLoadIdentity();
glPushAttrib(GL_ALL_ATTRIB_BITS);
glClearColor(153.f / 255, 153.f / 255, 153.f / 255, 1);
glClear(GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
gluPerspective(65, 1.3, 0.01f, 10.0f);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
gluLookAt(0, 0, 0, 0, 0, 1, 0, -1, 0);
glTranslatef(0, 0, 1 + app_state.offset_y*0.05f);
glRotated(app_state.pitch-20, 1, 0, 0);
glRotated(app_state.yaw+5, 0, 1, 0);
glTranslatef(0, 0, -0.5f);
glEnable(GL_DEPTH_TEST);
glEnable(GL_COLOR_MATERIAL);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glBegin(GL_POINTS);
// this segment actually prints the pointcloud
for (int i = 0; i < points.rows; i++)
{
// Get point coordinates from 'points' matrix
float x = points.at<float>(i, 0);
float y = points.at<float>(i, 1);
float z = points.at<float>(i, 2);
// Get point coordinates from 'normals' matrix
float nx = normals.at<float>(i, 0);
float ny = normals.at<float>(i, 1);
float nz = normals.at<float>(i, 2);
// Get the r, g, b values for the current point
uchar r = color.at<uchar>(i, 0);
uchar g = color.at<uchar>(i, 1);
uchar b = color.at<uchar>(i, 2);
// Register color and coordinates of the current point
glColor3ub(r, g, b);
glNormal3f(nx, ny, nz);
glVertex3f(x, y, z);
}
// OpenGL cleanup
glEnd();
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glPopAttrib();
}
void export_to_ply(Mat points, Mat normals)
{
// First generate a filename
const size_t buffer_size = 50;
char fname[buffer_size];
time_t t = time(0); // get time now
struct tm * now = localtime(&t);
strftime(fname, buffer_size, "%m%d%y %H%M%S.ply", now);
std::cout << "exporting to" << fname << std::endl;
// Get rgb values for points
Mat color;
colorize_pointcloud(points, color);
// Write the ply file
std::ofstream out(fname);
out << "ply\n";
out << "format binary_little_endian 1.0\n";
out << "comment pointcloud saved from Realsense Viewer\n";
out << "element vertex " << points.rows << "\n";
out << "property float" << sizeof(float) * 8 << " x\n";
out << "property float" << sizeof(float) * 8 << " y\n";
out << "property float" << sizeof(float) * 8 << " z\n";
out << "property float" << sizeof(float) * 8 << " nx\n";
out << "property float" << sizeof(float) * 8 << " ny\n";
out << "property float" << sizeof(float) * 8 << " nz\n";
out << "property uchar red\n";
out << "property uchar green\n";
out << "property uchar blue\n";
out << "end_header\n";
out.close();
out.open(fname, std::ios_base::app | std::ios_base::binary);
for (int i = 0; i < points.rows; i++)
{
// write vertices
out.write(reinterpret_cast<const char*>(&(points.at<float>(i, 0))), sizeof(float));
out.write(reinterpret_cast<const char*>(&(points.at<float>(i, 1))), sizeof(float));
out.write(reinterpret_cast<const char*>(&(points.at<float>(i, 2))), sizeof(float));
// write normals
out.write(reinterpret_cast<const char*>(&(normals.at<float>(i, 0))), sizeof(float));
out.write(reinterpret_cast<const char*>(&(normals.at<float>(i, 1))), sizeof(float));
out.write(reinterpret_cast<const char*>(&(normals.at<float>(i, 2))), sizeof(float));
// write colors
out.write(reinterpret_cast<const char*>(&(color.at<uchar>(i, 0))), sizeof(uint8_t));
out.write(reinterpret_cast<const char*>(&(color.at<uchar>(i, 1))), sizeof(uint8_t));
out.write(reinterpret_cast<const char*>(&(color.at<uchar>(i, 2))), sizeof(uint8_t));
}
}
// Thread-safe queue for OpenCV's Mat objects
class mat_queue
{
public:
void push(Mat& item)
{
std::lock_guard<std::mutex> lock(_mtx);
queue.push(item);
}
int try_get_next_item(Mat& item)
{
std::lock_guard<std::mutex> lock(_mtx);
if (queue.empty())
return false;
item = std::move(queue.front());
queue.pop();
return true;
}
private:
std::queue<Mat> queue;
std::mutex _mtx;
};
int main(int argc, char **argv)
{
// Declare KinFu and params pointers
Ptr<KinFu> kf;
Ptr<Params> params = Params::defaultParams();
// Create a pipeline and configure it
rs2::pipeline p;
rs2::config cfg;
float depth_scale;
cfg.enable_stream(RS2_STREAM_DEPTH, 1280, 720, RS2_FORMAT_Z16);
auto profile = p.start(cfg);
auto dev = profile.get_device();
auto stream_depth = profile.get_stream(RS2_STREAM_DEPTH);
// Get a new frame from the camera
rs2::frameset data = p.wait_for_frames();
auto d = data.get_depth_frame();
for (rs2::sensor& sensor : dev.query_sensors())
{
if (rs2::depth_sensor dpt = sensor.as<rs2::depth_sensor>())
{
// Set some presets for better results
dpt.set_option(RS2_OPTION_VISUAL_PRESET, RS2_RS400_VISUAL_PRESET_HIGH_DENSITY);
// Depth scale is needed for the kinfu set-up
depth_scale = dpt.get_depth_scale();
break;
}
}
// Declare post-processing filters for better results
auto decimation = rs2::decimation_filter();
auto spatial = rs2::spatial_filter();
auto temporal = rs2::temporal_filter();
auto clipping_dist = max_dist / depth_scale; // convert clipping_dist to raw depth units
// Use decimation once to get the final size of the frame
d = decimation.process(d);
auto w = d.get_width();
auto h = d.get_height();
Size size = Size(w, h);
auto intrin = stream_depth.as<rs2::video_stream_profile>().get_intrinsics();
// Configure kinfu's parameters
params->frameSize = size;
params->intr = Matx33f(intrin.fx, 0, intrin.ppx,
0, intrin.fy, intrin.ppy,
0, 0, 1);
params->depthFactor = 1 / depth_scale;
// Initialize KinFu object
kf = KinFu::create(params);
bool after_reset = false;
mat_queue points_queue;
mat_queue normals_queue;
window app(1280, 720, "RealSense KinectFusion Example");
glfw_state app_state;
register_glfw_callbacks(app, app_state);
std::atomic_bool stopped(false);
// This thread runs KinFu algorithm and calculates the pointcloud by fusing depth data from subsequent depth frames
std::thread calc_cloud_thread([&]() {
Mat _points;
Mat _normals;
try {
while (!stopped)
{
rs2::frameset data = p.wait_for_frames(); // Wait for next set of frames from the camera
auto d = data.get_depth_frame();
// Use post processing to improve results
d = decimation.process(d);
d = spatial.process(d);
d = temporal.process(d);
// Set depth values higher than clipping_dist to 0, to avoid unnecessary noise in the pointcloud
uint16_t* p_depth_frame = reinterpret_cast<uint16_t*>(const_cast<void*>(d.get_data()));
#pragma omp parallel for schedule(dynamic) //Using OpenMP to try to parallelise the loop
for (int y = 0; y < h; y++)
{
auto depth_pixel_index = y * w;
for (int x = 0; x < w; x++, ++depth_pixel_index)
{
// Check if the depth value of the current pixel is greater than the threshold
if (p_depth_frame[depth_pixel_index] > clipping_dist)
{
p_depth_frame[depth_pixel_index] = 0;
}
}
}
// Define matrices on the GPU for KinFu's use
UMat points;
UMat normals;
// Copy frame from CPU to GPU
Mat f(h, w, CV_16UC1, (void*)d.get_data());
UMat frame(h, w, CV_16UC1);
f.copyTo(frame);
f.release();
// Run KinFu on the new frame(on GPU)
if (!kf->update(frame))
{
kf->reset(); // If the algorithm failed, reset current state
// Save the pointcloud obtained before failure
export_to_ply(_points, _normals);
// To avoid calculating pointcloud before new frames were processed, set 'after_reset' to 'true'
after_reset = true;
points.release();
normals.release();
std::cout << "reset" << std::endl;
}
// Get current pointcloud
if (!after_reset)
{
kf->getCloud(points, normals);
}
if (!points.empty() && !normals.empty())
{
// copy points from GPU to CPU for rendering
points.copyTo(_points);
points.release();
normals.copyTo(_normals);
normals.release();
// Push to queue for rendering
points_queue.push(_points);
normals_queue.push(_normals);
}
after_reset = false;
}
}
catch (const std::exception& e) // Save pointcloud in case an error occurs (for example, camera disconnects)
{
export_to_ply(_points, _normals);
}
});
// Main thread handles rendering of the pointcloud
Mat points;
Mat normals;
while (app)
{
// Get the current state of the pointcloud
points_queue.try_get_next_item(points);
normals_queue.try_get_next_item(normals);
if (!points.empty() && !normals.empty()) // points or normals might not be ready on first iterations
draw_kinfu_pointcloud(app_state, points, normals);
}
stopped = true;
calc_cloud_thread.join();
// Save the pointcloud upon closing the app
export_to_ply(points, normals);
return 0;
}