PointCloudUtility.cc
Go to the documentation of this file.
1 
37 #ifdef WIN32
38 #ifndef WIN32_LEAN_AND_MEAN
39 #define WIN32_LEAN_AND_MEAN 1
40 #endif
41 
42 #include <windows.h>
43 #include <winsock2.h>
44 #else
45 #include <unistd.h>
46 #include <arpa/inet.h> // htons
47 #endif
48 
49 #include <fstream>
50 #include <iomanip>
51 #include <iostream>
52 #include <memory>
53 #include <sstream>
54 #include <signal.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <string>
59 
61 
64 
65 using namespace crl::multisense;
66 
67 namespace { // anonymous
68 
69 volatile bool doneG = false;
70 
71 void usage(const char *programNameP)
72 {
73  std::cerr << "USAGE: " << programNameP << " [<options>]" << std::endl;
74  std::cerr << "Where <options> are:" << std::endl;
75  std::cerr << "\t-a <current_address> : CURRENT IPV4 address (default=10.66.171.21)" << std::endl;
76  std::cerr << "\t-m <mtu> : CURRENT MTU (default=7200)" << std::endl;
77  std::cerr << "\t-d <min_disparity> : CURRENT MINIMUM DISPARITY (default=5.0)" << std::endl;
78 
79  exit(1);
80 }
81 
82 #ifdef WIN32
83 BOOL WINAPI signalHandler(DWORD dwCtrlType)
84 {
85  CRL_UNUSED (dwCtrlType);
86  doneG = true;
87  return TRUE;
88 }
89 #else
90 void signalHandler(int sig)
91 {
92  (void) sig;
93  doneG = true;
94 }
95 #endif
96 
97 struct WorldPoint
98 {
99  float x = 0.0;
100  float y = 0.0;
101  float z = 0.0;
102  uint8_t luma = 0u;
103 };
104 
105 //
106 // Wrapper around a Channel pointer to make cleanup easier
107 
108 class ChannelWrapper
109 {
110  public:
111 
112  ChannelWrapper(const std::string &ipAddress):
113  channelPtr_(Channel::Create(ipAddress))
114  {
115  }
116 
117  ~ChannelWrapper()
118  {
119  if (channelPtr_) {
120  Channel::Destroy(channelPtr_);
121  }
122  }
123 
124  Channel* ptr() noexcept
125  {
126  return channelPtr_;
127  }
128 
129  private:
130 
131  ChannelWrapper(const ChannelWrapper&) = delete;
132  ChannelWrapper operator=(const ChannelWrapper&) = delete;
133 
134  Channel* channelPtr_ = nullptr;
135 };
136 
137 //
138 // Wrapper to preserve image data outside of the image callback
139 
140 class ImageBufferWrapper
141 {
142 public:
143  ImageBufferWrapper(crl::multisense::Channel* driver,
144  const crl::multisense::image::Header &data):
145  driver_(driver),
146  callbackBuffer_(driver->reserveCallbackBuffer()),
147  data_(data)
148  {
149  }
150 
151  ~ImageBufferWrapper()
152  {
153  if (driver_) {
154  driver_->releaseCallbackBuffer(callbackBuffer_);
155  }
156  }
157 
158  const image::Header &data() const noexcept
159  {
160  return data_;
161  }
162 
163 private:
164 
165  ImageBufferWrapper(const ImageBufferWrapper&) = delete;
166  ImageBufferWrapper operator=(const ImageBufferWrapper&) = delete;
167 
168  crl::multisense::Channel * driver_ = nullptr;
169  void* callbackBuffer_;
170  const image::Header data_;
171 
172 };
173 
174 struct UserData
175 {
176  Channel *driver = nullptr;
177  std::shared_ptr<const ImageBufferWrapper> disparity = nullptr;
178  std::shared_ptr<const ImageBufferWrapper> leftRectified = nullptr;
181  double minDisparity = 0.0;
182 };
183 
184 std::vector<WorldPoint> reprojectDisparity(const std::shared_ptr<const ImageBufferWrapper> disparity,
185  const std::shared_ptr<const ImageBufferWrapper> leftRectified,
186  const image::Calibration &calibration,
187  const system::DeviceInfo &deviceInfo,
188  double minDisparity)
189 {
190  const size_t width = leftRectified->data().width;
191  const size_t height = leftRectified->data().height;
192 
193  //
194  // Scale our calibration based on the current sensor resolution. Calibrations stored on the camera
195  // are for full resolution images
196 
197  const double xScale = 1.0 / ((static_cast<double>(deviceInfo.imagerWidth) /
198  static_cast<double>(width)));
199 
200  const double yScale = 1.0 / ((static_cast<double>(deviceInfo.imagerHeight) /
201  static_cast<double>(height)));
202 
203  //
204  // Decompose rectified projection matrices sorted in the form
205  //
206  // Left: Right:
207  // Fx 0 Cx 0 Fx 0 Cx FxTx
208  // 0 Fy Cy 0 0 Fy Cy 0
209  // 0 0 0 1 0 0 0 1
210 
211  const double fx = calibration.left.P[0][0] * xScale;
212  const double fy = calibration.left.P[1][1] * yScale;
213  const double cx = calibration.left.P[0][2] * xScale;
214  const double cy = calibration.left.P[1][2] * yScale;
215  const double tx = calibration.right.P[0][3] / calibration.right.P[0][0];
216  const double cxRight = calibration.right.P[0][2] * xScale;
217 
218  const uint16_t *disparityP = reinterpret_cast<const uint16_t*>(disparity->data().imageDataP);
219  const uint8_t *leftRectifiedP = reinterpret_cast<const uint8_t*>(leftRectified->data().imageDataP);
220 
221  std::vector<WorldPoint> points;
222  points.reserve(height * width);
223 
224  for (size_t h = 0 ; h < height ; ++h) {
225  for (size_t w = 0 ; w < width ; ++w) {
226 
227  const size_t index = h * width + w;
228 
229  //
230  // MultiSense 16 bit disparity images are stored in 1/16 of a pixel. This allows us to send subpixel
231  // resolutions with integer values
232 
233  const double d = static_cast<double>(disparityP[index]) / 16.0;
234 
235  if (d < minDisparity) {
236  continue;
237  }
238 
239  //
240  // Q matrix used for reprojection. Manually preform the multiplication with the vector:
241  // [w, h, d, 1]^T
242  //
243  // FyTx 0 0 -FyCxTx
244  // 0 FxTx 0 -FxCyTx
245  // 0 0 0 FxFyTx
246  // 0 0 -Fy Fy(Cx - Cx')
247 
248  const double xB = ((fy * tx) * w) + (-fy * cx * tx);
249  const double yB = ((fx * tx) * h) + (-fx * cy * tx);
250  const double zB = (fx * fy * tx);
251  const double invB = 1. / (-fy * d) + (fy * (cx - cxRight));
252 
253  points.emplace_back(WorldPoint{static_cast<float>(xB * invB),
254  static_cast<float>(yB * invB),
255  static_cast<float>(zB * invB),
256  leftRectifiedP[index]});
257  }
258  }
259 
260  return points;
261 }
262 
263 bool savePly(const std::string& fileName,
264  const std::vector<WorldPoint> &points)
265 {
266  std::stringstream ss;
267 
268  ss << "ply\n";
269  ss << "format ascii 1.0\n";
270  ss << "element vertex " << points.size() << "\n";
271  ss << "property float x\n";
272  ss << "property float y\n";
273  ss << "property float z\n";
274  ss << "property uchar red\n";
275  ss << "property uchar green\n";
276  ss << "property uchar blue\n";
277  ss << "end_header\n";
278 
279  for (const auto &point : points) {
280  const uint32_t luma = static_cast<uint32_t>(point.luma);
281  ss << point.x << " " << point.y << " " << point.z << " " << luma << " " << luma << " " << luma << "\n";
282  }
283 
284  std::ofstream ply(fileName.c_str());
285  ply << ss.str();
286 
287  return true;
288 }
289 
290 void imageCallback(const image::Header& header,
291  void *userDataP)
292 {
293  UserData *userData = reinterpret_cast<UserData*>(userDataP);
294 
295  if (!userData->driver) {
296  std::cerr << "Invalid MultiSense channel" << std::endl;
297  return;
298  }
299 
300  switch (header.source) {
302  {
303  userData->leftRectified = std::make_shared<ImageBufferWrapper>(userData->driver, header);
304  if (userData->disparity && userData->disparity->data().frameId == header.frameId) {
305  //
306  // Our disparity and left recitified images match, we have enough info to create a pointcloud
307  break;
308  }
309 
310  return;
311  }
313  {
314  if (header.bitsPerPixel != 16) {
315  std::cerr << "Unsupported disparity pixel depth" << std::endl;
316  return;
317  }
318 
319 
320  userData->disparity = std::make_shared<ImageBufferWrapper>(userData->driver, header);
321  if (userData->leftRectified && userData->leftRectified->data().frameId == header.frameId) {
322  //
323  // Our disparity and left recitified images match, we have enough info to create a pointcloud
324  break;
325  }
326 
327  return;
328  }
329  default:
330  {
331  std::cerr << "Unknown image source: " << header.source << std::endl;
332  return;
333  }
334  };
335 
336  //
337  // Note this implementation of writing ply files can be slow since they are written in ascii
338 
339  std::cout << "Saving pointcloud for image header " << header.frameId << std::endl;
340  savePly(std::to_string(header.frameId) + ".ply", reprojectDisparity(userData->disparity,
341  userData->leftRectified,
342  userData->calibration,
343  userData->deviceInfo,
344  userData->minDisparity));
345 }
346 
347 } // anonymous
348 
349 int main(int argc,
350  char **argvPP)
351 {
352  std::string currentAddress = "10.66.171.21";
353  int32_t mtu = 7200;
354  double minDisparity = 5.0;
355 
356 #if WIN32
357  SetConsoleCtrlHandler (signalHandler, TRUE);
358 #else
359  signal(SIGINT, signalHandler);
360 #endif
361 
362  //
363  // Parse args
364 
365  int c;
366 
367  while(-1 != (c = getopt(argc, argvPP, "a:m:d:")))
368  switch(c) {
369  case 'a': currentAddress = std::string(optarg); break;
370  case 'm': mtu = atoi(optarg); break;
371  case 'd': minDisparity = std::stod(optarg); break;
372  default: usage(*argvPP); break;
373  }
374 
375  Status status;
376 
377  //
378  // Initialize communications.
379 
380  auto channelP = std::make_unique<ChannelWrapper>(currentAddress);
381  if (nullptr == channelP || nullptr == channelP->ptr()) {
382  std::cerr << "Failed to establish communications with \"" << currentAddress << "\"" << std::endl;
383  return EXIT_FAILURE;
384  }
385 
386  //
387  // Change MTU
388 
389  status = channelP->ptr()->setMtu(mtu);
390  if (Status_Ok != status) {
391  std::cerr << "Failed to set MTU to " << mtu << ": " << Channel::statusString(status) << std::endl;
392  return EXIT_FAILURE;
393  }
394 
395  //
396  // Query calibration
397 
398  image::Calibration calibration;
399  status = channelP->ptr()->getImageCalibration(calibration);
400  if (Status_Ok != status) {
401  std::cerr << "Failed to query calibraiton: " << Channel::statusString(status) << std::endl;
402  return EXIT_FAILURE;
403  }
404 
405  //
406  // Query device info
407 
409  status = channelP->ptr()->getDeviceInfo(deviceInfo);
410  if (Status_Ok != status) {
411  std::cerr << "Failed to query device info: " << Channel::statusString(status) << std::endl;
412  return EXIT_FAILURE;
413  }
414 
415  //
416  // Change framerate and resolution to 1/4 resolution
417 
418  image::Config cfg;
419 
420  status = channelP->ptr()->getImageConfig(cfg);
421  if (Status_Ok != status) {
422  std::cerr << "Failed to get image config: " << Channel::statusString(status) << std::endl;
423  return EXIT_FAILURE;
424  } else {
425  cfg.setFps(10.0);
426  cfg.setResolution(deviceInfo.imagerWidth / 2, deviceInfo.imagerHeight / 2);
427 
428  status = channelP->ptr()->setImageConfig(cfg);
429  if (Status_Ok != status) {
430  std::cerr << "Failed to configure sensor: " << Channel::statusString(status) << std::endl;
431  return EXIT_FAILURE;
432  }
433  }
434 
435  //
436  // Setup user data to store camera state for pointcloud reprojection
437 
438  UserData userData{channelP->ptr(), nullptr, nullptr, calibration, deviceInfo, minDisparity};
439 
440  //
441  // Add callbacks
442 
443  channelP->ptr()->addIsolatedCallback(imageCallback, Source_Luma_Rectified_Left | Source_Disparity_Left, &userData);
444 
445  //
446  // Start streaming
447 
448  status = channelP->ptr()->startStreams(Source_Luma_Rectified_Left | Source_Disparity_Left);
449  if (Status_Ok != status) {
450  std::cerr << "Failed to start streams: " << Channel::statusString(status) << std::endl;
451  return EXIT_FAILURE;
452  }
453 
454  while(!doneG) {
455  usleep(1000000);
456  }
457 
458  status = channelP->ptr()->stopStreams(Source_All);
459  if (Status_Ok != status) {
460  std::cerr << "Failed to stop streams: " << Channel::statusString(status) << std::endl;
461  return EXIT_FAILURE;
462  }
463 
464  return EXIT_SUCCESS;
465 }
d
static CRL_CONSTEXPR DataSource Source_Disparity_Left
static const char * statusString(Status status)
Definition: channel.cc:802
void setResolution(uint32_t w, uint32_t h)
int getopt(int argc, char **argv, char *opts)
Definition: getopt.c:31
#define CRL_UNUSED(var)
Definition: Portability.hh:44
static CRL_CONSTEXPR Status Status_Ok
def usage(progname)
int main(int argc, char **argvPP)
static void Destroy(Channel *instanceP)
Definition: channel.cc:789
char * optarg
Definition: getopt.c:29
static CRL_CONSTEXPR DataSource Source_All
static CRL_CONSTEXPR DataSource Source_Luma_Rectified_Left


multisense_lib
Author(s):
autogenerated on Sat Jun 24 2023 03:01:21