RealSense Cross Platform API
RealSense Cross-platform API
rs_export.hpp
Go to the documentation of this file.
1 // License: Apache 2.0. See LICENSE file in root directory.
2 // Copyright(c) 2017 RealSense, Inc. All Rights Reserved.
3 
4 #ifndef LIBREALSENSE_RS2_EXPORT_HPP
5 #define LIBREALSENSE_RS2_EXPORT_HPP
6 
7 #include <map>
8 #include <fstream>
9 #include <cmath>
10 #include <sstream>
11 #include <cassert>
12 #include "rs_processing.hpp"
13 #include "rs_internal.hpp"
14 #include <iostream>
15 #include <thread>
16 #include <chrono>
17 #include <array>
18 
19 namespace rs2
20 {
21  struct vec3d {
22  float x, y, z;
23  float length() const { return sqrt(x * x + y * y + z * z); }
24 
25  vec3d normalize() const
26  {
27  auto len = length();
28  return { x / len, y / len, z / len };
29  }
30  };
31 
32  inline vec3d operator + (const vec3d & a, const vec3d & b) { return{ a.x + b.x, a.y + b.y, a.z + b.z }; }
33  inline vec3d operator - (const vec3d & a, const vec3d & b) { return{ a.x - b.x, a.y - b.y, a.z - b.z }; }
34  inline vec3d cross(const vec3d & a, const vec3d & b) { return { a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x }; }
35 
36  class save_to_ply : public filter
37  {
38  public:
39  static const auto OPTION_IGNORE_COLOR = rs2_option(RS2_OPTION_COUNT + 10);
40  static const auto OPTION_PLY_MESH = rs2_option(RS2_OPTION_COUNT + 11);
41  static const auto OPTION_PLY_BINARY = rs2_option(RS2_OPTION_COUNT + 12);
42  static const auto OPTION_PLY_NORMALS = rs2_option(RS2_OPTION_COUNT + 13);
44 
45  save_to_ply(std::string filename = "RealSense Pointcloud ", pointcloud pc = pointcloud()) : filter([this](frame f, frame_source& s) { func(f, s); }),
46  _pc(std::move(pc)), fname(filename)
47  {
49  register_simple_option(OPTION_PLY_MESH, option_range{ 0, 1, 1, 1 });
50  register_simple_option(OPTION_PLY_NORMALS, option_range{ 0, 1, 0, 1 });
51  register_simple_option(OPTION_PLY_BINARY, option_range{ 0, 1, 1, 1 });
52  register_simple_option(OPTION_PLY_THRESHOLD, option_range{ 0, 1, 0.05f, 0 });
53  }
54 
55  private:
56  void func(frame data, frame_source& source)
57  {
58  frame depth, color;
59  if (auto fs = data.as<frameset>()) {
60  for (auto f : fs) {
61  if (f.is<points>()) depth = f;
62  else if (!depth && f.is<depth_frame>()) depth = f;
63  else if (!color && f.is<video_frame>()) color = f;
64  }
65  } else if (data.is<depth_frame>() || data.is<points>()) {
66  depth = data;
67  }
68 
69  if (!depth) throw std::runtime_error("Need depth data to save PLY");
70  if (!depth.is<points>()) {
71  if (color) _pc.map_to(color);
72  depth = _pc.calculate(depth);
73  }
74 
75  export_to_ply(depth, color);
76  source.frame_ready(data); // passthrough filter because processing_block::process doesn't support sinks
77  }
78 
79  void export_to_ply(points p, video_frame color) {
80  const bool use_texcoords = color && !get_option(OPTION_IGNORE_COLOR);
81  bool mesh = get_option(OPTION_PLY_MESH) != 0;
82  bool binary = get_option(OPTION_PLY_BINARY) != 0;
83  bool use_normals = get_option(OPTION_PLY_NORMALS) != 0;
84  const auto verts = p.get_vertices();
85  const auto texcoords = p.get_texture_coordinates();
86  const uint8_t* texture_data = nullptr;
87  if (use_texcoords) // texture might be on the gpu, get pointer to data before for-loop to avoid repeated access
88  texture_data = reinterpret_cast<const uint8_t*>(color.get_data());
89  std::vector<rs2::vertex> new_verts;
90  std::vector<vec3d> normals;
91  std::vector<std::array<uint8_t, 3>> new_tex;
92  std::map<size_t, size_t> idx_map;
93  std::map<size_t, std::vector<vec3d>> index_to_normals;
94 
95  new_verts.reserve(p.size());
96  if (use_texcoords) new_tex.reserve(p.size());
97 
98  static const auto min_distance = 1e-6;
99 
100  for (size_t i = 0; i < p.size(); ++i) {
101  if (fabs(verts[i].x) >= min_distance || fabs(verts[i].y) >= min_distance ||
102  fabs(verts[i].z) >= min_distance)
103  {
104  idx_map[int(i)] = int(new_verts.size());
105  new_verts.push_back({ verts[i].x, -1 * verts[i].y, -1 * verts[i].z });
106  if (use_texcoords)
107  {
108  auto rgb = get_texcolor(color, texture_data, texcoords[i].u, texcoords[i].v);
109  new_tex.push_back(rgb);
110  }
111  }
112  }
113 
114  auto profile = p.get_profile().as<video_stream_profile>();
115  auto width = profile.width(), height = profile.height();
116  static const auto threshold = get_option(OPTION_PLY_THRESHOLD);
117  std::vector<std::array<size_t, 3>> faces;
118  if (mesh)
119  {
120  for (size_t x = 0; x < width - 1; ++x) {
121  for (size_t y = 0; y < height - 1; ++y) {
122  auto a = y * width + x, b = y * width + x + 1, c = (y + 1)*width + x, d = (y + 1)*width + x + 1;
123  if (verts[a].z && verts[b].z && verts[c].z && verts[d].z
124  && fabs(verts[a].z - verts[b].z) < threshold && fabs(verts[a].z - verts[c].z) < threshold
125  && fabs(verts[b].z - verts[d].z) < threshold && fabs(verts[c].z - verts[d].z) < threshold)
126  {
127  if (idx_map.count(a) == 0 || idx_map.count(b) == 0 || idx_map.count(c) == 0 ||
128  idx_map.count(d) == 0)
129  continue;
130  faces.push_back({ idx_map[a], idx_map[d], idx_map[b] });
131  faces.push_back({ idx_map[d], idx_map[a], idx_map[c] });
132 
133  if (use_normals)
134  {
135  vec3d point_a = { verts[a].x , -1 * verts[a].y, -1 * verts[a].z };
136  vec3d point_b = { verts[b].x , -1 * verts[b].y, -1 * verts[b].z };
137  vec3d point_c = { verts[c].x , -1 * verts[c].y, -1 * verts[c].z };
138  vec3d point_d = { verts[d].x , -1 * verts[d].y, -1 * verts[d].z };
139 
140  auto n1 = cross(point_d - point_a, point_b - point_a);
141  auto n2 = cross(point_c - point_a, point_d - point_a);
142 
143  index_to_normals[idx_map[a]].push_back(n1);
144  index_to_normals[idx_map[a]].push_back(n2);
145 
146  index_to_normals[idx_map[b]].push_back(n1);
147 
148  index_to_normals[idx_map[c]].push_back(n2);
149 
150  index_to_normals[idx_map[d]].push_back(n1);
151  index_to_normals[idx_map[d]].push_back(n2);
152  }
153  }
154  }
155  }
156  }
157 
158  if (mesh && use_normals)
159  {
160  for (size_t i = 0; i < new_verts.size(); ++i)
161  {
162  auto normals_vec = index_to_normals[i];
163  vec3d sum = { 0, 0, 0 };
164  for (auto& n : normals_vec)
165  sum = sum + n;
166  if (normals_vec.size() > 0)
167  normals.push_back((sum.normalize()));
168  else
169  normals.push_back({ 0, 0, 0 });
170  }
171  }
172 
173  std::ofstream out(fname);
174  out << "ply\n";
175  if (binary)
176  out << "format binary_little_endian 1.0\n";
177  else
178  out << "format ascii 1.0\n";
179  out << "comment pointcloud saved from Realsense Viewer\n";
180  out << "element vertex " << new_verts.size() << "\n";
181  out << "property float" << sizeof(float) * 8 << " x\n";
182  out << "property float" << sizeof(float) * 8 << " y\n";
183  out << "property float" << sizeof(float) * 8 << " z\n";
184  if (mesh && use_normals)
185  {
186  out << "property float" << sizeof(float) * 8 << " nx\n";
187  out << "property float" << sizeof(float) * 8 << " ny\n";
188  out << "property float" << sizeof(float) * 8 << " nz\n";
189  }
190  if (use_texcoords)
191  {
192  out << "property uchar red\n";
193  out << "property uchar green\n";
194  out << "property uchar blue\n";
195  }
196  if (mesh)
197  {
198  out << "element face " << faces.size() << "\n";
199  out << "property list uchar int vertex_indices\n";
200  }
201  out << "end_header\n";
202 
203  if (binary)
204  {
205  out.close();
206  out.open(fname, std::ios_base::app | std::ios_base::binary);
207  for (size_t i = 0; i < new_verts.size(); ++i)
208  {
209  // we assume little endian architecture on your device
210  out.write(reinterpret_cast<const char*>(&(new_verts[i].x)), sizeof(float));
211  out.write(reinterpret_cast<const char*>(&(new_verts[i].y)), sizeof(float));
212  out.write(reinterpret_cast<const char*>(&(new_verts[i].z)), sizeof(float));
213 
214  if (mesh && use_normals)
215  {
216  out.write(reinterpret_cast<const char*>(&(normals[i].x)), sizeof(float));
217  out.write(reinterpret_cast<const char*>(&(normals[i].y)), sizeof(float));
218  out.write(reinterpret_cast<const char*>(&(normals[i].z)), sizeof(float));
219  }
220 
221  if (use_texcoords)
222  {
223  out.write(reinterpret_cast<const char*>(&(new_tex[i][0])), sizeof(uint8_t));
224  out.write(reinterpret_cast<const char*>(&(new_tex[i][1])), sizeof(uint8_t));
225  out.write(reinterpret_cast<const char*>(&(new_tex[i][2])), sizeof(uint8_t));
226  }
227  }
228  if (mesh)
229  {
230  auto size = faces.size();
231  for (size_t i = 0; i < size; ++i) {
232  static const int three = 3;
233  out.write(reinterpret_cast<const char*>(&three), sizeof(uint8_t));
234  out.write(reinterpret_cast<const char*>(&(faces[i][0])), sizeof(int));
235  out.write(reinterpret_cast<const char*>(&(faces[i][1])), sizeof(int));
236  out.write(reinterpret_cast<const char*>(&(faces[i][2])), sizeof(int));
237  }
238  }
239  }
240  else
241  {
242  for (size_t i = 0; i <new_verts.size(); ++i)
243  {
244  out << new_verts[i].x << " ";
245  out << new_verts[i].y << " ";
246  out << new_verts[i].z << " ";
247 
248  if (mesh && use_normals)
249  {
250  out << normals[i].x << " ";
251  out << normals[i].y << " ";
252  out << normals[i].z << " ";
253  }
254 
255  if (use_texcoords)
256  {
257  out << unsigned(new_tex[i][0]) << " ";
258  out << unsigned(new_tex[i][1]) << " ";
259  out << unsigned(new_tex[i][2]) << " ";
260  }
261  out << "\n";
262 
263  }
264  if (mesh)
265  {
266  auto size = faces.size();
267  for (size_t i = 0; i < size; ++i) {
268  int three = 3;
269  out << three << " ";
270  out << std::get<0>(faces[i]) << " ";
271  out << std::get<1>(faces[i]) << " ";
272  out << std::get<2>(faces[i]) << " ";
273  out << "\n";
274  }
275  }
276  }
277  }
278 
279  std::array<uint8_t, 3> get_texcolor(const video_frame& texture, const uint8_t* texture_data, float u, float v)
280  {
281  const int w = texture.get_width(), h = texture.get_height();
282  int x = std::min(std::max(int(u*w + .5f), 0), w - 1);
283  int y = std::min(std::max(int(v*h + .5f), 0), h - 1);
284  int idx = x * texture.get_bytes_per_pixel() + y * texture.get_stride_in_bytes();
285  return { texture_data[idx], texture_data[idx + 1], texture_data[idx + 2] };
286  }
287 
288  std::string fname;
289  pointcloud _pc;
290  };
291 
292  class save_single_frameset : public filter {
293  public:
294  save_single_frameset(std::string filename = "RealSense Frameset ")
295  : filter([this](frame f, frame_source& s) { save(f, s); }), fname(filename)
296  {}
297 
298  private:
299  void save(frame data, frame_source& source, bool do_signal=true)
300  {
301  software_device dev;
302 
303  std::vector<std::tuple<software_sensor, stream_profile, int>> sensors;
304  std::vector<std::tuple<stream_profile, stream_profile>> extrinsics;
305 
306  if (auto fs = data.as<frameset>()) {
307  for (int i = 0; size_t(i) < fs.size(); ++i) {
308  frame f = fs[i];
309  auto profile = f.get_profile();
310  std::stringstream sname;
311  sname << "Sensor (" << i << ")";
312  auto s = dev.add_sensor(sname.str());
313  stream_profile software_profile;
314 
315  if (auto vf = f.as<video_frame>()) {
316  auto vp = profile.as<video_stream_profile>();
317  rs2_video_stream stream{ vp.stream_type(), vp.stream_index(), i, vp.width(), vp.height(), vp.fps(), vf.get_bytes_per_pixel(), vp.format(), vp.get_intrinsics() };
318  software_profile = s.add_video_stream(stream);
319  if (f.is<rs2::depth_frame>()) {
320  auto ds = sensor_from_frame(f)->as<rs2::depth_sensor>();
321  s.add_read_only_option(RS2_OPTION_DEPTH_UNITS, ds.get_option(RS2_OPTION_DEPTH_UNITS));
322  }
323  } else if (f.is<motion_frame>()) {
324  auto mp = profile.as<motion_stream_profile>();
325  rs2_motion_stream stream{ mp.stream_type(), mp.stream_index(), i, mp.fps(), mp.format(), mp.get_motion_intrinsics() };
326  software_profile = s.add_motion_stream(stream);
327  } else if (f.is<pose_frame>()) {
328  rs2_pose_stream stream{ profile.stream_type(), profile.stream_index(), i, profile.fps(), profile.format() };
329  software_profile = s.add_pose_stream(stream);
330  } else {
331  // TODO: How to handle other frame types? (e.g. points)
332  assert(false);
333  }
334  sensors.emplace_back(s, software_profile, i);
335 
336  bool found_extrin = false;
337  for (auto& root : extrinsics) {
338  try {
339  std::get<0>(root).register_extrinsics_to(software_profile,
340  std::get<1>(root).get_extrinsics_to(profile)
341  );
342  found_extrin = true;
343  break;
344  } catch (...) {}
345  }
346  if (!found_extrin) {
347  extrinsics.emplace_back(software_profile, profile);
348  }
349  }
350 
351 
352  // Recorder needs sensors to already exist when its created
353  std::stringstream name;
354  name << fname << data.get_frame_number() << ".bag";
355  recorder rec(name.str(), dev);
356 
357  for (auto group : sensors) {
358  auto s = std::get<0>(group);
359  auto profile = std::get<1>(group);
360  s.open(profile);
361  s.start([](frame) {});
362  frame f = fs[std::get<2>(group)];
363  if (auto vf = f.as<video_frame>()) {
364  s.on_video_frame({ const_cast<void*>(vf.get_data()), [](void*) {}, vf.get_stride_in_bytes(), vf.get_bytes_per_pixel(),
365  vf.get_timestamp(), vf.get_frame_timestamp_domain(), static_cast<int>(vf.get_frame_number()), profile });
366  } else if (f.is<motion_frame>()) {
367  s.on_motion_frame({ const_cast<void*>(f.get_data()), [](void*) {}, f.get_timestamp(),
368  f.get_frame_timestamp_domain(), static_cast<int>(f.get_frame_number()), profile });
369  } else if (f.is<pose_frame>()) {
370  s.on_pose_frame({ const_cast<void*>(f.get_data()), [](void*) {}, f.get_timestamp(),
371  f.get_frame_timestamp_domain(), static_cast<int>(f.get_frame_number()), profile });
372  }
373  s.stop();
374  s.close();
375  }
376  } else {
377  // single frame
378  auto set = source.allocate_composite_frame({ data });
379  save(set, source, false);
380  }
381 
382  if (do_signal)
383  source.frame_ready(data);
384  }
385 
386  std::string fname;
387  };
388 }
389 
390 #endif
save_to_ply(std::string filename="RealSense Pointcloud ", pointcloud pc=pointcloud())
Definition: rs_export.hpp:45
Definition: rs_option.h:138
Definition: rs_frame.hpp:354
Definition: rs_export.hpp:36
vec3d cross(const vec3d &a, const vec3d &b)
Definition: rs_export.hpp:34
rs2_option
Defines general configuration controls. These can generally be mapped to camera UVC controls...
Definition: rs_option.h:26
int fps
Definition: rs_internal.h:71
Definition: rs_sensor.hpp:490
static const auto OPTION_PLY_MESH
Definition: rs_export.hpp:40
save_single_frameset(std::string filename="RealSense Frameset ")
Definition: rs_export.hpp:294
vec3d operator+(const vec3d &a, const vec3d &b)
Definition: rs_export.hpp:32
static const auto OPTION_PLY_THRESHOLD
Definition: rs_export.hpp:43
void register_simple_option(rs2_option option_id, option_range range)
Definition: rs_processing.hpp:348
All the parameters required to define a video stream.
Definition: rs_internal.h:41
T as() const
Definition: rs_processing.hpp:415
void map_to(frame mapped)
Definition: rs_processing.hpp:469
Definition: rs_context.hpp:11
Definition: rs_export.hpp:21
Definition: rs_option.h:56
float y
Definition: rs_export.hpp:22
float get_option(rs2_option option) const
Definition: rs_options.hpp:216
Definition: rs_processing.hpp:17
static const auto OPTION_PLY_NORMALS
Definition: rs_export.hpp:42
All the parameters required to define a pose stream.
Definition: rs_internal.h:66
Definition: rs_types.hpp:199
int width
Definition: rs_internal.h:46
All the parameters required to define a motion stream.
Definition: rs_internal.h:55
T as() const
Definition: rs_sensor.hpp:369
Definition: rs_processing.hpp:429
Definition: rs_processing.hpp:360
points calculate(frame depth) const
Definition: rs_processing.hpp:448
vec3d normalize() const
Definition: rs_export.hpp:25
static const auto OPTION_PLY_BINARY
Definition: rs_export.hpp:41
float length() const
Definition: rs_export.hpp:23
static const auto OPTION_IGNORE_COLOR
Definition: rs_export.hpp:39
int fps
Definition: rs_internal.h:60
float z
Definition: rs_export.hpp:22
vec3d operator-(const vec3d &a, const vec3d &b)
Definition: rs_export.hpp:33
Definition: rs_export.hpp:292
float x
Definition: rs_export.hpp:22
std::shared_ptr< sensor > sensor_from_frame(frame f)
Definition: rs_sensor.hpp:393
Definition: rs_frame.hpp:922