libvisiontransfer  8.3.0
reconstruct3d.cpp
1 /*******************************************************************************
2  * Copyright (c) 2020 Nerian Vision GmbH
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *******************************************************************************/
14 
15 #include "reconstruct3d.h"
16 #include "visiontransfer/alignedallocator.h"
17 #include <vector>
18 #include <cstring>
19 #include <algorithm>
20 #include <fstream>
21 #include <stdexcept>
22 #include <cmath>
23 
24 // SIMD Headers
25 #ifdef __AVX2__
26 #include <immintrin.h>
27 #elif __SSE2__
28 #include <emmintrin.h>
29 #endif
30 
31 using namespace std;
32 using namespace visiontransfer;
33 using namespace visiontransfer::internal;
34 
35 namespace visiontransfer {
36 
37 /*************** Pimpl class containing all private members ***********/
38 
39 class Reconstruct3D::Pimpl {
40 public:
41  Pimpl();
42 
43  float* createPointMap(const unsigned short* dispMap, int width, int height,
44  int rowStride, const float* q, unsigned short minDisparity, int subpixelFactor);
45 
46  float* createPointMap(const ImageSet& imageSet, unsigned short minDisparity);
47 
48  void projectSinglePoint(int imageX, int imageY, unsigned short disparity, const float* q,
49  float& pointX, float& pointY, float& pointZ, int subpixelFactor);
50 
51  void writePlyFile(const char* file, const unsigned short* dispMap,
52  const unsigned char* image, int width, int height, ImageSet::ImageFormat format,
53  int dispRowStride, int imageRowStride, const float* q,
54  double maxZ, bool binary, int subpixelFactor);
55 
56  void writePlyFile(const char* file, const ImageSet& imageSet,
57  double maxZ, bool binary);
58 
59 private:
60  std::vector<float, AlignedAllocator<float> > pointMap;
61 
62  float* createPointMapFallback(const unsigned short* dispMap, int width, int height,
63  int rowStride, const float* q, unsigned short minDisparity, int subpixelFactor);
64 
65  float* createPointMapSSE2(const unsigned short* dispMap, int width, int height,
66  int rowStride, const float* q, unsigned short minDisparity, int subpixelFactor);
67 
68  float* createPointMapAVX2(const unsigned short* dispMap, int width, int height,
69  int rowStride, const float* q, unsigned short minDisparity, int subpixelFactor);
70 };
71 
72 /******************** Stubs for all public members ********************/
73 
74 Reconstruct3D::Reconstruct3D()
75  :pimpl(new Pimpl) {
76 }
77 
78 Reconstruct3D::~Reconstruct3D() {
79  delete pimpl;
80 }
81 
82 float* Reconstruct3D::createPointMap(const unsigned short* dispMap, int width, int height,
83  int rowStride, const float* q, unsigned short minDisparity, int subpixelFactor) {
84  return pimpl->createPointMap(dispMap, width, height, rowStride, q, minDisparity, subpixelFactor);
85 }
86 
87 float* Reconstruct3D::createPointMap(const ImageSet& imageSet, unsigned short minDisparity) {
88  return pimpl->createPointMap(imageSet, minDisparity);
89 }
90 
91 void Reconstruct3D::projectSinglePoint(int imageX, int imageY, unsigned short disparity,
92  const float* q, float& pointX, float& pointY, float& pointZ, int subpixelFactor) {
93  pimpl->projectSinglePoint(imageX, imageY, disparity, q, pointX, pointY, pointZ,
94  subpixelFactor);
95 }
96 
97 void Reconstruct3D::writePlyFile(const char* file, const unsigned short* dispMap,
98  const unsigned char* image, int width, int height, ImageSet::ImageFormat format, int dispRowStride,
99  int imageRowStride, const float* q, double maxZ, bool binary, int subpixelFactor) {
100  pimpl->writePlyFile(file, dispMap, image, width, height, format, dispRowStride,
101  imageRowStride, q, maxZ, binary, subpixelFactor);
102 }
103 
104 void Reconstruct3D::writePlyFile(const char* file, const ImageSet& imageSet,
105  double maxZ, bool binary) {
106  pimpl->writePlyFile(file, imageSet, maxZ, binary);
107 }
108 
109 /******************** Implementation in pimpl class *******************/
110 
111 Reconstruct3D::Pimpl::Pimpl() {
112 }
113 
114 float* Reconstruct3D::Pimpl::createPointMap(const unsigned short* dispMap, int width,
115  int height, int rowStride, const float* q, unsigned short minDisparity, int subpixelFactor) {
116 
117  // Allocate the buffer
118  if(pointMap.size() != static_cast<unsigned int>(4*width*height)) {
119  pointMap.resize(4*width*height);
120  }
121 
122 # ifdef __AVX2__
123  if(width % 16 == 0 && (uintptr_t)dispMap % 32 == 0) {
124  return createPointMapAVX2(dispMap, width, height, rowStride, q, minDisparity, subpixelFactor);
125  } else
126 # endif
127 # ifdef __SSE2__
128  if(width % 8 == 0 && (uintptr_t)dispMap % 16 == 0) {
129  return createPointMapSSE2(dispMap, width, height, rowStride, q, minDisparity, subpixelFactor);
130  } else
131 # endif
132  return createPointMapFallback(dispMap, width, height, rowStride, q, minDisparity, subpixelFactor);
133 }
134 
135 float* Reconstruct3D::Pimpl::createPointMap(const ImageSet& imageSet, unsigned short minDisparity) {
136  if(!imageSet.hasImageType(ImageSet::IMAGE_DISPARITY)) {
137  throw std::runtime_error("ImageSet does not contain a disparity map!");
138  }
139 
140  if(imageSet.getPixelFormat(ImageSet::IMAGE_DISPARITY) != ImageSet::FORMAT_12_BIT_MONO) {
141  throw std::runtime_error("Disparity map must have 12-bit pixel format!");
142  }
143 
144  return createPointMap(reinterpret_cast<unsigned short*>(imageSet.getPixelData(ImageSet::IMAGE_DISPARITY)), imageSet.getWidth(),
145  imageSet.getHeight(), imageSet.getRowStride(ImageSet::IMAGE_DISPARITY), imageSet.getQMatrix(), minDisparity,
146  imageSet.getSubpixelFactor());
147 }
148 
149 float* Reconstruct3D::Pimpl::createPointMapFallback(const unsigned short* dispMap, int width,
150  int height, int rowStride, const float* q, unsigned short minDisparity, int subpixelFactor) {
151  // Code without SSE or AVX optimization
152  float* outputPtr = &pointMap[0];
153  int stride = rowStride / 2;
154 
155  for(int y = 0; y < height; y++) {
156  double qx = q[1]*y + q[3];
157  double qy = q[5]*y + q[7];
158  double qz = q[9]*y + q[11];
159  double qw = q[13]*y + q[15];
160 
161  for(int x = 0; x < width; x++) {
162  unsigned short intDisp = std::max(minDisparity, dispMap[y*stride + x]);
163  if(intDisp >= 0xFFF) {
164  intDisp = minDisparity; // Invalid disparity
165  }
166 
167  double d = intDisp / double(subpixelFactor);
168  double w = qw + q[14]*d;
169 
170  *outputPtr = static_cast<float>((qx + q[2]*d)/w); // x
171  outputPtr++;
172 
173  *outputPtr = static_cast<float>((qy + q[6]*d)/w); // y
174  outputPtr++;
175 
176  *outputPtr = static_cast<float>((qz + q[10]*d)/w); // z
177  outputPtr+=2; // Consider padding
178 
179  qx += q[0];
180  qy += q[4];
181  qz += q[8];
182  qw += q[12];
183  }
184  }
185  return &pointMap[0];
186 }
187 
188 void Reconstruct3D::Pimpl::projectSinglePoint(int imageX, int imageY, unsigned short disparity,
189  const float* q, float& pointX, float& pointY, float& pointZ, int subpixelFactor) {
190 
191  double d = disparity / double(subpixelFactor);
192  double w = q[15] + q[14]*d;
193  pointX = static_cast<float>((imageX*q[0] + q[3])/w);
194  pointY = static_cast<float>((imageY*q[5] + q[7])/w);
195  pointZ = static_cast<float>(q[11]/w);
196 }
197 
198 # ifdef __AVX2__
199 float* Reconstruct3D::Pimpl::createPointMapAVX2(const unsigned short* dispMap, int width,
200  int height, int rowStride, const float* q, unsigned short minDisparity, int subpixelFactor) {
201 
202  // Create column vectors of q
203  const __m256 qCol0 = _mm256_setr_ps(q[0], q[4], q[8], q[12], q[0], q[4], q[8], q[12]);
204  const __m256 qCol1 = _mm256_setr_ps(q[1], q[5], q[9], q[13], q[1], q[5], q[9], q[13]);
205  const __m256 qCol2 = _mm256_setr_ps(q[2], q[6], q[10], q[14], q[2], q[6], q[10], q[14]);
206  const __m256 qCol3 = _mm256_setr_ps(q[3], q[7], q[11], q[15], q[3], q[7], q[11], q[15]);
207 
208  // More constants that we need
209  const __m256i minDispVector = _mm256_set1_epi16(minDisparity);
210  const __m256i maxDispVector = _mm256_set1_epi16(0xFFF);
211  const __m256 scaleVector = _mm256_set1_ps(1.0/double(subpixelFactor));
212  const __m256i zeroVector = _mm256_set1_epi16(0);
213 
214  float* outputPtr = &pointMap[0];
215 
216  for(int y = 0; y < height; y++) {
217  const unsigned char* rowStart = &reinterpret_cast<const unsigned char*>(dispMap)[y*rowStride];
218  const unsigned char* rowEnd = &reinterpret_cast<const unsigned char*>(dispMap)[y*rowStride + 2*width];
219 
220  int x = 0;
221  for(const unsigned char* ptr = rowStart; ptr != rowEnd; ptr += 32) {
222  __m256i disparities = _mm256_load_si256(reinterpret_cast<const __m256i*>(ptr));
223 
224  // Find invalid disparities and set them to 0
225  __m256i validMask = _mm256_cmpgt_epi16(maxDispVector, disparities);
226  disparities = _mm256_and_si256(validMask, disparities);
227 
228  // Clamp to minimum disparity
229  disparities = _mm256_max_epi16(disparities, minDispVector);
230 
231  // Stupid AVX2 unpack mixes everything up! Lets swap the register beforehand.
232  __m256i disparitiesMixup = _mm256_permute4x64_epi64(disparities, 0xd8);
233 
234  // Convert to floats and scale with 1/subpixelFactor
235  __m256 floatDisp = _mm256_cvtepi32_ps(_mm256_unpacklo_epi16(disparitiesMixup, zeroVector));
236  __m256 dispScaled = _mm256_mul_ps(floatDisp, scaleVector);
237 
238  // Copy to array
239 #ifdef _MSC_VER
240  __declspec(align(32)) float dispArray[16];
241 #else
242  float dispArray[16]__attribute__((aligned(32)));
243 #endif
244  _mm256_store_ps(&dispArray[0], dispScaled);
245 
246  // Same for other half
247  floatDisp = _mm256_cvtepi32_ps(_mm256_unpackhi_epi16(disparitiesMixup, zeroVector));
248  dispScaled = _mm256_mul_ps(floatDisp, scaleVector);
249  _mm256_store_ps(&dispArray[8], dispScaled);
250 
251  // Iterate over disparities and perform matrix multiplication for each
252  for(int i=0; i<16; i+=2) {
253  // Create two vectors
254  __m256 vec = _mm256_setr_ps(x, y, dispArray[i], 1.0,
255  x+1, y, dispArray[i+1], 1.0);
256 
257  // Multiply with matrix
258  __m256 u1 = _mm256_shuffle_ps(vec,vec, _MM_SHUFFLE(0,0,0,0));
259  __m256 u2 = _mm256_shuffle_ps(vec,vec, _MM_SHUFFLE(1,1,1,1));
260  __m256 u3 = _mm256_shuffle_ps(vec,vec, _MM_SHUFFLE(2,2,2,2));
261  __m256 u4 = _mm256_shuffle_ps(vec,vec, _MM_SHUFFLE(3,3,3,3));
262 
263  __m256 prod1 = _mm256_mul_ps(u1, qCol0);
264  __m256 prod2 = _mm256_mul_ps(u2, qCol1);
265  __m256 prod3 = _mm256_mul_ps(u3, qCol2);
266  __m256 prod4 = _mm256_mul_ps(u4, qCol3);
267 
268  __m256 multResult = _mm256_add_ps(_mm256_add_ps(prod1, prod2), _mm256_add_ps(prod3, prod4));
269 
270  // Divide by w to receive point coordinates
271  __m256 point = _mm256_div_ps(multResult,
272  _mm256_shuffle_ps(multResult,multResult, _MM_SHUFFLE(3,3,3,3)));
273 
274  // Write result to memory
275  _mm256_store_ps(outputPtr, point);
276 
277  outputPtr += 8;
278  x+=2;
279  }
280  }
281  }
282 
283  return &pointMap[0];
284 }
285 #endif
286 
287 #ifdef __SSE2__
288 float* Reconstruct3D::Pimpl::createPointMapSSE2(const unsigned short* dispMap, int width,
289  int height, int rowStride, const float* q, unsigned short minDisparity, int subpixelFactor) {
290 
291  // Create column vectors of q
292  const __m128 qCol0 = _mm_setr_ps(q[0], q[4], q[8], q[12]);
293  const __m128 qCol1 = _mm_setr_ps(q[1], q[5], q[9], q[13]);
294  const __m128 qCol2 = _mm_setr_ps(q[2], q[6], q[10], q[14]);
295  const __m128 qCol3 = _mm_setr_ps(q[3], q[7], q[11], q[15]);
296 
297  // More constants that we need
298  const __m128i minDispVector = _mm_set1_epi16(minDisparity);
299  const __m128i maxDispVector = _mm_set1_epi16(0xFFF);
300  const __m128 scaleVector = _mm_set1_ps(1.0/double(subpixelFactor));
301  const __m128i zeroVector = _mm_set1_epi16(0);
302 
303  float* outputPtr = &pointMap[0];
304 
305  for(int y = 0; y < height; y++) {
306  const unsigned char* rowStart = &reinterpret_cast<const unsigned char*>(dispMap)[y*rowStride];
307  const unsigned char* rowEnd = &reinterpret_cast<const unsigned char*>(dispMap)[y*rowStride + 2*width];
308 
309  int x = 0;
310  for(const unsigned char* ptr = rowStart; ptr != rowEnd; ptr += 16) {
311  __m128i disparities = _mm_load_si128(reinterpret_cast<const __m128i*>(ptr));
312 
313  // Find invalid disparities and set them to 0
314  __m128i validMask = _mm_cmplt_epi16(disparities, maxDispVector);
315  disparities = _mm_and_si128(validMask, disparities);
316 
317  // Clamp to minimum disparity
318  disparities = _mm_max_epi16(disparities, minDispVector);
319 
320  // Convert to floats and scale with 1/subpixelFactor
321  __m128 floatDisp = _mm_cvtepi32_ps(_mm_unpacklo_epi16(disparities, zeroVector));
322  __m128 dispScaled = _mm_mul_ps(floatDisp, scaleVector);
323 
324  // Copy to array
325 #ifdef _MSC_VER
326  __declspec(align(16)) float dispArray[8];
327 #else
328  float dispArray[8]__attribute__((aligned(16)));
329 #endif
330  _mm_store_ps(&dispArray[0], dispScaled);
331 
332  // Same for other half
333  floatDisp = _mm_cvtepi32_ps(_mm_unpackhi_epi16(disparities, zeroVector));
334  dispScaled = _mm_mul_ps(floatDisp, scaleVector);
335  _mm_store_ps(&dispArray[4], dispScaled);
336 
337  // Iterate over disparities and perform matrix multiplication for each
338  for(int i=0; i<8; i++) {
339  // Create vector
340  __m128 vec = _mm_setr_ps(static_cast<float>(x), static_cast<float>(y), dispArray[i], 1.0);
341 
342  // Multiply with matrix
343  __m128 u1 = _mm_shuffle_ps(vec,vec, _MM_SHUFFLE(0,0,0,0));
344  __m128 u2 = _mm_shuffle_ps(vec,vec, _MM_SHUFFLE(1,1,1,1));
345  __m128 u3 = _mm_shuffle_ps(vec,vec, _MM_SHUFFLE(2,2,2,2));
346  __m128 u4 = _mm_shuffle_ps(vec,vec, _MM_SHUFFLE(3,3,3,3));
347 
348  __m128 prod1 = _mm_mul_ps(u1, qCol0);
349  __m128 prod2 = _mm_mul_ps(u2, qCol1);
350  __m128 prod3 = _mm_mul_ps(u3, qCol2);
351  __m128 prod4 = _mm_mul_ps(u4, qCol3);
352 
353  __m128 multResult = _mm_add_ps(_mm_add_ps(prod1, prod2), _mm_add_ps(prod3, prod4));
354 
355  // Divide by w to receive point coordinates
356  __m128 point = _mm_div_ps(multResult,
357  _mm_shuffle_ps(multResult,multResult, _MM_SHUFFLE(3,3,3,3)));
358 
359  // Write result to memory
360  _mm_store_ps(outputPtr, point);
361 
362  outputPtr += 4;
363  x++;
364  }
365  }
366  }
367 
368  return &pointMap[0];
369 }
370 #endif
371 
372 void Reconstruct3D::Pimpl::writePlyFile(const char* file, const unsigned short* dispMap,
373  const unsigned char* image, int width, int height, ImageSet::ImageFormat format, int dispRowStride,
374  int imageRowStride, const float* q, double maxZ, bool binary, int subpixelFactor) {
375 
376  float* pointMap = createPointMap(dispMap, width, height, dispRowStride, q, 0, subpixelFactor);
377 
378  // Count number of valid points
379  int pointsCount = 0;
380  if(maxZ >= 0) {
381  for(int i=0; i<width*height; i++) {
382  if(pointMap[4*i+2] <= maxZ) {
383  pointsCount++;
384  }
385  }
386  } else {
387  pointsCount = width*height;
388  }
389 
390  // Write file header
391  fstream strm(file, binary ? (ios::out | ios::binary) : ios::out);
392  strm << "ply" << endl;
393 
394  if(binary) {
395  strm << "format binary_little_endian 1.0" << endl;
396  } else {
397  strm << "format ascii 1.0" << endl;
398  }
399 
400  strm << "element vertex " << pointsCount << endl
401  << "property float x" << endl
402  << "property float y" << endl
403  << "property float z" << endl;
404  if (image != nullptr) {
405  // include RGB information only if a camera image was provided
406  strm << "property uchar red" << endl
407  << "property uchar green" << endl
408  << "property uchar blue" << endl;
409  }
410  strm << "end_header" << endl;
411 
412  // Write points
413  for(int i=0; i<width*height; i++) {
414  int y = i / width;
415  int x = i % width;
416 
417  if(maxZ < 0 || pointMap[4*i+2] <= maxZ) {
418  if(binary) {
419  // Write binary format
420  strm.write(reinterpret_cast<char*>(&pointMap[4*i]), sizeof(float)*3);
421  if (image == nullptr) {
422  // disparity only, no image data
423  } else if(format == ImageSet::FORMAT_8_BIT_RGB) {
424  const unsigned char* col = &image[y*imageRowStride + 3*x];
425  strm.write(reinterpret_cast<const char*>(col), 3*sizeof(*col));
426  } else if(format == ImageSet::FORMAT_8_BIT_MONO) {
427  const unsigned char* col = &image[y*imageRowStride + x];
428  unsigned char writeData[3] = {*col, *col, *col};
429  strm.write(reinterpret_cast<const char*>(writeData), sizeof(writeData));
430  } else if(format == ImageSet::FORMAT_12_BIT_MONO) {
431  const unsigned short* col = reinterpret_cast<const unsigned short*>(&image[y*imageRowStride + 2*x]);
432  unsigned char writeData[3] = {
433  (unsigned char)(*col >> 4),
434  (unsigned char)(*col >> 4),
435  (unsigned char)(*col >> 4)
436  };
437  strm.write(reinterpret_cast<const char*>(writeData), sizeof(writeData));
438  }
439  } else {
440  // Write ASCII format
441  if(std::isfinite(pointMap[4*i + 2])) {
442  strm << pointMap[4*i]
443  << " " << pointMap[4*i + 1]
444  << " " << pointMap[4*i + 2];
445  } else {
446  strm << "NaN NaN NaN";
447  }
448 
449  if (image == nullptr) {
450  // disparity only, no image data
451  strm << endl;
452  } else if(format == ImageSet::FORMAT_8_BIT_RGB) {
453  const unsigned char* col = &image[y*imageRowStride + 3*x];
454  strm << " " << static_cast<int>(col[0])
455  << " " << static_cast<int>(col[1])
456  << " " << static_cast<int>(col[2]) << endl;
457  } else if(format == ImageSet::FORMAT_8_BIT_MONO) {
458  const unsigned char* col = &image[y*imageRowStride + x];
459  strm << " " << static_cast<int>(*col)
460  << " " << static_cast<int>(*col)
461  << " " << static_cast<int>(*col) << endl;
462  } else if(format == ImageSet::FORMAT_12_BIT_MONO) {
463  const unsigned short* col = reinterpret_cast<const unsigned short*>(&image[y*imageRowStride + 2*x]);
464  strm << " " << static_cast<int>(*col >> 4)
465  << " " << static_cast<int>(*col >> 4)
466  << " " << static_cast<int>(*col >> 4) << endl;
467  }
468  }
469  }
470  }
471 }
472 
473 void Reconstruct3D::Pimpl::writePlyFile(const char* file, const ImageSet& imageSet,
474  double maxZ, bool binary) {
475  int indexDisp = imageSet.getIndexOf(ImageSet::IMAGE_DISPARITY);
476  int indexImg = imageSet.getIndexOf(ImageSet::IMAGE_LEFT);
477  if(indexDisp == -1) {
478  throw std::runtime_error("No disparity channel present, cannot create point map!");
479  }
480  if(imageSet.getPixelFormat(ImageSet::IMAGE_DISPARITY) != ImageSet::FORMAT_12_BIT_MONO) {
481  throw std::runtime_error("Disparity map must have 12-bit pixel format!");
482  }
483 
484  // write Ply file, passing image data for point colors, if available
485  writePlyFile(file, reinterpret_cast<unsigned short*>(imageSet.getPixelData(indexDisp)),
486  (indexImg == -1) ? nullptr : imageSet.getPixelData(indexImg),
487  imageSet.getWidth(), imageSet.getHeight(),
488  (indexImg == -1) ? ImageSet::FORMAT_8_BIT_MONO : imageSet.getPixelFormat(indexImg),
489  imageSet.getRowStride(indexDisp),
490  (indexImg == -1) ? 0 : imageSet.getRowStride(indexImg),
491  imageSet.getQMatrix(),
492  maxZ, binary, imageSet.getSubpixelFactor());
493 }
494 
495 } // namespace
496 
int getRowStride(int imageNumber) const
Returns the row stride for the pixel data of one image.
Definition: imageset.h:218
const float * getQMatrix() const
Returns a pointer to the disparity-to-depth mapping matrix q.
Definition: imageset.h:293
int getIndexOf(ImageType what, bool throwIfNotFound=false) const
Returns the index of a specific image type.
Definition: imageset.cpp:212
int getHeight() const
Returns the height of each image.
Definition: imageset.h:207
int getSubpixelFactor() const
Gets the subpixel factor for this image set.
Definition: imageset.h:330
ImageFormat
Image formats that can be transferred.
Definition: imageset.h:44
unsigned char * getPixelData(int imageNumber) const
Returns the pixel data for the given image.
Definition: imageset.h:272
void projectSinglePoint(int imageX, int imageY, unsigned short disparity, const float *q, float &pointX, float &pointY, float &pointZ, int subpixelFactor=16)
Reconstructs the 3D location of one individual point.
bool hasImageType(ImageType what) const
Returns whether a left camera image is included in the enabled data.
Definition: imageset.h:434
A set of one to three images, but usually two (the left camera image and the disparity map)...
Definition: imageset.h:38
float * createPointMap(const unsigned short *dispMap, int width, int height, int rowStride, const float *q, unsigned short minDisparity=1, int subpixelFactor=16)
Reconstructs the 3D location of each pixel in the given disparity map.
ImageFormat getPixelFormat(int imageNumber) const
Returns the pixel format for the given image.
Definition: imageset.h:245
void writePlyFile(const char *file, const unsigned short *dispMap, const unsigned char *image, int width, int height, ImageSet::ImageFormat format, int dispRowStride, int imageRowStride, const float *q, double maxZ=std::numeric_limits< double >::max(), bool binary=false, int subpixelFactor=16)
Projects the given disparity map to 3D points and exports the result to a PLY file.
int getWidth() const
Returns the width of each image.
Definition: imageset.h:202
Nerian Vision Technologies