ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvCameraSensor.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "ecvCameraSensor.h"
9 
10 #include <cmath>
11 
12 // LOCAL
13 #include "ecvDisplayTools.h"
14 #include "ecvImage.h"
15 #include "ecvMesh.h"
16 #include "ecvPointCloud.h"
17 
18 // CV_CORE_LIB
19 #include <ConjugateGradient.h>
20 
21 // QT
22 #include <QDir>
23 
24 // Qt5/Qt6 Compatibility
25 #include <QtCompat.h>
26 
28  : vertFocal_pix(1.0f),
29  skew(0),
30  vFOV_rad(0),
31  zNear_mm(0.001f),
32  zFar_mm(1000.0f),
33  arrayWidth(0),
34  arrayHeight(0) {
35  pixelSize_mm[0] = 1.0f;
36  pixelSize_mm[1] = 1.0f;
37 
38  principal_point[0] = arrayWidth / 2.0f;
39  principal_point[1] = arrayHeight / 2.0f;
40 }
41 
43  IntrinsicParameters& params) {
44  // default Kinect parameters from:
45  // "Accuracy and Resolution of Kinect Depth Data for Indoor Mapping
46  // Applications" Kourosh Khoshelham and Sander Oude Elberink
47  float focal_mm = static_cast<float>(
48  5.45 * 1.0e-3); // focal length (real distance in meter)
49  float pixelSize_mm = static_cast<float>(
50  9.3 * 1.0e-6); // pixel size (real distance in meter)
51  params.vertFocal_pix = ConvertFocalMMToPix(focal_mm, pixelSize_mm);
52  params.pixelSize_mm[0] = pixelSize_mm;
53  params.pixelSize_mm[1] = pixelSize_mm;
54  params.skew = static_cast<float>(0.0); // skew in image
55  params.vFOV_rad = static_cast<float>(
56  43.0 * M_PI / 180.0); // vertical field of view (in rad)
57  params.zNear_mm = static_cast<float>(
58  0.5); // distance to the closest recordable depth
59  params.zFar_mm = static_cast<float>(
60  5.0); // distance to the furthest recordable depth
61  params.arrayWidth = 640; // image width
62  params.arrayHeight = 480; // image height
63  params.principal_point[0] = params.arrayWidth / 2.0f;
64  params.principal_point[1] = params.arrayHeight / 2.0f;
65 }
66 
68  principalPointOffset[0] = 0;
69  principalPointOffset[1] = 0;
70  linearDisparityParams[0] = 0;
71  linearDisparityParams[1] = 0;
72  K_BrownParams[0] = 0;
73  K_BrownParams[1] = 0;
74  K_BrownParams[2] = 0;
75  P_BrownParams[0] = 0;
76  P_BrownParams[1] = 0;
77 }
78 
80  BrownDistortionParameters& params) {
81  // default Kinect parameters from:
82  // "Accuracy and Resolution of Kinect Depth Data for Indoor Mapping
83  // Applications" Kourosh Khoshelham and Sander Oude Elberink
84  params.principalPointOffset[0] = static_cast<float>(-0.063 * 1.0e-3);
85  params.principalPointOffset[1] = static_cast<float>(-0.039 * 1.0e-3);
86  params.linearDisparityParams[0] = static_cast<float>(-2.85 * 1.0e-3);
87  params.linearDisparityParams[1] = static_cast<float>(3.0);
88  params.K_BrownParams[0] = static_cast<float>(2.42 * 1.0e-3);
89  params.K_BrownParams[1] = static_cast<float>(-1.7 * 1.0e-4);
90  params.K_BrownParams[2] = static_cast<float>(0.0);
91  params.P_BrownParams[0] = static_cast<float>(-3.3 * 1.0e-4);
92  params.P_BrownParams[1] = static_cast<float>(5.25 * 1.0e-4);
93 }
94 
96  : isComputed(false),
97  drawFrustum(false),
98  drawSidePlanes(false),
99  frustumCorners(nullptr),
100  frustumHull(nullptr) {}
101 
103  // always delete the hull before the corners, are it depends on them!
104  if (frustumHull) {
105  delete frustumHull;
106  frustumHull = nullptr;
107  }
108  if (frustumCorners) {
109  delete frustumCorners;
110  frustumCorners = nullptr;
111  }
112 }
113 
115  if (!frustumCorners) {
116  frustumCorners = new ccPointCloud("Frustum corners");
117  } else {
118  frustumCorners->clear();
119  }
120 
121  if (!frustumCorners->reserve(8)) {
122  // not enough memory to load frustum corners!
123  delete frustumCorners;
124  frustumCorners = nullptr;
125  return false;
126  }
127  return true;
128 }
129 
131  // we only need to do this once!
132  if (frustumHull) return true;
133 
134  if (!frustumCorners || frustumCorners->size() < 8) {
136  "[ccCameraSensor::FrustumInformation::initFrustumHull] Corners "
137  "are not initialized!");
138  return false;
139  }
140 
141  frustumHull = new ccMesh(frustumCorners);
142  if (!frustumHull->reserve(6 * 2)) {
144  "[ccCameraSensor::FrustumInformation::initFrustumHull] Not "
145  "enough memory!");
146  delete frustumHull;
147  frustumHull = nullptr;
148  return false;
149  }
150 
151  frustumHull->addTriangle(0, 2, 3);
152  frustumHull->addTriangle(0, 3, 1);
153 
154  frustumHull->addTriangle(2, 4, 5);
155  frustumHull->addTriangle(2, 5, 3);
156 
157  frustumHull->addTriangle(4, 6, 7);
158  frustumHull->addTriangle(4, 7, 5);
159 
160  frustumHull->addTriangle(6, 0, 1);
161  frustumHull->addTriangle(6, 1, 7);
162 
163  frustumHull->addTriangle(6, 4, 2);
164  frustumHull->addTriangle(6, 2, 0);
165 
166  frustumHull->addTriangle(1, 3, 5);
167  frustumHull->addTriangle(1, 5, 7);
168 
169  frustumHull->setVisible(true);
170 
171  return true;
172 }
173 
175  : ccSensor("Camera Sensor"),
178  // graphic representation
179  lockVisibility(false);
181 }
182 
184  : ccSensor("Camera Sensor"),
185  m_plane_color(ecvColor::red),
186  m_projectionMatrixIsValid(false) {
187  // graphic representation
188  lockVisibility(false);
190 
191  // projection
192  setIntrinsicParameters(iParams);
193 }
194 
196  : ccSensor(sensor),
197  m_plane_color(ecvColor::red),
198  m_projectionMatrix(sensor.m_projectionMatrix),
199  m_projectionMatrixIsValid(false) {
201 
202  // distortion params
203  if (m_distortionParams) {
204  LensDistortionParameters::Shared clonedDistParams;
205  switch (m_distortionParams->getModel()) {
207  // simply duplicate the struct
210  *clone = *static_cast<const RadialDistortionParameters*>(
211  sensor.m_distortionParams.data());
212  clonedDistParams = LensDistortionParameters::Shared(clone);
213  } break;
214 
216  // simply duplicate the struct
219  *clone =
220  *static_cast<const ExtendedRadialDistortionParameters*>(
221  sensor.m_distortionParams.data());
222  clonedDistParams = LensDistortionParameters::Shared(clone);
223  } break;
224 
225  case BROWN_DISTORTION: {
226  // simply duplicate the struct
229  *clone = *static_cast<const BrownDistortionParameters*>(
230  sensor.m_distortionParams.data());
231  clonedDistParams = LensDistortionParameters::Shared(clone);
232  } break;
233 
234  default:
235  // unhandled type?!
236  assert(false);
237  break;
238  }
239  setDistortionParameters(clonedDistParams);
240  }
241 }
242 
244 
245 ccBBox ccCameraSensor::getOwnBB(bool withGLFeatures /*=false*/) {
246  if (!withGLFeatures) {
247  return ccBBox();
248  }
249 
250  // get current sensor position
251  ccIndexedTransformation sensorPos;
252  if (!getAbsoluteTransformation(sensorPos, m_activeIndex)) {
253  return ccBBox();
254  }
255 
256  CCVector3 upperLeftPoint = computeUpperLeftPoint();
257 
258  ccPointCloud cloud;
259  if (!cloud.reserve(5)) {
260  // not enough memory?!
261  return ccBBox();
262  }
263 
264  cloud.addPoint(CCVector3(0, 0, 0));
265  cloud.addPoint(
266  CCVector3(upperLeftPoint.x, upperLeftPoint.y, -upperLeftPoint.z));
267  cloud.addPoint(
268  CCVector3(-upperLeftPoint.x, upperLeftPoint.y, -upperLeftPoint.z));
269  cloud.addPoint(
270  CCVector3(-upperLeftPoint.x, -upperLeftPoint.y, -upperLeftPoint.z));
271  cloud.addPoint(
272  CCVector3(upperLeftPoint.x, -upperLeftPoint.y, -upperLeftPoint.z));
273 
274  // add frustum corners if necessary
278  unsigned cornerCount = m_frustumInfos.frustumCorners->size();
279  if (cloud.reserve(cloud.size() + cornerCount)) {
280  for (unsigned i = 0; i < cornerCount; ++i)
282  }
283  }
284 
285  cloud.applyRigidTransformation(sensorPos);
286  return cloud.getOwnBB(false);
287 }
288 
290  // get current sensor position
291  ccIndexedTransformation sensorPos;
292  if (!getAbsoluteTransformation(sensorPos, m_activeIndex)) {
293  return ccBBox();
294  }
295 
296  trans = sensorPos;
297 
298  CCVector3 upperLeftPoint = computeUpperLeftPoint();
299  if (upperLeftPoint.z > 0) {
300  return ccBBox(-upperLeftPoint,
301  CCVector3(upperLeftPoint.x, upperLeftPoint.y, 0));
302  } else {
303  return ccBBox(CCVector3(upperLeftPoint.x, upperLeftPoint.y, 0),
304  -upperLeftPoint);
305  }
306 }
307 
308 void ccCameraSensor::setVertFocal_pix(float vertFocal_pix) {
309  assert(vertFocal_pix > 0);
310  m_intrinsicParams.vertFocal_pix = vertFocal_pix;
311 
312  // old frustum is not valid anymore!
313  m_frustumInfos.isComputed = false;
314  // same thing for the projection matrix
316 }
317 
319  assert(fov_rad > 0);
320  m_intrinsicParams.vFOV_rad = fov_rad;
321 }
322 
324  m_intrinsicParams = params;
325  // old frustum is not valid anymore!
326  m_frustumInfos.isComputed = false;
327  // same thing for the projection matrix
329 }
330 
334  "[ccCameraSensor::applyViewport] No associated display!");
335  return false;
336  }
337 
339  if (!getActiveAbsoluteTransformation(trans)) {
340  return false;
341  }
342 
343  if (m_intrinsicParams.arrayHeight <= 0) {
344  CVLog::Warning("[ccCameraSensor::applyViewport] Sensor height is 0!");
345  return false;
346  }
347 
349 
351  viewParams.fov_deg = fov_deg;
352  viewParams.zFar = static_cast<double>(m_intrinsicParams.zFar_mm);
353  viewParams.zNear = static_cast<double>(m_intrinsicParams.zNear_mm);
354 
355  CCVector3 upperLeftPointd = computeUpperLeftPoint();
356 
357  // pos
358  CCVector3 camC = trans.getTranslationAsVec3D();
359  viewParams.setCameraCenter(CCVector3d::fromArray(camC));
360 
361  int flag = 1;
362  if (upperLeftPointd.z > 0) {
363  flag = 1;
364  } else {
365  flag = -1;
366  }
367 
368  // up
369  CCVector3 upDir = flag * trans.getColumnAsVec3D(1);
370  upDir.normalize();
371  if (cloudViewer::LessThanEpsilon(std::fabs(upDir.norm()))) {
373  "[ccCameraSensor::applyViewport] Viewing dir is parallel to "
374  "the plane Y!");
375  return false;
376  }
377  viewParams.up = CCVector3d::fromArray(upDir);
378 
379  // focal
380  CCVector3 viewDir = flag * trans.getColumnAsVec3D(2);
381  CCVector3 focal3D = camC - viewDir;
382  viewParams.focal = CCVector3d::fromArray(focal3D);
383  viewParams.setPivotPoint(viewParams.focal, true);
384 
387 
388  return true;
389 }
390 
393 
394  matrix = m_projectionMatrix;
395 
396  return m_projectionMatrixIsValid; // even if we have computed the
397  // projection matrix, it may still have
398  // failed!
399 }
400 
403  float* mat = m_projectionMatrix.data();
404 
405  // diagonal
406  mat[0] = getHorizFocal_pix();
407  mat[5] = getVertFocal_pix();
408  mat[10] = 1.0f;
409  mat[15] = 1.0f;
410 
411  // skew
412  mat[4] = m_intrinsicParams.skew;
413 
414  // translation from image (0,0)
415  mat[12] = m_intrinsicParams.principal_point[0];
416  mat[13] = m_intrinsicParams.principal_point[1];
417 
419 }
420 
421 bool ccCameraSensor::toFile_MeOnly(QFile& out, short dataVersion) const {
422  assert(out.isOpen() && (out.openMode() & QIODevice::WriteOnly));
423  if (dataVersion < 38) {
424  assert(false);
425  return false;
426  }
427 
428  if (!ccSensor::toFile_MeOnly(out, dataVersion)) return false;
429 
430  // projection matrix (35 <= dataVersion < 38)
431  // if (!m_projectionMatrix.toFile(out, dataVersion))
432  // return WriteError();
433 
436  // IntrinsicParameters
437  QDataStream outStream(&out);
438  outStream << m_intrinsicParams.vertFocal_pix;
439  outStream << m_intrinsicParams.arrayWidth;
440  outStream << m_intrinsicParams.arrayHeight;
441  outStream << m_intrinsicParams.pixelSize_mm[0];
442  outStream << m_intrinsicParams.pixelSize_mm[1];
443  outStream << m_intrinsicParams.skew;
444  outStream << m_intrinsicParams.vFOV_rad;
445  outStream << m_intrinsicParams.zNear_mm;
446  outStream << m_intrinsicParams.zFar_mm;
447  outStream << m_intrinsicParams.principal_point[0];
448  outStream << m_intrinsicParams.principal_point[1];
449 
450  // distortion parameters (dataVersion>=38)
452  ? m_distortionParams->getModel()
454  outStream << static_cast<uint32_t>(distModel);
455 
456  if (m_distortionParams) {
457  switch (m_distortionParams->getModel()) {
460  static_cast<RadialDistortionParameters*>(
461  m_distortionParams.data());
462  outStream << params->k1;
463  outStream << params->k2;
464  } break;
465 
469  m_distortionParams.data());
470  outStream << params->k1;
471  outStream << params->k2;
472  outStream << params->k3;
473  } break;
474 
475  case BROWN_DISTORTION: {
476  BrownDistortionParameters* params =
477  static_cast<BrownDistortionParameters*>(
478  m_distortionParams.data());
479  outStream << params->K_BrownParams[0];
480  outStream << params->K_BrownParams[1];
481  outStream << params->K_BrownParams[2];
482  outStream << params->P_BrownParams[0];
483  outStream << params->P_BrownParams[1];
484  outStream << params->principalPointOffset[0];
485  outStream << params->principalPointOffset[1];
486  outStream << params->linearDisparityParams[0];
487  outStream << params->linearDisparityParams[1];
488  } break;
489  default:
490  assert(false);
491  break;
492  }
493  }
494 
495  // FrustumInformation
496  outStream << m_frustumInfos.drawFrustum;
497  outStream << m_frustumInfos.drawSidePlanes;
498  outStream << m_frustumInfos.center.x;
499  outStream << m_frustumInfos.center.y;
500  outStream << m_frustumInfos.center.z;
501 
502  return true;
503 }
504 
506  // Camera sensor with intrinsic/distortion params requires version 43+
507  return std::max(static_cast<short>(43),
509 }
510 
512  short dataVersion,
513  int flags,
514  LoadedIDMap& oldToNewIDMap) {
515  if (!ccSensor::fromFile_MeOnly(in, dataVersion, flags, oldToNewIDMap))
516  return false;
517 
518  // serialization wasn't possible before v3.5!
519  if (dataVersion < 35) return false;
520 
521  // projection matrix (35 <= dataVersion < 38)
522  if (dataVersion < 38) {
523  // we don't need to save/load this matrix as it is dynamically computed!
524  ccGLMatrix dummyMatrix;
525  if (!dummyMatrix.fromFile(in, dataVersion, flags, oldToNewIDMap))
526  return ReadError();
527  }
529 
532  // IntrinsicParameters
533  QDataStream inStream(&in);
534  inStream >> m_intrinsicParams.vertFocal_pix;
535  inStream >> m_intrinsicParams.arrayWidth;
536  inStream >> m_intrinsicParams.arrayHeight;
537  inStream >> m_intrinsicParams.pixelSize_mm[0];
538  inStream >> m_intrinsicParams.pixelSize_mm[1];
539  inStream >> m_intrinsicParams.skew;
540  inStream >> m_intrinsicParams.vFOV_rad;
541  inStream >> m_intrinsicParams.zNear_mm;
542  inStream >> m_intrinsicParams.zFar_mm;
543 
544  if (dataVersion >= 43) {
545  // since version 43, we added the principal point
546  inStream >> m_intrinsicParams.principal_point[0];
547  inStream >> m_intrinsicParams.principal_point[1];
548  } else {
553  }
554 
555  // distortion parameters
557  if (dataVersion < 38) {
558  // before v38, only Brown's parameters were used (and always set)
559  distModel = BROWN_DISTORTION;
560  } else {
561  uint32_t distModeli;
562  inStream >> distModeli;
563  distModel = static_cast<DistortionModel>(distModeli);
564  }
565 
566  // load parameters (if any)
567  switch (distModel) {
569  RadialDistortionParameters* distParams =
571  inStream >> distParams->k1;
572  inStream >> distParams->k2;
573 
576  } break;
577 
581  inStream >> distParams->k1;
582  inStream >> distParams->k2;
583  inStream >> distParams->k3;
584 
587  } break;
588 
589  case BROWN_DISTORTION: {
590  BrownDistortionParameters* distParams =
592  inStream >> distParams->K_BrownParams[0];
593  inStream >> distParams->K_BrownParams[1];
594  inStream >> distParams->K_BrownParams[2];
595  inStream >> distParams->P_BrownParams[0];
596  inStream >> distParams->P_BrownParams[1];
597  inStream >> distParams->principalPointOffset[0];
598  inStream >> distParams->principalPointOffset[1];
599  inStream >> distParams->linearDisparityParams[0];
600  inStream >> distParams->linearDisparityParams[1];
601 
604  } break;
605 
606  default:
607  // do nothing
608  break;
609  }
610 
611  // FrustumInformation
612  if (dataVersion < 38) {
613  bool dummyBool; // formerly: m_frustumInfos.isComputed (no need to
614  // save/load it!)
615  inStream >> dummyBool;
616  }
617  m_frustumInfos.isComputed = false;
618  inStream >> m_frustumInfos.drawFrustum;
619  inStream >> m_frustumInfos.drawSidePlanes;
621  m_frustumInfos.center.u, 3);
622 
623  if (dataVersion < 38) {
624  // frustum corners: no need to save/load them!
625  for (unsigned i = 0; i < 8; ++i) {
626  CCVector3 P;
628  3);
629  }
630  }
631 
632  return true;
633 }
634 
636  CCVector3& globalCoord) const {
638 
639  if (!getActiveAbsoluteTransformation(trans)) return false;
640 
641  globalCoord = localCoord;
642  trans.apply(globalCoord);
643 
644  return true;
645 }
646 
648  CCVector3& localCoord) const {
650 
651  if (!getActiveAbsoluteTransformation(trans)) return false;
652 
653  localCoord = globalCoord;
654  trans.inverse().apply(localCoord);
655 
656  return true;
657 }
658 
660  const CCVector3& localCoord,
661  CCVector2& imageCoord,
662  bool withLensError /*=true*/) const {
663 #ifdef CHECK_THIS_AFTERWARDS
664 
665  // Change in 3D image coordinates system for good projection
666  CCVector3 imageCoordSystem(localCoord.x, localCoord.y, -localCoord.z);
667 
668  // We test if the point is in front or behind the sensor ? If it is behind
669  // (or in the center of the sensor i.e. z=0.0), we can't project!
670  if (imageCoordSystem.z < FLT_EPSILON) return false;
671 
672  // projection
673  ccGLMatrix mat;
674  if (!getProjectionMatrix(mat)) return false;
675  CCVector3 projCoord =
676  mat *
677  imageCoordSystem; // at this stage, coordinates are homogeneous
678  projCoord = projCoord / projCoord.z; // coordinates are now in pixels
679  CCVector2 initial(projCoord.x, projCoord.y);
680  CCVector2 coord = initial;
681 
682  // apply lens correction if necessary
683  // if (withLensError)
684  // fromIdealImCoordToRealImCoord(initial, coord);
685 
686  // test if the projected point is into the image boundaries (width,height)
687  if (coord.x < 0 || coord.x >= m_intrinsicParams.arrayWidth || coord.y < 0 ||
688  coord.y >= m_intrinsicParams.arrayHeight) {
689  return false;
690  }
691 
692  // Change in 3D image coordinates system
693  imageCoord = coord;
694 
695 #else
696 
697  // We test if the point is in front or behind the sensor ? If it is behind
698  // (or in the center of the sensor i.e. depth = 0), we can't project!
699  double depth = -static_cast<double>(
700  localCoord.z); // warning: the camera looks backward!
701 #define BACK_POINTS_CULLING
702 #ifdef BACK_POINTS_CULLING
703  if (depth < FLT_EPSILON) return false;
704 #endif
705 
706  // perspective division
707  CCVector2d p(localCoord.x / depth, localCoord.y / depth);
708 
709  // conversion to pixel coordinates
710  double factor = m_intrinsicParams.vertFocal_pix;
711 
712  // apply radial distortion (if any)
713  if (withLensError && m_distortionParams) {
714  if (m_distortionParams->getModel() == SIMPLE_RADIAL_DISTORTION) {
715  const RadialDistortionParameters* params =
716  static_cast<RadialDistortionParameters*>(
717  m_distortionParams.data());
718  double norm2 = p.norm2();
719  double rp = 1.0 +
720  norm2 * (params->k1 +
721  norm2 * params->k2); // scaling factor to undo
722  // the radial distortion
723  factor *= rp;
724  } else if (m_distortionParams->getModel() ==
726  const ExtendedRadialDistortionParameters* params =
728  m_distortionParams.data());
729  double norm2 = p.norm2();
730  double rp =
731  1.0 +
732  norm2 * (params->k1 +
733  norm2 * (params->k2 +
734  norm2 * params->k3)); // scaling factor
735  // to undo the
736  // radial
737  // distortion
738  factor *= rp;
739  }
740  }
741  //*/
742 
743  CCVector2d p2 = p * factor;
744 
746  p2.y = m_intrinsicParams.principal_point[1] - p2.y;
747 
748  imageCoord.x = static_cast<PointCoordinateType>(p2.x);
749  imageCoord.y = static_cast<PointCoordinateType>(p2.y);
750 
751 #endif
752 
753  return true;
754 }
755 
757  const CCVector2& imageCoord,
758  CCVector3& localCoord,
759  PointCoordinateType depth,
760  bool withLensCorrection /*=true*/) const {
761  CCVector3d p2(imageCoord.x, imageCoord.y, 0.0);
762 
764  p2.y = m_intrinsicParams.principal_point[1] - p2.y;
765 
766  // apply inverse radial distortion (if any)
767  // TODO
768 
769  double factor = static_cast<double>(m_intrinsicParams.vertFocal_pix);
770  CCVector3d p = p2 / factor;
771 
772  // perspective
773  localCoord =
774  CCVector3(static_cast<PointCoordinateType>(p.x * depth),
775  static_cast<PointCoordinateType>(p.y * depth), -depth);
776 
777  return true;
778 }
779 
781  const CCVector3& globalCoord,
782  CCVector2& imageCoord,
783  bool withLensError /*=true*/) const {
784  CCVector3 localCoord;
785  if (!fromGlobalCoordToLocalCoord(globalCoord, localCoord)) return false;
786 
787  return fromLocalCoordToImageCoord(localCoord, imageCoord, withLensError);
788 }
789 
791  const CCVector2& imageCoord,
792  CCVector3& globalCoord,
794  bool withLensCorrection /*=true*/) const {
796 
797  if (!getActiveAbsoluteTransformation(trans)) return false;
798 
799  CCVector3 localCoord;
800  if (!fromImageCoordToLocalCoord(imageCoord, localCoord, PC_ONE,
801  withLensCorrection))
802  return false;
803 
804  // update altitude: we must compute the intersection between the plane Z =
805  // Z0 (world) and the camera (input pixel) viewing direction
806  CCVector3 viewDir = localCoord;
807  trans.applyRotation(viewDir);
808  viewDir.normalize();
809 
810  if (cloudViewer::LessThanEpsilon(fabs(viewDir.z))) {
811  // viewing dir is parallel to the plane Z = Z0!
812  return false;
813  }
814 
815  CCVector3 camC = trans.getTranslationAsVec3D();
816  PointCoordinateType dZ = z0 - camC.z;
817 
818  PointCoordinateType u = dZ / viewDir.z;
819 #ifdef BACK_POINTS_CULLING
820  if (u < 0) return false; // wrong direction!
821 #endif
822 
823  globalCoord = camC + u * viewDir;
824 
825  return true;
826 }
827 
829  CCVector2& ideal) const {
830  // no distortion parameters?
831  if (!m_distortionParams) {
832  ideal = real;
833  return true;
834  }
835 
836  switch (m_distortionParams->getModel()) {
839  // TODO: we need a pre-computed distortion map to do this!
840  } break;
841 
842  case BROWN_DISTORTION: {
843  const BrownDistortionParameters* distParams =
844  static_cast<BrownDistortionParameters*>(
845  m_distortionParams.data());
846  const float& sX = m_intrinsicParams.pixelSize_mm[0];
847  const float& sY = m_intrinsicParams.pixelSize_mm[1];
848 
849  // 1st correction : principal point correction
850  float cx = m_intrinsicParams.principal_point[0] +
851  distParams->principalPointOffset[0] / sX; // in pixels
852  float cy = m_intrinsicParams.principal_point[1] +
853  distParams->principalPointOffset[1] / sY; // in pixels
854 
855  // 2nd correction : Brown's lens distortion correction
856  float dx = (static_cast<float>(real.x) - cx) *
857  m_intrinsicParams.pixelSize_mm[0]; // real distance
858  float dy = (static_cast<float>(real.y) - cy) *
859  m_intrinsicParams.pixelSize_mm[1]; // real distance
860  float dx2 = dx * dx;
861  float dy2 = dy * dy;
862  float r = sqrt(dx2 + dy2);
863  float r2 = r * r;
864  float r4 = r2 * r2;
865  float r6 = r4 * r2;
866  const float& K1 = distParams->K_BrownParams[0];
867  const float& K2 = distParams->K_BrownParams[1];
868  const float& K3 = distParams->K_BrownParams[2];
869  const float& P1 = distParams->P_BrownParams[0];
870  const float& P2 = distParams->P_BrownParams[1];
871 
872  // compute new value
873  float correctedX = (dx * (1 + K1 * r2 + K2 * r4 + K3 * r6) +
874  P1 * (r2 + 2 * dx2) + 2 * P2 * dx * dy);
875  float correctedY = (dy * (1 + K1 * r2 + K2 * r4 + K3 * r6) +
876  P2 * (r2 + 2 * dy2) + 2 * P1 * dx * dy);
877  ideal.x = static_cast<PointCoordinateType>(correctedX / sX);
878  ideal.y = static_cast<PointCoordinateType>(correctedY / sY);
879 
880  // We test if the new pixel falls inside the image boundaries
881  // return ( ideal.x >= 0 && ideal.x < m_intrinsicParams.arrayWidth
882  // && ideal.y >= 0 && ideal.y <
883  // m_intrinsicParams.arrayHeight
884  //); DGM: the ideal pixel can be outside of the original image of
885  // course!!!
886  return true;
887  }
888 
889  default:
890  // not handled?
891  assert(false);
892  break;
893  }
894 
895  return false;
896 }
897 
899  const float depth,
900  Vector3Tpl<ScalarType>& sigma) const {
901  // no distortion parameters?
902  if (!m_distortionParams) {
903  return false;
904  }
905 
906  switch (m_distortionParams->getModel()) {
909  // TODO
910  return false;
911  } break;
912 
913  case BROWN_DISTORTION: {
914  const BrownDistortionParameters* distParams =
915  static_cast<BrownDistortionParameters*>(
916  m_distortionParams.data());
917  // TODO ==> check if the input pixel coordinate must be the real or
918  // ideal projection
919 
920  const int& width = m_intrinsicParams.arrayWidth;
921  const int& height = m_intrinsicParams.arrayHeight;
922  const float* c = m_intrinsicParams.principal_point;
923 
924  // check validity
925  if (pixel.x < 0 || pixel.x > width || pixel.y < 0 ||
926  pixel.y > height || depth < FLT_EPSILON)
927  return false;
928 
929  // init parameters
930  const float& A = distParams->linearDisparityParams[0];
931  float z2 = depth * depth;
932  float invSigmaD = 8.0f;
933  float factor = A * z2 / invSigmaD;
934 
935  const float& mu = m_intrinsicParams.pixelSize_mm[0];
936  const float verFocal_pix = getVertFocal_pix();
937  const float horizFocal_pix = getHorizFocal_pix();
938 
939  // computes uncertainty
940  sigma.x = static_cast<ScalarType>(
941  std::abs(factor * (pixel.x - c[0]) / horizFocal_pix));
942  sigma.y = static_cast<ScalarType>(
943  std::abs(factor * (pixel.y - c[1]) / verFocal_pix));
944  sigma.z = static_cast<ScalarType>(std::abs(factor * mu));
945 
946  return true;
947  }
948 
949  default: {
950  // not handled?
951  assert(false);
952  } break;
953  }
954 
955  return false;
956 }
957 
960  std::vector<Vector3Tpl<ScalarType>>&
961  accuracy /*, bool lensCorrection*/) {
962  if (!points || points->size() == 0) {
964  "[ccCameraSensor::computeUncertainty] Internal error: invalid "
965  "input cloud");
966  return false;
967  }
968 
969  if (!m_distortionParams ||
970  m_distortionParams->getModel() != BROWN_DISTORTION) {
972  "[ccCameraSensor::computeUncertainty] Sensor has no associated "
973  "uncertainty model! (Brown, etc.)");
974  return false;
975  }
976 
977  unsigned count = points->size();
978  accuracy.clear();
979  try {
980  accuracy.resize(count);
981  } catch (const std::bad_alloc&) {
983  "[ccCameraSensor::computeUncertainty] Not enough memory!");
984  return false;
985  }
986 
987  for (unsigned i = 0; i < count; i++) {
988  const CCVector3* coordGlobal = points->getPoint(i);
989  CCVector3 coordLocal;
990  CCVector2 coordImage;
991 
992  if (fromGlobalCoordToLocalCoord(*coordGlobal, coordLocal) &&
993  fromLocalCoordToImageCoord(coordLocal, coordImage)) {
994  computeUncertainty(coordImage, std::abs(coordLocal.z), accuracy[i]);
995  } else {
996  accuracy[i].x = accuracy[i].y = accuracy[i].z = NAN_VALUE;
997  }
998  }
999 
1000  return true;
1001 }
1002 
1003 // see
1004 // http://opencv.willowgarage.com/documentation/cpp/camera_calibration_and_3d_reconstruction.html
1005 QImage ccCameraSensor::undistort(const QImage& image) const {
1006  if (image.isNull()) {
1007  CVLog::Warning("[ccCameraSensor::undistort] Invalid input image!");
1008  return QImage();
1009  }
1010 
1011  // nothing to do
1012  // no distortion parameters?
1013  if (!m_distortionParams) {
1014  CVLog::Warning("[ccCameraSensor::undistort] No distortion model set!");
1015  return QImage();
1016  }
1017 
1018  switch (m_distortionParams->getModel()) {
1021  const RadialDistortionParameters* params =
1022  static_cast<RadialDistortionParameters*>(
1023  m_distortionParams.data());
1024  float k1 = params->k1;
1025  float k2 = params->k2;
1026  if (k1 == 0 && k2 == 0) {
1028  "[ccCameraSensor::undistort] Invalid radial distortion "
1029  "coefficients!");
1030  return QImage();
1031  }
1032  float k3 = 0;
1033  if (m_distortionParams->getModel() == EXTENDED_RADIAL_DISTORTION) {
1034  k3 = static_cast<ExtendedRadialDistortionParameters*>(
1035  m_distortionParams.data())
1036  ->k3;
1037  }
1038 
1039  int width = image.width();
1040  int height = image.height();
1041 
1042  float xScale = image.width() /
1043  static_cast<float>(m_intrinsicParams.arrayWidth);
1044  float yScale = image.height() /
1045  static_cast<float>(m_intrinsicParams.arrayHeight);
1046  float rScale = sqrt(xScale * xScale + yScale * yScale);
1047 
1048  // try to reserve memory for new image
1049  QImage newImage(QSize(width, height), image.format());
1050  if (newImage.isNull()) {
1052  "[ccCameraSensor::undistort] Not enough memory!");
1053  return QImage();
1054  }
1055  newImage.fill(0);
1056 
1057  float vertFocal_pix = getVertFocal_pix() * xScale;
1058  float horizFocal_pix = getHorizFocal_pix() * yScale;
1059  float vf2 = vertFocal_pix * vertFocal_pix;
1060  float hf2 = horizFocal_pix * horizFocal_pix;
1061  float cx = m_intrinsicParams.principal_point[0] * xScale;
1062  float cy = m_intrinsicParams.principal_point[1] * yScale;
1063  k1 *= rScale;
1064  k2 *= rScale;
1065  k3 *= rScale;
1066 
1067  assert((image.depth() % 8) == 0);
1068  int depth = image.depth() / 8;
1069  int bytesPerLine = image.bytesPerLine();
1070  const uchar* iImageBits = image.bits();
1071  uchar* oImageBits = newImage.bits();
1072 
1073  // image undistortion
1074  {
1075  for (int i = 0; i < width; ++i) {
1076  float x = i - cx;
1077  float x2 = x * x;
1078  for (int j = 0; j < height; ++j) {
1079  float y = j - cy;
1080  float y2 = y * y;
1081 
1082  float p2 = x2 / hf2 + y2 / vf2; // p = pix/f
1083  float rp =
1084  1.0f +
1085  p2 * (k1 +
1086  p2 * (k2 + p2 * k3)); // r(p) = 1.0 + k1
1087  // * ||p||^2 + k2 *
1088  // ||p||^4 + k3 *
1089  // ||p||^6
1090  float eqx = rp * x + cx;
1091  float eqy = rp * y + cy;
1092 
1093  int pixx = static_cast<int>(eqx);
1094  int pixy = static_cast<int>(eqy);
1095  if (pixx >= 0 && pixx < width && pixy >= 0 &&
1096  pixy < height) {
1097  const uchar* iPixel =
1098  iImageBits + j * bytesPerLine + i * depth;
1099  uchar* oPixel = oImageBits + pixy * bytesPerLine +
1100  pixx * depth;
1101  memcpy(oPixel, iPixel, depth);
1102  // newImage.setPixel(i, j, image.pixel(pixx, pixy));
1103  }
1104  }
1105  }
1106  }
1107 
1108  return newImage;
1109  } break;
1110 
1111  case BROWN_DISTORTION:
1112  // TODO
1113  break;
1114 
1115  default:
1116  // not handled?
1117  assert(false);
1118  break;
1119  }
1120 
1122  "[ccCameraSensor::undistort] Can't undistort the image with the "
1123  "current distortion model!");
1124 
1125  return QImage();
1126 }
1127 
1129  bool inplace /*=true*/) const {
1130  if (!image || image->data().isNull()) {
1132  "[ccCameraSensor::undistort] Invalid/empty input image!");
1133  return nullptr;
1134  }
1135 
1136  QImage newImage = undistort(image->data());
1137  if (newImage.isNull()) {
1138  // warning message should have been already issued
1139  return nullptr;
1140  }
1141 
1142  // update image parameters
1143  if (inplace) {
1144  image->setData(newImage);
1145  return image;
1146  } else {
1147  return new ccImage(newImage, image->getName() + QString(".undistort"));
1148  }
1149 }
1150 
1152  const CCVector3& globalCoord /*, bool withLensCorrection*/) const {
1153  CCVector3 localCoord;
1154 
1155  // Tests if the projection is in the field of view
1156  if (!fromGlobalCoordToLocalCoord(globalCoord,
1157  localCoord /*, withLensCorrection*/))
1158  return false;
1159 
1160  // Tests if the projected point is between zNear and zFar
1161  const float& z = localCoord.z;
1162  const float& n = m_intrinsicParams.zNear_mm;
1163  const float& f = m_intrinsicParams.zFar_mm;
1164 
1165  return (-z <= f && -z > n && std::abs(f + z) >= FLT_EPSILON &&
1166  std::abs(n + z) >= FLT_EPSILON);
1167 }
1168 
1170  if (m_intrinsicParams.arrayHeight == 0) return CCVector3(0, 0, 0);
1171 
1172  float ar = m_intrinsicParams.arrayHeight != 0
1173  ? static_cast<float>(m_intrinsicParams.arrayWidth) /
1175  : 1.0f;
1176  float halfFov = m_intrinsicParams.vFOV_rad / 2;
1177 
1178  CCVector3 upperLeftPoint;
1179  upperLeftPoint.z =
1182  upperLeftPoint.y = upperLeftPoint.z * tanf(halfFov);
1183  upperLeftPoint.x = upperLeftPoint.z * tanf(halfFov * ar);
1184 
1185  return upperLeftPoint;
1186 }
1187 
1189  if (m_intrinsicParams.arrayHeight == 0) {
1191  "[ccCameraSensor::computeFrustumCorners] Sensor height is 0!");
1192  return false;
1193  }
1194 
1195  float ar = static_cast<float>(m_intrinsicParams.arrayWidth) /
1197  float halfFov = m_intrinsicParams.vFOV_rad / 2;
1198 
1199  float xIn = std::abs(tan(halfFov * ar));
1200  float yIn = std::abs(tan(halfFov));
1201  const float& zNear = m_intrinsicParams.zNear_mm;
1202  const float& zFar = m_intrinsicParams.zFar_mm;
1203 
1204  // compute points of frustum in image coordinate system (warning: in the
1205  // system, z=-z)
1208  "[ccCameraSensor::computeFrustumCorners] Not enough memory!");
1209  return false;
1210  }
1211 
1212  PointCoordinateType orientation = -PC_ONE * m_scale / std::abs(m_scale);
1213 
1214  // DO NOT MODIFY THE ORDER OF THE CORNERS!! A LOT OF CODE DEPENDS OF THIS
1215  // ORDER!!
1216  m_frustumInfos.frustumCorners->addPoint(CCVector3(xIn, yIn, orientation) *
1217  zNear);
1218  m_frustumInfos.frustumCorners->addPoint(CCVector3(xIn, yIn, orientation) *
1219  zFar);
1220  m_frustumInfos.frustumCorners->addPoint(CCVector3(xIn, -yIn, orientation) *
1221  zNear);
1222  m_frustumInfos.frustumCorners->addPoint(CCVector3(xIn, -yIn, orientation) *
1223  zFar);
1224  m_frustumInfos.frustumCorners->addPoint(CCVector3(-xIn, -yIn, orientation) *
1225  zNear);
1226  m_frustumInfos.frustumCorners->addPoint(CCVector3(-xIn, -yIn, orientation) *
1227  zFar);
1228  m_frustumInfos.frustumCorners->addPoint(CCVector3(-xIn, yIn, orientation) *
1229  zNear);
1230  m_frustumInfos.frustumCorners->addPoint(CCVector3(-xIn, yIn, orientation) *
1231  zFar);
1232 
1233  // compute center of the circumscribed sphere
1236 
1237  float dz = P0->z - P5->z;
1238  float z = (std::abs(dz) < FLT_EPSILON
1239  ? P0->z
1240  : (P0->norm2() - P5->norm2()) / (2 * dz));
1241 
1242  m_frustumInfos.center = CCVector3(0, 0, z);
1243 
1244  // frustum corners are now computed
1245  m_frustumInfos.isComputed = true;
1246 
1247  return true;
1248 }
1249 
1251  float planeCoefficients[6][4],
1252  CCVector3 frustumCorners[8],
1253  CCVector3 edges[6],
1254  CCVector3& center) {
1256  if (!computeFrustumCorners()) return false;
1257 
1258  assert(m_frustumInfos.frustumCorners &&
1260 
1261  // compute frustum corners in the global coordinates system
1263  frustumCorners[0]);
1265  frustumCorners[1]);
1267  frustumCorners[2]);
1269  frustumCorners[3]);
1271  frustumCorners[4]);
1273  frustumCorners[5]);
1275  frustumCorners[6]);
1277  frustumCorners[7]);
1278 
1279  /*
1280  //-- METHOD 1 --//
1281  // See "Fast Extraction of Viewing Frustum Planes from the
1282  World-View-Projection Matrix" of Gil Gribb and Klaus Hartmann
1283  // Attention !! With this method, plane equations are not normalized ! You
1284  should add normalization if you need it :
1285  // It means that if you have your plane equation in the form (ax + by + cz +
1286  d = 0), then --> k = sqrt(a*a + b*b + c*c) and your new coefficients are -->
1287  a=a/k, b=b/k, c=c/k, d=d/k ccGLMatrix projectionMatrix; if
1288  (!getProjectionMatrix(projectionMatrix)) return false;
1289 
1290  ccGLMatrix mat = projectionMatrix * m_orientMatrix;
1291  float* coeffs = mat.data();
1292  // right
1293  planeCoefficients[0][0] = coeffs[3] - coeffs[0];
1294  planeCoefficients[0][1] = coeffs[7] - coeffs[4];
1295  planeCoefficients[0][2] = coeffs[11] - coeffs[8];
1296  planeCoefficients[0][3] = coeffs[15] - coeffs[12];
1297  // bottom
1298  planeCoefficients[1][0] = coeffs[3] + coeffs[1];
1299  planeCoefficients[1][1] = coeffs[7] + coeffs[5];
1300  planeCoefficients[1][2] = coeffs[11] + coeffs[9];
1301  planeCoefficients[1][3] = coeffs[15] + coeffs[12];
1302  // left
1303  planeCoefficients[2][0] = coeffs[3] + coeffs[0];
1304  planeCoefficients[2][1] = coeffs[7] + coeffs[4];
1305  planeCoefficients[2][2] = coeffs[11] + coeffs[8];
1306  planeCoefficients[2][3] = coeffs[15] + coeffs[12];
1307  // top
1308  planeCoefficients[3][0] = coeffs[3] - coeffs[1];
1309  planeCoefficients[3][1] = coeffs[7] - coeffs[5];
1310  planeCoefficients[3][2] = coeffs[11] - coeffs[9];
1311  planeCoefficients[3][3] = coeffs[15] - coeffs[12];
1312  // near
1313  planeCoefficients[4][0] = coeffs[3] + coeffs[2];
1314  planeCoefficients[4][1] = coeffs[7] + coeffs[6];
1315  planeCoefficients[4][2] = coeffs[11] + coeffs[10];
1316  planeCoefficients[4][3] = coeffs[15] + coeffs[14];
1317  // far
1318  planeCoefficients[5][0] = coeffs[3] - coeffs[2];
1319  planeCoefficients[5][1] = coeffs[7] - coeffs[6];
1320  planeCoefficients[5][2] = coeffs[11] - coeffs[10];
1321  planeCoefficients[5][3] = coeffs[15] - coeffs[14];
1322  // normalization --> temporary because it is quite long ; could be done
1323  before... for (int i=0 ; i<6 ; i++)
1324  {
1325  float a = planeCoefficients[i][0];
1326  float b = planeCoefficients[i][1];
1327  float c = planeCoefficients[i][2];
1328  float d = planeCoefficients[i][3];
1329  float k = sqrt(pow(a,2)+pow(b,2)+pow(c,2));
1330  planeCoefficients[i][0] = a/k;
1331  planeCoefficients[i][1] = b/k;
1332  planeCoefficients[i][2] = c/k;
1333  planeCoefficients[i][3] = d/k;
1334  }
1335  */
1336 
1337  //-- METHOD 2 --//
1338  // If you do not like method 1, use this standard method!
1339  // compute equations for side planes
1340  for (int i = 0; i < 4; i++) {
1341  CCVector3 v1 = frustumCorners[i * 2 + 1] - frustumCorners[i * 2];
1342  CCVector3 v2 =
1343  frustumCorners[((i + 1) * 2) % 8] - frustumCorners[i * 2];
1344  CCVector3 n = v1.cross(v2);
1345  n.normalize();
1346  planeCoefficients[i][0] = n.x;
1347  planeCoefficients[i][1] = n.y;
1348  planeCoefficients[i][2] = n.z;
1349  planeCoefficients[i][3] = -frustumCorners[i * 2].dot(n);
1350  }
1351  // compute equations for near and far planes
1352  {
1353  CCVector3 v1 = frustumCorners[0] - frustumCorners[6];
1354  CCVector3 v2 = frustumCorners[4] - frustumCorners[6];
1355  CCVector3 n = v1.cross(v2);
1356  n.normalize();
1357  planeCoefficients[4][0] = n.x;
1358  planeCoefficients[4][1] = n.y;
1359  planeCoefficients[4][2] = n.z;
1360  planeCoefficients[4][3] = -frustumCorners[6].dot(n);
1361  planeCoefficients[5][0] = -n.x;
1362  planeCoefficients[5][1] = -n.y;
1363  planeCoefficients[5][2] = -n.z;
1364  planeCoefficients[5][3] = -frustumCorners[7].dot(-n);
1365  }
1366 
1367  // compute frustum edges
1368  {
1369  edges[0] = frustumCorners[1] - frustumCorners[0];
1370  edges[1] = frustumCorners[3] - frustumCorners[2];
1371  edges[2] = frustumCorners[5] - frustumCorners[4];
1372  edges[3] = frustumCorners[7] - frustumCorners[6];
1373  edges[4] = frustumCorners[6] - frustumCorners[0];
1374  edges[5] = frustumCorners[2] - frustumCorners[0];
1375  for (unsigned i = 0; i < 6; i++) {
1376  edges[i].normalize();
1377  }
1378  }
1379 
1380  // compute frustum center in the global coordinates system
1382 
1383  return true;
1384 }
1385 
1387  CCVector3d upperLeftPointd =
1389 
1390  // near plane
1391  m_nearPlane.clear();
1392  m_nearPlane.points_.push_back(Eigen::Vector3d(
1393  upperLeftPointd.x, upperLeftPointd.y, -upperLeftPointd.z));
1394  m_nearPlane.points_.push_back(Eigen::Vector3d(
1395  -upperLeftPointd.x, upperLeftPointd.y, -upperLeftPointd.z));
1396  m_nearPlane.points_.push_back(Eigen::Vector3d(
1397  -upperLeftPointd.x, -upperLeftPointd.y, -upperLeftPointd.z));
1398  m_nearPlane.points_.push_back(Eigen::Vector3d(
1399  upperLeftPointd.x, -upperLeftPointd.y, -upperLeftPointd.z));
1400  m_nearPlane.lines_.push_back(Eigen::Vector2i(0, 1));
1401  m_nearPlane.lines_.push_back(Eigen::Vector2i(1, 2));
1402  m_nearPlane.lines_.push_back(Eigen::Vector2i(2, 3));
1403  m_nearPlane.lines_.push_back(Eigen::Vector2i(3, 0));
1404 
1405  // side lines
1406  m_sideLines.clear();
1407  m_sideLines.points_.push_back(Eigen::Vector3d(0.0, 0.0, 0.0));
1408  m_sideLines.points_.push_back(Eigen::Vector3d(
1409  upperLeftPointd.x, upperLeftPointd.y, -upperLeftPointd.z));
1410  m_sideLines.points_.push_back(Eigen::Vector3d(
1411  -upperLeftPointd.x, upperLeftPointd.y, -upperLeftPointd.z));
1412  m_sideLines.points_.push_back(Eigen::Vector3d(
1413  -upperLeftPointd.x, -upperLeftPointd.y, -upperLeftPointd.z));
1414  m_sideLines.points_.push_back(Eigen::Vector3d(
1415  upperLeftPointd.x, -upperLeftPointd.y, -upperLeftPointd.z));
1416  m_sideLines.lines_.push_back(Eigen::Vector2i(0, 1));
1417  m_sideLines.lines_.push_back(Eigen::Vector2i(0, 2));
1418  m_sideLines.lines_.push_back(Eigen::Vector2i(0, 3));
1419  m_sideLines.lines_.push_back(Eigen::Vector2i(0, 4));
1420 
1421  // up arrow
1422  const double arrowHeight = 3 * upperLeftPointd.y / 2;
1423  const double baseHeight = 6 * upperLeftPointd.y / 5;
1424  const double arrowHalfWidth = 2 * upperLeftPointd.x / 5;
1425  const double baseHalfWidth = 1 * upperLeftPointd.x / 5;
1426 
1427  // arrow
1428  m_arrow.clear();
1429  m_arrow.points_.push_back(
1430  Eigen::Vector3d(-baseHalfWidth, baseHeight, -upperLeftPointd.z));
1431  m_arrow.points_.push_back(
1432  Eigen::Vector3d(baseHalfWidth, baseHeight, -upperLeftPointd.z));
1433  m_arrow.points_.push_back(Eigen::Vector3d(baseHalfWidth, upperLeftPointd.y,
1434  -upperLeftPointd.z));
1435  m_arrow.points_.push_back(Eigen::Vector3d(-baseHalfWidth, upperLeftPointd.y,
1436  -upperLeftPointd.z));
1437  m_arrow.lines_.push_back(Eigen::Vector2i(0, 1));
1438  m_arrow.lines_.push_back(Eigen::Vector2i(1, 2));
1439  m_arrow.lines_.push_back(Eigen::Vector2i(2, 3));
1440  m_arrow.lines_.push_back(Eigen::Vector2i(3, 0));
1441  m_arrow.points_.push_back(
1442  Eigen::Vector3d(-arrowHalfWidth, baseHeight, -upperLeftPointd.z));
1443  m_arrow.points_.push_back(
1444  Eigen::Vector3d(arrowHalfWidth, baseHeight, -upperLeftPointd.z));
1445  m_arrow.points_.push_back(
1446  Eigen::Vector3d(0, arrowHeight, -upperLeftPointd.z));
1447  m_arrow.lines_.push_back(Eigen::Vector2i(4, 5));
1448  m_arrow.lines_.push_back(Eigen::Vector2i(5, 6));
1449  m_arrow.lines_.push_back(Eigen::Vector2i(6, 4));
1450 
1451  // axis (for test)
1452  {
1453  double l = std::abs(upperLeftPointd.z) / 2.0;
1454  m_axis.clear();
1455  m_axis.points_.push_back(Eigen::Vector3d(0.0, 0.0, 0.0));
1456  m_axis.points_.push_back(Eigen::Vector3d(l, 0.0, 0.0));
1457  m_axis.points_.push_back(Eigen::Vector3d(0.0, l, 0.0));
1458  m_axis.points_.push_back(Eigen::Vector3d(0.0, 0.0, -l));
1459 
1460  // right vector
1461  m_axis.lines_.push_back(Eigen::Vector2i(0, 1));
1463 
1464  // up vector
1465  m_axis.lines_.push_back(Eigen::Vector2i(0, 2));
1467 
1468  // view vector
1469  m_axis.lines_.push_back(Eigen::Vector2i(0, 3));
1471  }
1472 }
1473 
1475  if (!MACRO_Draw3D(context)) return;
1476 
1477  // we draw a little 3d representation of the sensor and some of its
1478  // attributes
1480  return;
1481  }
1482 
1483  // build-up the normal representation own 'context'
1484  CC_DRAW_CONTEXT cameraContext = context;
1485  // we must remove the 'push name flag' so that the primitives don't push
1486  // their own!
1487  cameraContext.drawingFlags &= (~CC_ENTITY_PICKING);
1488  cameraContext.currentLineWidth = 1;
1489  cameraContext.defaultPolylineColor = getFrameColor();
1490  cameraContext.defaultMeshColor = getPlaneColor();
1491  cameraContext.opacity = 0.2f;
1492  ccIndexedTransformation sensorPos;
1493  if (!getAbsoluteTransformation(sensorPos, getActiveIndex())) {
1494  // no visible position for this index!
1495  return;
1496  }
1497 
1498  // update drawing data
1499  updateData();
1500 
1501  // frustum
1503  // always compute corners
1505 
1508  // frustum area (lines)
1510  const CCVector3* P0 =
1512  const CCVector3* P1 =
1514  const CCVector3* P2 =
1516  const CCVector3* P3 =
1518  const CCVector3* P4 =
1520  const CCVector3* P5 =
1522  const CCVector3* P6 =
1524  const CCVector3* P7 =
1526 
1527  cameraContext.currentLineWidth = 2;
1528 
1529  m_sideLines.points_.push_back(CCVector3d::fromArray(*P0)); // 5
1530  m_sideLines.points_.push_back(CCVector3d::fromArray(*P1)); // 6
1531  m_sideLines.points_.push_back(CCVector3d::fromArray(*P2)); // 7
1532  m_sideLines.points_.push_back(CCVector3d::fromArray(*P3)); // 8
1533  m_sideLines.points_.push_back(CCVector3d::fromArray(*P4)); // 9
1534  m_sideLines.points_.push_back(
1535  CCVector3d::fromArray(*P5)); // 10
1536  m_sideLines.points_.push_back(
1537  CCVector3d::fromArray(*P6)); // 11
1538  m_sideLines.points_.push_back(
1539  CCVector3d::fromArray(*P7)); // 12
1540 
1541  m_sideLines.lines_.push_back(Eigen::Vector2i(5, 6));
1542  m_sideLines.lines_.push_back(Eigen::Vector2i(6, 8));
1543  m_sideLines.lines_.push_back(Eigen::Vector2i(8, 7));
1544  m_sideLines.lines_.push_back(Eigen::Vector2i(7, 5));
1545 
1546  m_sideLines.lines_.push_back(Eigen::Vector2i(7, 8));
1547  m_sideLines.lines_.push_back(Eigen::Vector2i(8, 10));
1548  m_sideLines.lines_.push_back(Eigen::Vector2i(10, 9));
1549  m_sideLines.lines_.push_back(Eigen::Vector2i(9, 7));
1550 
1551  m_sideLines.lines_.push_back(Eigen::Vector2i(9, 10));
1552  m_sideLines.lines_.push_back(Eigen::Vector2i(10, 12));
1553  m_sideLines.lines_.push_back(Eigen::Vector2i(12, 11));
1554  m_sideLines.lines_.push_back(Eigen::Vector2i(11, 9));
1555 
1556  m_sideLines.lines_.push_back(Eigen::Vector2i(11, 12));
1557  m_sideLines.lines_.push_back(Eigen::Vector2i(12, 6));
1558  m_sideLines.lines_.push_back(Eigen::Vector2i(6, 5));
1559  m_sideLines.lines_.push_back(Eigen::Vector2i(5, 11));
1560 
1561  m_sideLines.lines_.push_back(Eigen::Vector2i(11, 5));
1562  m_sideLines.lines_.push_back(Eigen::Vector2i(5, 7));
1563  m_sideLines.lines_.push_back(Eigen::Vector2i(7, 9));
1564  m_sideLines.lines_.push_back(Eigen::Vector2i(9, 11));
1565 
1566  m_sideLines.lines_.push_back(Eigen::Vector2i(6, 12));
1567  m_sideLines.lines_.push_back(Eigen::Vector2i(12, 10));
1568  m_sideLines.lines_.push_back(Eigen::Vector2i(10, 8));
1569  m_sideLines.lines_.push_back(Eigen::Vector2i(8, 6));
1570  }
1571 
1572  // frustum area (planes)
1574  if (!m_frustumInfos.frustumHull &&
1576  if (m_frustumInfos.isComputed) {
1578  sensorPos);
1579  }
1580 
1581  // set the rigth display (just to be sure)
1586  cameraContext.opacity);
1587  cameraContext.defaultMeshColor = m_color;
1588  cameraContext.meshRenderingMode =
1590  cameraContext.viewID =
1592  ecvDisplayTools::Draw(cameraContext,
1594  }
1595  }
1596  }
1597  }
1598 
1599  // frustum area (planes)
1601  cameraContext.visible =
1603  cameraContext.viewID = m_frustumInfos.frustumHull->getViewId();
1604  ecvDisplayTools::HideShowEntities(cameraContext);
1605  }
1606 
1607  // tranformation
1608  {
1609  Eigen::Matrix4d transformation = ccGLMatrixd::ToEigenMatrix4(sensorPos);
1610  m_nearPlane.Transform(transformation);
1612  m_sideLines.Transform(transformation);
1614  m_arrow.Transform(transformation);
1616  m_axis.Transform(transformation);
1617  }
1618 
1619  cameraContext.visible = context.visible;
1620  cameraContext.viewID = context.viewID;
1621  cameraContext.defaultMeshColor = getPlaneColor();
1622  ecvDisplayTools::Draw(cameraContext, this);
1623 }
1624 
1628  context.removeEntityType = ENTITY_TYPE::ECV_MESH;
1629  context.removeViewID = m_frustumInfos.frustumHull->getViewId();
1631  }
1633 }
1634 
1636  context.viewID = this->getViewId();
1638 
1643  }
1644 }
1645 
1647  float ccdPixelSize_mm) {
1648  if (ccdPixelSize_mm < FLT_EPSILON) {
1650  "[ccCameraSensor::convertFocalPixToMM] Invalid CCD pixel size! "
1651  "(<= 0)");
1652  return -1.0f;
1653  }
1654 
1655  return focal_pix * ccdPixelSize_mm;
1656 }
1657 
1659  float ccdPixelSize_mm) {
1660  if (ccdPixelSize_mm < FLT_EPSILON) {
1662  "[ccCameraSensor::convertFocalMMToPix] Invalid CCD pixel size! "
1663  "(<= 0)");
1664  return -1.0f;
1665  }
1666 
1667  return focal_mm / ccdPixelSize_mm;
1668 }
1669 
1671  int imageSize_pix) {
1672  if (imageSize_pix <= 0) {
1673  // invalid image size
1674  return -1.0f;
1675  }
1676 
1677  return 2 * atanf(imageSize_pix / (2 * focal_pix));
1678 }
1679 
1681  float ccdSize_mm) {
1682  if (ccdSize_mm < FLT_EPSILON) {
1683  // invalid CDD size
1684  return -1.0f;
1685  }
1686 
1687  return 2 * atanf(ccdSize_mm / (2 * focal_mm));
1688 }
1689 
1691  const ccImage* image,
1692  cloudViewer::GenericIndexedCloud* keypoints3D,
1693  std::vector<KeyPoint>& keypointsImage,
1694  double a_out[3],
1695  double b_out[3],
1696  double c_out[3]) const {
1697  if (!image || !keypoints3D) return false;
1698 
1699  unsigned count = static_cast<unsigned>(keypointsImage.size());
1700  if (count < 4) return false;
1701 
1702  // first guess for X (a0 a1 a2 b0 b1 b2 c1 c2)
1703  double norm = static_cast<double>(std::max(image->getW(), image->getH()));
1704  double X0[8] = {1.0 / sqrt(norm), 1.0 / norm, 1.0 / norm, 1.0 / sqrt(norm),
1705  1.0 / norm, 1.0 / norm, 1.0 / norm, 1.0 / norm};
1706 
1707  // compute the A matrix and b vector
1708  unsigned Neq = 2 * count; // number of equations
1709  double* A = new double[8 * Neq]; // 8 coefficients: a0 a1 a2 b0 b1 b2 c1 c2
1710  double* b = new double[Neq];
1711  if (!A || !b) {
1712  // not enough memory
1713  if (A) delete[] A;
1714  if (b) delete[] b;
1715  return false;
1716  }
1717 
1718  // for all points
1719  {
1720  double* _A = A;
1721  double* _b = b;
1722  for (unsigned i = 0; i < count; ++i) {
1723  const KeyPoint& kp = keypointsImage[i];
1724  double kpx = static_cast<double>(kp.x);
1725  double kpy = static_cast<double>(kp.y);
1726  const CCVector3* P = keypoints3D->getPoint(kp.index);
1727 
1728  *_A++ = 1.0;
1729  *_A++ = kpx;
1730  *_A++ = kpy;
1731  *_A++ = 0.0;
1732  *_A++ = 0.0;
1733  *_A++ = 0.0;
1734  *_A++ = -kpx * static_cast<double>(P->x);
1735  *_A++ = -kpy * static_cast<double>(P->x);
1736  *_b++ = static_cast<double>(P->x);
1737 
1738  *_A++ = 0.0;
1739  *_A++ = 0.0;
1740  *_A++ = 0.0;
1741  *_A++ = 1.0;
1742  *_A++ = kpx;
1743  *_A++ = kpy;
1744  *_A++ = -kpx * static_cast<double>(P->y);
1745  *_A++ = -kpy * static_cast<double>(P->y);
1746  *_b++ = static_cast<double>(P->y);
1747  }
1748  }
1749 
1750  // conjugate gradient initialization
1751  // we solve tA.A.X = tA.b
1753  cloudViewer::SquareMatrixd& tAA = cg.A();
1754  double* tAb = cg.b();
1755 
1756  // compute tA.A and tA.b
1757  {
1758  for (unsigned i = 0; i < 8; ++i) {
1759  // tA.A part
1760  for (unsigned j = i; j < 8; ++j) {
1761  double sum_prod = 0;
1762  const double* _Ai = A + i;
1763  const double* _Aj = A + j;
1764  for (unsigned k = 0; k < Neq; ++k) {
1765  // sum_prod += A[(8*2*k)+i]*A[(8*2*k)+j];
1766  sum_prod += (*_Ai) * (*_Aj);
1767  _Ai += 8;
1768  _Aj += 8;
1769  }
1770  tAA.m_values[j][i] = tAA.m_values[i][j] = sum_prod;
1771  }
1772 
1773  // tA.b part
1774  {
1775  double sum_prod = 0;
1776  const double* _Ai = A + i;
1777  const double* _b = b;
1778  for (unsigned k = 0; k < Neq; ++k) {
1779  // sum_prod += A[(8*2*k)+i]*b[k];
1780  sum_prod += (*_Ai) * (*_b++);
1781  _Ai += 8;
1782  }
1783  tAb[i] = sum_prod;
1784  }
1785  }
1786  }
1787 
1788  // init. conjugate gradient
1789  cg.initConjugateGradient(X0);
1790 
1791  // conjugate gradient iterations
1792  {
1793  double convergenceThreshold =
1794  1.0e-8 /* * norm*/; // max. error for convergence
1795  for (unsigned i = 0; i < 1500; ++i) {
1796  double lastError = cg.iterConjugateGradient(X0);
1797  if (lastError < convergenceThreshold) // converged
1798  {
1800  QString("[computeOrthoRectificationParams] Convergence "
1801  "reached in %1 iteration(s) (error: %2)")
1802  .arg(i + 1)
1803  .arg(lastError));
1804  break;
1805  }
1806  }
1807  }
1808 
1809  delete[] A;
1810  A = nullptr;
1811  delete[] b;
1812  b = nullptr;
1813 
1814  a_out[0] = X0[0];
1815  a_out[1] = X0[1];
1816  a_out[2] = X0[2];
1817  b_out[0] = X0[3];
1818  b_out[1] = X0[4];
1819  b_out[2] = X0[5];
1820  c_out[0] = 1.0;
1821  c_out[1] = X0[6];
1822  c_out[2] = X0[7];
1823 
1824  return true;
1825 }
1826 
1828  const ccImage* image,
1830  double& pixelSize,
1831  bool undistortImages /*=true*/,
1832  double* minCorner /*=0*/,
1833  double* maxCorner /*=0*/,
1834  double* realCorners /*=0*/) const {
1835  // first, we compute the ortho-rectified image corners
1836  double corners[8];
1837 
1838  int width = static_cast<int>(image->getW());
1839  int height = static_cast<int>(image->getH());
1840 
1841  // top-left
1842  {
1843  CCVector2 xTopLeft(0, 0);
1844  CCVector3 P3D;
1845  if (!fromImageCoordToGlobalCoord(xTopLeft, P3D, Z0)) return nullptr;
1846 #ifdef QT_DEBUG
1847  // internal check
1848  CCVector2 check(0, 0);
1849  fromGlobalCoordToImageCoord(P3D, check, false);
1850  assert((xTopLeft - check).norm2() <
1851  std::max(width, height) * FLT_EPSILON);
1852 #endif
1853  corners[0] = P3D.x;
1854  corners[1] = P3D.y;
1855  }
1856 
1857  // top-right
1858  {
1859  CCVector2 xTopRight(static_cast<PointCoordinateType>(width), 0);
1860  CCVector3 P3D;
1861  if (!fromImageCoordToGlobalCoord(xTopRight, P3D, Z0)) return nullptr;
1862 #ifdef QT_DEBUG
1863  // internal check
1864  CCVector2 check(0, 0);
1865  fromGlobalCoordToImageCoord(P3D, check, false);
1866  assert((xTopRight - check).norm2() <
1867  std::max(width, height) * FLT_EPSILON);
1868 #endif
1869  corners[2] = P3D.x;
1870  corners[3] = P3D.y;
1871  }
1872 
1873  // bottom-right
1874  {
1875  CCVector2 xBottomRight(static_cast<PointCoordinateType>(width),
1876  static_cast<PointCoordinateType>(height));
1877  CCVector3 P3D;
1878  if (!fromImageCoordToGlobalCoord(xBottomRight, P3D, Z0)) return nullptr;
1879 #ifdef QT_DEBUG
1880  // internal check
1881  CCVector2 check(0, 0);
1882  fromGlobalCoordToImageCoord(P3D, check, false);
1883  assert((xBottomRight - check).norm2() <
1884  std::max(width, height) * FLT_EPSILON);
1885 #endif
1886  corners[4] = P3D.x;
1887  corners[5] = P3D.y;
1888  }
1889 
1890  // bottom-left
1891  {
1892  CCVector2 xBottomLeft(0, static_cast<PointCoordinateType>(height));
1893  CCVector3 P3D;
1894  if (!fromImageCoordToGlobalCoord(xBottomLeft, P3D, Z0)) return nullptr;
1895 #ifdef QT_DEBUG
1896  // internal check
1897  CCVector2 check(0, 0);
1898  fromGlobalCoordToImageCoord(P3D, check, false);
1899  assert((xBottomLeft - check).norm2() <
1900  std::max(width, height) * FLT_EPSILON);
1901 #endif
1902  corners[6] = P3D.x;
1903  corners[7] = P3D.y;
1904  }
1905 
1906  if (realCorners) memcpy(realCorners, corners, 8 * sizeof(double));
1907 
1908  // we look for min and max bounding box
1909  double minC[2] = {corners[0], corners[1]};
1910  double maxC[2] = {corners[0], corners[1]};
1911 
1912  for (unsigned k = 1; k < 4; ++k) {
1913  const double* C = corners + 2 * k;
1914  if (minC[0] > C[0])
1915  minC[0] = C[0];
1916  else if (maxC[0] < C[0])
1917  maxC[0] = C[0];
1918 
1919  if (minC[1] > C[1])
1920  minC[1] = C[1];
1921  else if (maxC[1] < C[1])
1922  maxC[1] = C[1];
1923  }
1924 
1925  // output 3D boundaries (optional)
1926  if (minCorner) {
1927  minCorner[0] = minC[0];
1928  minCorner[1] = minC[1];
1929  }
1930  if (maxCorner) {
1931  maxCorner[0] = maxC[0];
1932  maxCorner[1] = maxC[1];
1933  }
1934 
1935  double dx = maxC[0] - minC[0];
1936  double dy = maxC[1] - minC[1];
1937 
1938  double _pixelSize = pixelSize;
1939  if (_pixelSize <= 0) {
1940  int maxSize = std::max(width, height);
1941  _pixelSize = std::max(dx, dy) / maxSize;
1942  }
1943  unsigned w = static_cast<unsigned>(dx / _pixelSize);
1944  unsigned h = static_cast<unsigned>(dy / _pixelSize);
1945 
1946  QImage orthoImage(w, h, QImage::Format_ARGB32);
1947  if (orthoImage.isNull()) // not enough memory!
1948  return nullptr;
1949 
1950  const QRgb blackValue = qRgb(0, 0, 0);
1951  const QRgb blackAlphaZero = qRgba(0, 0, 0, 0);
1952 
1953  for (unsigned i = 0; i < w; ++i) {
1954  PointCoordinateType xip =
1955  static_cast<PointCoordinateType>(minC[0] + i * _pixelSize);
1956  for (unsigned j = 0; j < h; ++j) {
1957  PointCoordinateType yip =
1958  static_cast<PointCoordinateType>(minC[1] + j * _pixelSize);
1959 
1960  QRgb rgb = blackValue; // output pixel is (transparent) black by
1961  // default
1962 
1963  CCVector3 P3D(xip, yip, Z0);
1964  CCVector2 imageCoord;
1965  if (fromGlobalCoordToImageCoord(P3D, imageCoord, undistortImages)) {
1966  int x = static_cast<int>(imageCoord.x);
1967  int y = static_cast<int>(imageCoord.y);
1968  if (x >= 0 && x < width && y >= 0 && y < height) {
1969  rgb = image->data().pixel(x, y);
1970  }
1971  }
1972 
1973  // pure black pixels are treated as transparent ones!
1974  orthoImage.setPixel(i, h - 1 - j,
1975  rgb != blackValue ? rgb : blackAlphaZero);
1976  }
1977  }
1978 
1979  // output pixel size (auto)
1980  pixelSize = _pixelSize;
1981 
1982  return new ccImage(orthoImage, getName());
1983 }
1984 
1986  const ccImage* image,
1987  cloudViewer::GenericIndexedCloud* keypoints3D,
1988  std::vector<KeyPoint>& keypointsImage,
1989  double& pixelSize,
1990  double* minCorner /*=0*/,
1991  double* maxCorner /*=0*/,
1992  double* realCorners /*=0*/) const {
1993  double a[3], b[3], c[3];
1994 
1995  if (!computeOrthoRectificationParams(image, keypoints3D, keypointsImage, a,
1996  b, c)) {
1997  return nullptr;
1998  }
1999 
2000  const double& a0 = a[0];
2001  const double& a1 = a[1];
2002  const double& a2 = a[2];
2003  const double& b0 = b[0];
2004  const double& b1 = b[1];
2005  const double& b2 = b[2];
2006  // const double& c0 = c[0];
2007  const double& c1 = c[1];
2008  const double& c2 = c[2];
2009 
2010  // first, we compute the ortho-rectified image corners
2011  double corners[8];
2012  double xi, yi, qi;
2013 
2014  int width = static_cast<int>(image->getW());
2015  int height = static_cast<int>(image->getH());
2016  double halfWidth = width / 2.0;
2017  double halfHeight = height / 2.0;
2018 
2019  // top-left
2020  xi = -halfWidth;
2021  yi = -halfHeight;
2022  qi = 1.0 + c1 * xi + c2 * yi;
2023  corners[0] = (a0 + a1 * xi + a2 * yi) / qi;
2024  corners[1] = (b0 + b1 * xi + b2 * yi) / qi;
2025 
2026  // top-right
2027  xi = halfWidth;
2028  yi = -halfHeight;
2029  qi = 1.0 + c1 * xi + c2 * yi;
2030  corners[2] = (a0 + a1 * xi + a2 * yi) / qi;
2031  corners[3] = (b0 + b1 * xi + b2 * yi) / qi;
2032 
2033  // bottom-right
2034  xi = halfWidth;
2035  yi = halfHeight;
2036  qi = 1.0 + c1 * xi + c2 * yi;
2037  corners[4] = (a0 + a1 * xi + a2 * yi) / qi;
2038  corners[5] = (b0 + b1 * xi + b2 * yi) / qi;
2039 
2040  // bottom-left
2041  xi = -halfWidth;
2042  yi = halfHeight;
2043  qi = 1.0 + c1 * xi + c2 * yi;
2044  corners[6] = (a0 + a1 * xi + a2 * yi) / qi;
2045  corners[7] = (b0 + b1 * xi + b2 * yi) / qi;
2046 
2047  if (realCorners) {
2048  memcpy(realCorners, corners, 8 * sizeof(double));
2049  }
2050 
2051  // we look for min and max bounding box
2052  double minC[2] = {corners[0], corners[1]};
2053  double maxC[2] = {corners[0], corners[1]};
2054 
2055  for (unsigned k = 1; k < 4; ++k) {
2056  const double* C = corners + 2 * k;
2057  if (minC[0] > C[0])
2058  minC[0] = C[0];
2059  else if (maxC[0] < C[0])
2060  maxC[0] = C[0];
2061 
2062  if (minC[1] > C[1])
2063  minC[1] = C[1];
2064  else if (maxC[1] < C[1])
2065  maxC[1] = C[1];
2066  }
2067 
2068  // output 3D boundaries (optional)
2069  if (minCorner) {
2070  minCorner[0] = minC[0];
2071  minCorner[1] = minC[1];
2072  }
2073  if (maxCorner) {
2074  maxCorner[0] = maxC[0];
2075  maxCorner[1] = maxC[1];
2076  }
2077 
2078  double dx = maxC[0] - minC[0];
2079  double dy = maxC[1] - minC[1];
2080 
2081  double _pixelSize = pixelSize;
2082  if (_pixelSize <= 0) {
2083  int maxSize = std::max(width, height);
2084  _pixelSize = std::max(dx, dy) / maxSize;
2085  }
2086  unsigned w = static_cast<unsigned>(dx / _pixelSize);
2087  unsigned h = static_cast<unsigned>(dy / _pixelSize);
2088 
2089  QImage orthoImage(w, h, QImage::Format_ARGB32);
2090  if (orthoImage.isNull()) // not enough memory!
2091  return nullptr;
2092 
2093  const QRgb blackValue = qRgb(0, 0, 0);
2094  const QRgb blackAlphaZero = qRgba(0, 0, 0, 0);
2095 
2096  for (unsigned i = 0; i < w; ++i) {
2097  double xip = minC[0] + static_cast<double>(i) * _pixelSize;
2098  for (unsigned j = 0; j < h; ++j) {
2099  QRgb rgb = blackValue; // output pixel is (transparent) black by
2100  // default
2101 
2102  double yip = minC[1] + static_cast<double>(j) * _pixelSize;
2103  double q = (c2 * xip - a2) * (c1 * yip - b1) -
2104  (c2 * yip - b2) * (c1 * xip - a1);
2105  double p =
2106  (a0 - xip) * (c1 * yip - b1) - (b0 - yip) * (c1 * xip - a1);
2107  double yi = p / q;
2108  yi += halfHeight;
2109  int y = static_cast<int>(yi);
2110 
2111  if (y >= 0 && y < height) {
2112  q = (c1 * xip - a1) * (c2 * yip - b2) -
2113  (c1 * yip - b1) * (c2 * xip - a2);
2114  p = (a0 - xip) * (c2 * yip - b2) - (b0 - yip) * (c2 * xip - a2);
2115  double xi = p / q;
2116  xi += halfWidth;
2117  int x = static_cast<int>(xi);
2118 
2119  if (x >= 0 && x < width) {
2120  rgb = image->data().pixel(x, y);
2121  }
2122  }
2123 
2124  // pure black pixels are treated as transparent ones!
2125  orthoImage.setPixel(i, h - 1 - j,
2126  rgb != blackValue ? rgb : blackAlphaZero);
2127  }
2128  }
2129 
2130  // output pixel size (auto)
2131  pixelSize = _pixelSize;
2132 
2133  return new ccImage(orthoImage, getName());
2134 }
2135 
2137  std::vector<ccImage*> images,
2138  double a[],
2139  double b[],
2140  double c[],
2141  unsigned maxSize,
2142  QDir* outputDir /*=0*/,
2143  std::vector<ccImage*>* result /*=0*/,
2144  std::vector<std::pair<double, double>>* relativePos /*=0*/) {
2145  size_t count = images.size();
2146  if (count == 0) {
2147  CVLog::Warning("[OrthoRectifyAsImages] No image to process?!");
2148  return false;
2149  }
2150 
2151  // min & max corners for each images
2152  std::vector<double> minCorners;
2153  std::vector<double> maxCorners;
2154  try {
2155  minCorners.resize(2 * count);
2156  maxCorners.resize(2 * count);
2157  } catch (const std::bad_alloc&) {
2158  // not enough memory
2159  CVLog::Warning("[OrthoRectifyAsImages] Not enough memory!");
2160  return false;
2161  }
2162  // max dimension of all (ortho-rectified) images, horizontally or vertically
2163  double maxDimAllImages = 0;
2164  // corners for the global set
2165  double globalCorners[4] = {0, 0, 0, 0};
2166 
2167  // compute output corners and max dimension for all images
2168  for (size_t k = 0; k < count; ++k) {
2169  const double& a0 = a[k * 3];
2170  const double& a1 = a[k * 3 + 1];
2171  const double& a2 = a[k * 3 + 2];
2172  const double& b0 = b[k * 3];
2173  const double& b1 = b[k * 3 + 1];
2174  const double& b2 = b[k * 3 + 2];
2175  // const double& c0 = c[k*3];
2176  const double& c1 = c[k * 3 + 1];
2177  const double& c2 = c[k * 3 + 2];
2178 
2179  // first, we compute the ortho-rectified image corners
2180  double corners[8];
2181  double xi, yi, qi;
2182 
2183  unsigned width = images[k]->getW();
2184  unsigned height = images[k]->getH();
2185 
2186  // top-left
2187  xi = -0.5 * width;
2188  yi = -0.5 * height;
2189  qi = 1.0 + c1 * xi + c2 * yi;
2190  corners[0] = (a0 + a1 * xi + a2 * yi) / qi;
2191  corners[1] = (b0 + b1 * xi + b2 * yi) / qi;
2192 
2193  // top-right
2194  xi = 0.5 * width;
2195  // yi = -0.5*height;
2196  qi = 1.0 + c1 * xi + c2 * yi;
2197  corners[2] = (a0 + a1 * xi + a2 * yi) / qi;
2198  corners[3] = (b0 + b1 * xi + b2 * yi) / qi;
2199 
2200  // bottom-right
2201  // xi = 0.5*width;
2202  yi = 0.5 * height;
2203  qi = 1.0 + c1 * xi + c2 * yi;
2204  corners[4] = (a0 + a1 * xi + a2 * yi) / qi;
2205  corners[5] = (b0 + b1 * xi + b2 * yi) / qi;
2206 
2207  // bottom-left
2208  xi = -0.5 * width;
2209  // yi = 0.5*height;
2210  qi = 1.0 + c1 * xi + c2 * yi;
2211  corners[6] = (a0 + a1 * xi + a2 * yi) / qi;
2212  corners[7] = (b0 + b1 * xi + b2 * yi) / qi;
2213 
2214  // we look for min and max bounding box
2215  double* minC = &minCorners[2 * k];
2216  double* maxC = &maxCorners[2 * k];
2217  maxC[0] = minC[0] = corners[0];
2218  maxC[1] = minC[1] = corners[1];
2219  for (unsigned k = 1; k < 4; ++k) {
2220  const double* C = corners + 2 * k;
2221  // dimension: X
2222  if (minC[0] > C[0])
2223  minC[0] = C[0];
2224  else if (maxC[0] < C[0])
2225  maxC[0] = C[0];
2226 
2227  if (globalCorners[0] > minC[0]) globalCorners[0] = minC[0];
2228  if (globalCorners[2] < maxC[0]) globalCorners[2] = maxC[0];
2229 
2230  // dimension: Y
2231  if (minC[1] > C[1])
2232  minC[1] = C[1];
2233  else if (maxC[1] < C[1])
2234  maxC[1] = C[1];
2235 
2236  if (globalCorners[1] > minC[1]) globalCorners[1] = minC[1];
2237  if (globalCorners[3] < maxC[1]) globalCorners[3] = maxC[1];
2238  }
2239 
2240  double dx = maxC[0] - minC[0];
2241  double dy = maxC[1] - minC[1];
2242  double maxd = std::max(dx, dy);
2243  if (maxd > maxDimAllImages) maxDimAllImages = maxd;
2244  }
2245 
2246  // deduce pixel size
2247  double pixelSize = maxDimAllImages / maxSize;
2248 
2249  if (outputDir) {
2250  // write header
2251  QFile f(outputDir->absoluteFilePath("ortho_rectification_log.txt"));
2252  if (f.open(QIODevice::WriteOnly | QIODevice::Text)) {
2253  QTextStream stream(&f);
2254  stream.setRealNumberNotation(QTextStream::FixedNotation);
2255  stream.setRealNumberPrecision(6);
2256  stream << "PixelSize" << ' ' << pixelSize << QtCompat::endl;
2257  stream << "Global3DBBox" << ' ' << globalCorners[0] << ' '
2258  << globalCorners[1] << ' ' << globalCorners[2] << ' '
2259  << globalCorners[3] << QtCompat::endl;
2260  int globalWidth = static_cast<int>(
2261  ceil((globalCorners[2] - globalCorners[0]) / pixelSize));
2262  int globalHeight = static_cast<int>(
2263  ceil((globalCorners[3] - globalCorners[1]) / pixelSize));
2264  stream << "Global2DBBox" << ' ' << 0 << ' ' << 0 << ' '
2265  << globalWidth - 1 << ' ' << globalHeight - 1
2266  << QtCompat::endl;
2267  }
2268  }
2269 
2270  // projet each image accordingly
2271  for (size_t k = 0; k < count; ++k) {
2272  double* minC = &minCorners[2 * k];
2273  double* maxC = &maxCorners[2 * k];
2274  double dx = maxC[0] - minC[0];
2275  double dy = maxC[1] - minC[1];
2276 
2277  ccImage* image = images[k];
2278  unsigned width = images[k]->getW();
2279  unsigned height = images[k]->getH();
2280  unsigned w = static_cast<unsigned>(ceil(dx / pixelSize));
2281  unsigned h = static_cast<unsigned>(ceil(dy / pixelSize));
2282 
2283  QImage orthoImage(w, h, QImage::Format_ARGB32);
2284  if (orthoImage.isNull()) // not enough memory!
2285  {
2286  // clear mem.
2287  if (result) {
2288  while (!result->empty()) {
2289  delete result->back();
2290  result->pop_back();
2291  }
2292  }
2293  CVLog::Warning("[OrthoRectifyAsImages] Not enough memory!");
2294  return false;
2295  }
2296 
2297  // ortho rectification parameters
2298  const double& a0 = a[k * 3];
2299  const double& a1 = a[k * 3 + 1];
2300  const double& a2 = a[k * 3 + 2];
2301  const double& b0 = b[k * 3];
2302  const double& b1 = b[k * 3 + 1];
2303  const double& b2 = b[k * 3 + 2];
2304  // const double& c0 = c[k*3];
2305  const double& c1 = c[k * 3 + 1];
2306  const double& c2 = c[k * 3 + 2];
2307 
2308  for (unsigned i = 0; i < w; ++i) {
2309  double xip = minC[0] + static_cast<double>(i) * pixelSize;
2310  for (unsigned j = 0; j < h; ++j) {
2311  double yip = minC[1] + static_cast<double>(j) * pixelSize;
2312  double q = (c2 * xip - a2) * (c1 * yip - b1) -
2313  (c2 * yip - b2) * (c1 * xip - a1);
2314  double p = (a0 - xip) * (c1 * yip - b1) -
2315  (b0 - yip) * (c1 * xip - a1);
2316  double yi = p / q;
2317 
2318  q = (c1 * xip - a1) * (c2 * yip - b2) -
2319  (c1 * yip - b1) * (c2 * xip - a2);
2320  p = (a0 - xip) * (c2 * yip - b2) - (b0 - yip) * (c2 * xip - a2);
2321  double xi = p / q;
2322 
2323  xi += 0.5 * width;
2324  yi += 0.5 * height;
2325 
2326  int x = static_cast<int>(xi);
2327  int y = static_cast<int>(yi);
2328  if (x >= 0 && x < static_cast<int>(width) && y >= 0 &&
2329  y < static_cast<int>(height)) {
2330  QRgb rgb = image->data().pixel(x, y);
2331  // pure black pixels are treated as transparent ones!
2332  if (qRed(rgb) + qGreen(rgb) + qBlue(rgb) > 0)
2333  orthoImage.setPixel(i, h - 1 - j, rgb);
2334  else
2335  orthoImage.setPixel(
2336  i, h - 1 - j,
2337  qRgba(qRed(rgb), qGreen(rgb), qBlue(rgb), 0));
2338  } else
2339  orthoImage.setPixel(i, h - 1 - j, qRgba(255, 0, 255, 0));
2340  }
2341  }
2342 
2343  // eventually compute relative pos
2344  if (relativePos) {
2345  double xShift = (minC[0] - minCorners[0]) / pixelSize;
2346  double yShift = (minC[1] - minCorners[1]) / pixelSize;
2347  relativePos->emplace_back(xShift, yShift);
2348  }
2349 
2350  if (outputDir) {
2351  // export image
2352  QString exportFilename =
2353  QString("ortho_rectified_%1.png").arg(image->getName());
2354  orthoImage.save(outputDir->absoluteFilePath(exportFilename));
2355 
2356  // export meta-data
2357  QFile f(outputDir->absoluteFilePath("ortho_rectification_log.txt"));
2358  if (f.open(QIODevice::WriteOnly | QIODevice::Append |
2359  QIODevice::Text)) // always append
2360  {
2361  double xShiftGlobal = (minC[0] - globalCorners[0]) / pixelSize;
2362  double yShiftGlobal = (minC[1] - globalCorners[1]) / pixelSize;
2363  QTextStream stream(&f);
2364  stream.setRealNumberNotation(QTextStream::FixedNotation);
2365  stream.setRealNumberPrecision(6);
2366  stream << "Image" << ' ' << exportFilename << ' ';
2367  stream << "Local3DBBox" << ' ' << minC[0] << ' ' << minC[1]
2368  << ' ' << maxC[0] << ' ' << maxC[1] << ' ';
2369  stream << "Local2DBBox" << ' ' << xShiftGlobal << ' '
2370  << yShiftGlobal << ' ' << xShiftGlobal + (double)(w - 1)
2371  << ' ' << yShiftGlobal + (double)(h - 1)
2372  << QtCompat::endl;
2373  f.close();
2374  }
2375  }
2376 
2377  if (result)
2378  result->push_back(new ccImage(orthoImage, image->getName()));
2379  }
2380 
2381  return true;
2382 }
2383 
2385  const ccImage* image,
2386  cloudViewer::GenericIndexedCloud* keypoints3D,
2387  std::vector<KeyPoint>& keypointsImage) const {
2388  double a[3], b[3], c[3];
2389 
2390  if (!computeOrthoRectificationParams(image, keypoints3D, keypointsImage, a,
2391  b, c))
2392  return nullptr;
2393 
2394  const double& a0 = a[0];
2395  const double& a1 = a[1];
2396  const double& a2 = a[2];
2397  const double& b0 = b[0];
2398  const double& b1 = b[1];
2399  const double& b2 = b[2];
2400  // const double& c0 = c[0];
2401  const double& c1 = c[1];
2402  const double& c2 = c[2];
2403 
2404  PointCoordinateType defaultZ = 0;
2405 
2406  unsigned width = image->getW();
2407  unsigned height = image->getH();
2408 
2409  ccPointCloud* proj =
2410  new ccPointCloud(getName() + QString(".ortho-rectified"));
2411  if (!proj->reserve(width * height) || !proj->reserveTheRGBTable()) {
2412  CVLog::Warning("[orthoRectifyAsCloud] Not enough memory!");
2413  delete proj;
2414  return nullptr;
2415  }
2416  proj->showColors(true);
2417 
2418  unsigned realCount = 0;
2419 
2420  // ortho rectification
2421  {
2422  for (unsigned pi = 0; pi < width; ++pi) {
2423  double xi = static_cast<double>(pi) - 0.5 * width;
2424  for (unsigned pj = 0; pj < height; ++pj) {
2425  double yi = static_cast<double>(pj) - 0.5 * height;
2426  double qi = 1.0 + c1 * xi + c2 * yi;
2427  CCVector3 P(static_cast<PointCoordinateType>(
2428  (a0 + a1 * xi + a2 * yi) / qi),
2429  static_cast<PointCoordinateType>(
2430  (b0 + b1 * xi + b2 * yi) / qi),
2431  defaultZ);
2432 
2433  // and color?
2434  QRgb rgb = image->data().pixel(pi, pj);
2435  int r = qRed(rgb);
2436  int g = qGreen(rgb);
2437  int b = qBlue(rgb);
2438  if (r + g + b > 0) {
2439  // add point
2440  proj->addPoint(P);
2441  // and color
2442  ecvColor::Rgb C(static_cast<ColorCompType>(r),
2443  static_cast<ColorCompType>(g),
2444  static_cast<ColorCompType>(b));
2445  proj->addRGBColor(C);
2446  ++realCount;
2447  }
2448  }
2449  }
2450  }
2451 
2452  if (realCount == 0) {
2453  CVLog::Warning(QString("[orthoRectifyAsCloud] Image '%1' was black, "
2454  "nothing to project!")
2455  .arg(image->getName()));
2456  delete proj;
2457  proj = nullptr;
2458  } else {
2459  proj->resize(realCount);
2460  }
2461 
2462  return proj;
2463 }
2464 
2465 /********************************************************************/
2466 /******************* *******************/
2467 /******************* ccOctreeFrustumIntersector *******************/
2468 /******************* *******************/
2469 /********************************************************************/
2470 
2472  if (!octree) return false;
2473 
2474  for (int i = 0; i < cloudViewer::DgmOctree::MAX_OCTREE_LEVEL + 1; i++)
2475  m_cellsBuilt[i].clear();
2476 
2477  const cloudViewer::DgmOctree::cellsContainer& thePointsAndTheirCellCodes =
2479  cloudViewer::DgmOctree::cellsContainer::const_iterator it =
2480  thePointsAndTheirCellCodes.begin();
2481 
2482  try {
2483  for (it = thePointsAndTheirCellCodes.begin();
2484  it != thePointsAndTheirCellCodes.end(); ++it) {
2485  cloudViewer::DgmOctree::CellCode completeCode = it->theCode;
2486  for (unsigned char level = 1;
2487  level <= cloudViewer::DgmOctree::MAX_OCTREE_LEVEL; level++) {
2488  unsigned char bitDec =
2490  m_cellsBuilt[level].insert(completeCode >> bitDec);
2491  }
2492  }
2493  } catch (const std::bad_alloc&) {
2494  CVLog::Warning("[ccCameraSensor::prepareOctree] Not enough memory!");
2495  for (int i = 0; i <= cloudViewer::DgmOctree::MAX_OCTREE_LEVEL; i++) {
2496  m_cellsBuilt[i].clear();
2497  }
2498  return false;
2499  }
2500 
2502 
2503  return true;
2504 }
2505 
2508  const CCVector3& bbMin,
2509  const CCVector3& bbMax,
2510  const float planesCoefficients[6][4],
2511  const CCVector3 frustumCorners[8],
2512  const CCVector3 frustumEdges[6],
2513  const CCVector3& frustumCenter) {
2514  // first test : if the box is too far from the frustum, there is no
2515  // intersection
2516  CCVector3 boxCenter = (bbMax + bbMin) / 2;
2517  PointCoordinateType dCenter = (boxCenter - frustumCenter).norm();
2518  PointCoordinateType boxRadius = (bbMax - bbMin).norm();
2519  PointCoordinateType frustumRadius =
2520  (frustumCorners[0] - frustumCenter).norm();
2521  if (dCenter > boxRadius + frustumRadius) return CELL_OUTSIDE_FRUSTUM;
2522 
2523  // We could add a test : is the cell circumscribed circle in the frustum
2524  // incircle frustum ?
2525  // --> if we are lucky, it could save a lot of time !...
2526 
2527  // box corners
2528  CCVector3 boxCorners[8];
2529  {
2530  for (unsigned i = 0; i < 8; i++)
2531  boxCorners[i] = CCVector3((i & 4) ? bbMin.x : bbMax.x,
2532  (i & 2) ? bbMin.y : bbMax.y,
2533  (i & 1) ? bbMin.z : bbMax.z);
2534  }
2535 
2536  // There are 28 tests to perform:
2537  // nbFacesCube = n1 = 3;
2538  // nbFacesFrustum = n2 = 5;
2539  // nbEdgesCube = n3 = 3;
2540  // nbEdgesFrustum = n4 = 6;
2541  // nbOtherFrustumCombinations = n5 = 2
2542  // nbVecToTest = n1 + n2 + n3*n4 = 3 + 5 + 3*6 + n5 = 28;
2543  static const unsigned nbVecToTest = 28;
2544  CCVector3 VecToTest[nbVecToTest];
2545  // vectors orthogonals to box planes
2546  VecToTest[0] = CCVector3(1, 0, 0);
2547  VecToTest[1] = CCVector3(0, 1, 0);
2548  VecToTest[2] = CCVector3(0, 0, 1);
2549  // vectors orthogonals to frustum planes
2550  VecToTest[3] = CCVector3(planesCoefficients[0][0], planesCoefficients[0][1],
2551  planesCoefficients[0][2]);
2552  VecToTest[4] = CCVector3(planesCoefficients[1][0], planesCoefficients[1][1],
2553  planesCoefficients[1][2]);
2554  VecToTest[5] = CCVector3(planesCoefficients[2][0], planesCoefficients[2][1],
2555  planesCoefficients[2][2]);
2556  VecToTest[6] = CCVector3(planesCoefficients[3][0], planesCoefficients[3][1],
2557  planesCoefficients[3][2]);
2558  VecToTest[7] = CCVector3(planesCoefficients[4][0], planesCoefficients[4][1],
2559  planesCoefficients[4][2]);
2560  // combinations box and frustum
2561  VecToTest[8] = VecToTest[0].cross(frustumEdges[0]);
2562  VecToTest[9] = VecToTest[0].cross(frustumEdges[1]);
2563  VecToTest[10] = VecToTest[0].cross(frustumEdges[2]);
2564  VecToTest[11] = VecToTest[0].cross(frustumEdges[3]);
2565  VecToTest[12] = VecToTest[0].cross(frustumEdges[4]);
2566  VecToTest[13] = VecToTest[0].cross(frustumEdges[5]);
2567  VecToTest[14] = VecToTest[1].cross(frustumEdges[0]);
2568  VecToTest[15] = VecToTest[1].cross(frustumEdges[1]);
2569  VecToTest[16] = VecToTest[1].cross(frustumEdges[2]);
2570  VecToTest[17] = VecToTest[1].cross(frustumEdges[3]);
2571  VecToTest[18] = VecToTest[1].cross(frustumEdges[4]);
2572  VecToTest[19] = VecToTest[1].cross(frustumEdges[5]);
2573  VecToTest[20] = VecToTest[2].cross(frustumEdges[0]);
2574  VecToTest[21] = VecToTest[2].cross(frustumEdges[1]);
2575  VecToTest[22] = VecToTest[2].cross(frustumEdges[2]);
2576  VecToTest[23] = VecToTest[2].cross(frustumEdges[3]);
2577  VecToTest[24] = VecToTest[2].cross(frustumEdges[4]);
2578  VecToTest[25] = VecToTest[2].cross(frustumEdges[5]);
2579  // combinations frustum
2580  VecToTest[26] = frustumEdges[0].cross(frustumEdges[2]);
2581  VecToTest[27] = frustumEdges[1].cross(frustumEdges[3]);
2582 
2583  // normalization
2584  {
2585  for (unsigned i = 0; i < nbVecToTest; i++) VecToTest[i].normalize();
2586  }
2587 
2588  bool boxInside = true;
2589 
2590  // project volume corners
2591  {
2592  for (unsigned i = 0; i < nbVecToTest; i++) {
2593  CCVector3 testVec = VecToTest[i];
2594 
2595  // box points
2596  float dMinBox = testVec.dot(boxCorners[0]);
2597  float dMaxBox = dMinBox;
2598  {
2599  for (unsigned j = 1; j < 8; j++) {
2600  float d = testVec.dot(boxCorners[j]);
2601  if (d > dMaxBox) dMaxBox = d;
2602  if (d < dMinBox) dMinBox = d;
2603  }
2604  }
2605 
2606  // frustum points
2607  float dMinFru = testVec.dot(frustumCorners[0]);
2608  float dMaxFru = dMinFru;
2609  {
2610  for (unsigned j = 1; j < 8; j++) {
2611  float d = testVec.dot(frustumCorners[j]);
2612  if (d > dMaxFru) dMaxFru = d;
2613  if (d < dMinFru) dMinFru = d;
2614  }
2615  }
2616 
2617  // if this plane is a separating plane, the cell is outside the
2618  // frustum
2619  if (dMaxBox < dMinFru || dMaxFru < dMinBox)
2620  return CELL_OUTSIDE_FRUSTUM;
2621 
2622  // if this plane is NOT a separating plane, the cell is at least
2623  // intersecting the frustum
2624  else {
2625  // moreover, the cell can be completely inside the frustum...
2626  if (dMaxBox > dMaxFru || dMinBox < dMinFru) boxInside = false;
2627  }
2628  }
2629  }
2630 
2631  return boxInside ? CELL_INSIDE_FRUSTUM : CELL_INTERSECT_FRUSTUM;
2632 }
2633 
2635  unsigned char level,
2636  cloudViewer::DgmOctree::CellCode parentTruncatedCode,
2637  OctreeCellVisibility parentResult,
2638  const float planesCoefficients[6][4],
2639  const CCVector3 ptsFrustum[8],
2640  const CCVector3 edges[6],
2641  const CCVector3& center) {
2642  if (parentResult == CELL_OUTSIDE_FRUSTUM) return;
2643 
2644  // move code to the left
2645  cloudViewer::DgmOctree::CellCode baseTruncatedCode =
2646  (parentTruncatedCode << 3);
2647 
2648  // test to do on the 8 child cells
2649  for (unsigned i = 0; i < 8; i++) {
2650  // set truncated code of the current cell
2651  cloudViewer::DgmOctree::CellCode truncatedCode = baseTruncatedCode + i;
2652 
2653  // if the current cell has not been built (contains no 3D points), we
2654  // skip it
2655  std::unordered_set<cloudViewer::DgmOctree::CellCode>::const_iterator
2656  got = m_cellsBuilt[level].find(truncatedCode);
2657  if (got != m_cellsBuilt[level].end()) {
2658  // get extrema of the current cell
2659  CCVector3 bbMin, bbMax;
2660  m_associatedOctree->computeCellLimits(truncatedCode, level, bbMin,
2661  bbMax, true);
2662 
2663  // look if there is a separating plane
2665  (parentResult == CELL_INSIDE_FRUSTUM
2667  : separatingAxisTest(bbMin, bbMax,
2668  planesCoefficients,
2669  ptsFrustum, edges, center));
2670 
2671  // if the cell is not outside the frustum, there is a kind of
2672  // intersection (inside or intesecting)
2673  if (result != CELL_OUTSIDE_FRUSTUM) {
2674  if (result == CELL_INSIDE_FRUSTUM)
2675  m_cellsInFrustum[level].insert(truncatedCode);
2676  else
2677  m_cellsIntersectFrustum[level].insert(truncatedCode);
2678 
2679  // we do the same for the children (if we have not already
2680  // reached the end of the tree)
2683  level + 1, truncatedCode, result,
2684  planesCoefficients, ptsFrustum, edges, center);
2685  }
2686  }
2687  }
2688 }
2689 
2691  std::vector<std::pair<unsigned, CCVector3>>& pointsToTest,
2692  std::vector<unsigned>& inCameraFrustum,
2693  const float planesCoefficients[6][4],
2694  const CCVector3 ptsFrustum[8],
2695  const CCVector3 edges[6],
2696  const CCVector3& center) {
2697  // clear old result
2698  {
2699  for (int i = 0; i <= cloudViewer::DgmOctree::MAX_OCTREE_LEVEL; i++) {
2700  m_cellsInFrustum[i].clear();
2701  m_cellsIntersectFrustum[i].clear();
2702  }
2703  }
2704 
2705  // find intersecting cells
2707  planesCoefficients, ptsFrustum, edges,
2708  center);
2709 
2710  // get points
2711  unsigned char level = static_cast<unsigned char>(
2713 
2714  // dealing with cells completely inside the frustum
2715  std::unordered_set<cloudViewer::DgmOctree::CellCode>::const_iterator it;
2716  cloudViewer::ReferenceCloud pointsInCell(
2718  for (it = m_cellsInFrustum[level].begin();
2719  it != m_cellsInFrustum[level].end(); ++it) {
2720  // get all points in cell
2721  if (m_associatedOctree->getPointsInCell(*it, level, &pointsInCell,
2722  true)) {
2723  // all points are inside the frustum since the cell itself is
2724  // completely inside
2725  for (size_t i = 0; i < pointsInCell.size(); i++)
2726  inCameraFrustum.push_back(pointsInCell.getPointGlobalIndex(
2727  static_cast<unsigned>(i)));
2728  }
2729  }
2730 
2731  // dealing with cells intersecting the frustum (not completely inside)
2732  for (it = m_cellsIntersectFrustum[level].begin();
2733  it != m_cellsIntersectFrustum[level].end(); ++it) {
2734  // get all points in cell
2735  if (m_associatedOctree->getPointsInCell(*it, level, &pointsInCell,
2736  true)) {
2737  // all points may not be inside the frustum since the cell itself is
2738  // not completely inside
2739  size_t pointCount = pointsInCell.size();
2740  size_t sizeBefore = pointsToTest.size();
2741  pointsToTest.resize(pointCount + sizeBefore);
2742  for (size_t i = 0; i < pointCount; i++) {
2743  unsigned currentIndice = pointsInCell.getPointGlobalIndex(
2744  static_cast<unsigned>(i));
2745  const CCVector3* vec =
2746  pointsInCell.getPoint(static_cast<unsigned>(i));
2747  pointsToTest[sizeBefore + i] =
2748  std::pair<unsigned, CCVector3>(currentIndice, *vec);
2749  }
2750  }
2751  }
2752 }
constexpr PointCoordinateType PC_ONE
'1' as a PointCoordinateType value
Definition: CVConst.h:67
constexpr double M_PI
Pi.
Definition: CVConst.h:19
constexpr ScalarType NAN_VALUE
NaN as a ScalarType value.
Definition: CVConst.h:76
Vector3Tpl< PointCoordinateType > CCVector3
Default 3D Vector.
Definition: CVGeom.h:798
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
std::shared_ptr< core::Tensor > image
int width
int height
int count
int points
core::Tensor result
Definition: VtkUtils.cpp:76
static bool PrintDebug(const char *format,...)
Same as Print, but works only in Debug mode.
Definition: CVLog.cpp:153
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
Type y
Definition: CVGeom.h:137
Type u[3]
Definition: CVGeom.h:139
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
Type x
Definition: CVGeom.h:36
Type norm2() const
Returns vector square norm.
Definition: CVGeom.h:61
Type y
Definition: CVGeom.h:36
void normalize()
Sets vector norm to unity.
Definition: CVGeom.h:428
Type dot(const Vector3Tpl &v) const
Dot product.
Definition: CVGeom.h:408
Type norm2() const
Returns vector square norm.
Definition: CVGeom.h:417
Type norm() const
Returns vector norm.
Definition: CVGeom.h:424
Vector3Tpl cross(const Vector3Tpl &v) const
Cross product.
Definition: CVGeom.h:412
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
Bounding box structure.
Definition: ecvBBox.h:25
Camera (projective) sensor.
const ecvColor::Rgb & getPlaneColor() const
virtual ccBBox getOwnBB(bool withGLFeatures=false) override
Returns the entity's own bounding-box.
bool computeGlobalPlaneCoefficients(float planeCoefficients[6][4], CCVector3 ptsFrustum[8], CCVector3 edges[6], CCVector3 &center)
ccCameraSensor()
Default constructor.
FrustumInformation m_frustumInfos
Frustum information structure.
bool fromImageCoordToLocalCoord(const CCVector2 &imageCoord, CCVector3 &localCoord, PointCoordinateType depth, bool withLensCorrection=true) const
ecvColor::Rgb m_plane_color
void computeProjectionMatrix()
Compute the projection matrix (from intrinsic parameters)
virtual ccBBox getOwnFitBB(ccGLMatrix &trans) override
Returns best-fit bounding-box (if available)
LensDistortionParameters::Shared m_distortionParams
Lens distortion parameters.
void setVerticalFov_rad(float fov_rad)
Sets the (vertical) field of view in radians.
bool computeOrthoRectificationParams(const ccImage *image, cloudViewer::GenericIndexedCloud *keypoints3D, std::vector< KeyPoint > &keypointsImage, double a[3], double b[3], double c[3]) const
Computes ortho-rectification parameters for a given image.
cloudViewer::geometry::LineSet m_arrow
ccPointCloud * orthoRectifyAsCloud(const ccImage *image, cloudViewer::GenericIndexedCloud *keypoints3D, std::vector< KeyPoint > &keypointsImage) const
Projective ortho-rectification of an image (as cloud)
virtual void hideShowDrawings(CC_DRAW_CONTEXT &context) override
bool toFile_MeOnly(QFile &out, short dataVersion) const override
Save own object data.
bool computeFrustumCorners()
Computes the eight corners of the frustum.
virtual ~ccCameraSensor() override
Destructor.
float getVertFocal_pix() const
Returns vertical focal (in pixels)
static float ConvertFocalMMToPix(float focal_mm, float ccdPixelSize_mm)
Helper: converts camera focal from mm to pixels.
bool fromGlobalCoordToImageCoord(const CCVector3 &globalCoord, CCVector2 &imageCoord, bool withLensError=true) const
CCVector3 computeUpperLeftPoint() const
Used internally for display.
void setVertFocal_pix(float vertFocal_pix)
Sets focal (in pixels)
ccImage * orthoRectifyAsImageDirect(const ccImage *image, PointCoordinateType altitude, double &pixelSize, bool undistortImages=true, double *minCorner=nullptr, double *maxCorner=nullptr, double *realCorners=nullptr) const
Direct ortho-rectification of an image (as image)
short minimumFileVersion_MeOnly() const override
virtual void clearDrawings() override
ccImage * orthoRectifyAsImage(const ccImage *image, cloudViewer::GenericIndexedCloud *keypoints3D, std::vector< KeyPoint > &keypointsImage, double &pixelSize, double *minCorner=nullptr, double *maxCorner=nullptr, double *realCorners=nullptr) const
Projective ortho-rectification of an image (as image)
bool isGlobalCoordInFrustum(const CCVector3 &globalCoord) const
Tests if a 3D point is in the field of view of the camera.
void setIntrinsicParameters(const IntrinsicParameters &params)
Sets intrinsic parameters.
bool fromGlobalCoordToLocalCoord(const CCVector3 &globalCoord, CCVector3 &localCoord) const
static float ComputeFovRadFromFocalPix(float focal_pix, int imageSize_pix)
Helper: deduces camera f.o.v. (in radians) from focal (in pixels)
cloudViewer::geometry::LineSet m_nearPlane
static bool OrthoRectifyAsImages(std::vector< ccImage * > images, double a[], double b[], double c[], unsigned maxSize, QDir *outputDir=nullptr, std::vector< ccImage * > *orthoRectifiedImages=nullptr, std::vector< std::pair< double, double >> *relativePos=nullptr)
Projective ortho-rectification of multiple images (as image files)
float getHorizFocal_pix() const
Returns horizontal focal (in pixels)
cloudViewer::geometry::LineSet m_axis
bool fromLocalCoordToGlobalCoord(const CCVector3 &localCoord, CCVector3 &globalCoord) const
cloudViewer::geometry::LineSet m_sideLines
virtual void drawMeOnly(CC_DRAW_CONTEXT &context) override
Draws the entity only (not its children)
bool getProjectionMatrix(ccGLMatrix &matrix)
Returns the camera projection matrix.
IntrinsicParameters m_intrinsicParams
Camera intrinsic parameters.
bool computeUncertainty(const CCVector2 &pixel, const float depth, Vector3Tpl< ScalarType > &sigma) const
static float ConvertFocalPixToMM(float focal_pix, float ccdPixelSize_mm)
Helper: converts camera focal from pixels to mm.
bool fromLocalCoordToImageCoord(const CCVector3 &localCoord, CCVector2 &imageCoord, bool withLensError=true) const
bool fromFile_MeOnly(QFile &in, short dataVersion, int flags, LoadedIDMap &oldToNewIDMap) override
Loads own object data.
QImage undistort(const QImage &image) const
Undistorts an image based on the sensor distortion parameters.
static float ComputeFovRadFromFocalMm(float focal_mm, float ccdSize_mm)
Helper: deduces camera f.o.v. (in radians) from focal (in mm)
bool fromImageCoordToGlobalCoord(const CCVector2 &imageCoord, CCVector3 &globalCoord, PointCoordinateType z0, bool withLensCorrection=true) const
virtual bool applyViewport() override
Apply sensor 'viewport' to a 3D view.
bool m_projectionMatrixIsValid
Whether the intrinsic matrix is valid or not.
bool fromRealImCoordToIdealImCoord(const CCVector2 &real, CCVector2 &ideal) const
DistortionModel
Supported distortion models.
void setDistortionParameters(LensDistortionParameters::Shared params)
Sets uncertainty parameters.
ccGLMatrix m_projectionMatrix
Intrinsic parameters matrix.
void drawFrustum(bool state)
Sets whether the frustum should be displayed or not.
virtual void lockVisibility(bool state)
Locks/unlocks visibility.
virtual void setTempColor(const ecvColor::Rgb &col, bool autoActivate=true)
Sets current temporary (unique)
virtual void showColors(bool state)
Sets colors visibility.
virtual void setOpacity(float opacity)
Set opacity activation state.
Vector3Tpl< T > getTranslationAsVec3D() const
Returns a copy of the translation as a CCVector3.
virtual void toZero()
Clears matrix.
bool fromFile(QFile &in, short dataVersion, int flags, LoadedIDMap &oldToNewIDMap) override
Loads data from binary stream.
void applyRotation(Vector3Tpl< float > &vec) const
Applies rotation only to a 3D vector (in place) - float version.
T * data()
Returns a pointer to internal data.
void apply(float vec[3]) const
Applies transformation to a 3D vector (in place) - float version.
Vector3Tpl< T > getColumnAsVec3D(unsigned index) const
Returns a copy of a given column as a CCVector3.
static Eigen::Matrix< double, 4, 4 > ToEigenMatrix4(const ccGLMatrixTpl< float > &mat)
Float version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:19
virtual void showWired(bool state)
Sets whether mesh should be displayed as a wire or with plain facets.
void enableStippling(bool state)
Enables polygon stippling.
ccBBox getOwnBB(bool withGLFeatures=false) override
Returns the entity's own bounding-box.
@ SELECTION_FIT_BBOX
Definition: ecvHObject.h:607
QString getViewId() const
Definition: ecvHObject.h:225
virtual void setSelectionBehavior(SelectionBehavior mode)
Sets selection behavior (when displayed)
Definition: ecvHObject.h:616
Generic image.
Definition: ecvImage.h:19
ccIndexedTransformation inverse() const
Returns inverse transformation.
Triangular mesh.
Definition: ecvMesh.h:35
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
void computeFrustumIntersectionWithOctree(std::vector< std::pair< unsigned, CCVector3 >> &pointsToTest, std::vector< unsigned > &inCameraFrustum, const float planesCoefficients[6][4], const CCVector3 ptsFrustum[8], const CCVector3 edges[6], const CCVector3 &center)
cloudViewer::DgmOctree * m_associatedOctree
OctreeCellVisibility
Definition of the state of a cell compared to a frustum.
OctreeCellVisibility separatingAxisTest(const CCVector3 &bbMin, const CCVector3 &bbMax, const float planesCoefficients[6][4], const CCVector3 frustumCorners[8], const CCVector3 frustumEdges[6], const CCVector3 &frustumCenter)
Separating Axis Test.
bool build(cloudViewer::DgmOctree *octree)
Prepares structure for frustum filtering.
std::unordered_set< cloudViewer::DgmOctree::CellCode > m_cellsBuilt[cloudViewer::DgmOctree::MAX_OCTREE_LEVEL+1]
void computeFrustumIntersectionByLevel(unsigned char level, cloudViewer::DgmOctree::CellCode parentTruncatedCode, OctreeCellVisibility parentResult, const float planesCoefficients[6][4], const CCVector3 ptsFrustum[8], const CCVector3 edges[6], const CCVector3 &center)
std::unordered_set< cloudViewer::DgmOctree::CellCode > m_cellsInFrustum[cloudViewer::DgmOctree::MAX_OCTREE_LEVEL+1]
std::unordered_set< cloudViewer::DgmOctree::CellCode > m_cellsIntersectFrustum[cloudViewer::DgmOctree::MAX_OCTREE_LEVEL+1]
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
bool reserveTheRGBTable()
Reserves memory to store the RGB colors.
virtual void applyRigidTransformation(const ccGLMatrix &trans) override
Applies a rigid transformation (rotation + translation)
bool resize(unsigned numberOfPoints) override
Resizes all the active features arrays.
void addRGBColor(const ecvColor::Rgb &C)
Pushes an RGB color on stack.
Generic sensor interface.
Definition: ecvSensor.h:27
short minimumFileVersion_MeOnly() const override
Definition: ecvSensor.cpp:155
bool fromFile_MeOnly(QFile &in, short dataVersion, int flags, LoadedIDMap &oldToNewIDMap) override
Loads own object data.
Definition: ecvSensor.cpp:161
PointCoordinateType m_scale
Sensor graphic representation scale.
Definition: ecvSensor.h:169
bool toFile_MeOnly(QFile &out, short dataVersion) const override
Save own object data.
Definition: ecvSensor.cpp:123
double m_activeIndex
Active index (for displayed position, etc.)
Definition: ecvSensor.h:161
bool getAbsoluteTransformation(ccIndexedTransformation &trans, double index) const
Definition: ecvSensor.cpp:78
bool getActiveAbsoluteTransformation(ccIndexedTransformation &trans) const
Gets currently active absolute transformation.
Definition: ecvSensor.cpp:90
ecvColor::Rgb m_color
Color of the sensor.
Definition: ecvSensor.h:166
double getActiveIndex() const
Sets currently active index (displayed position, etc.)
Definition: ecvSensor.h:119
const ecvColor::Rgb & getFrameColor() const
Definition: ecvSensor.h:131
QMultiMap< unsigned, unsigned > LoadedIDMap
Map of loaded unique IDs (old ID --> new ID)
static bool ReadError()
Sends a custom error message (read error) and returns 'false'.
static void CoordsFromDataStream(QDataStream &stream, int flags, PointCoordinateType *out, unsigned count=1)
A class to perform a conjugate gradient optimization.
Scalar iterConjugateGradient(Scalar *Xn)
Iterates the conjugate gradient.
cloudViewer::SquareMatrixTpl< Scalar > & A()
Returns A matrix.
void initConjugateGradient(const Scalar *X0)
Initializes the conjugate gradient.
Scalar * b()
Returns b vector.
The octree structure used throughout the library.
Definition: DgmOctree.h:39
unsigned CellCode
Type of the code of an octree cell.
Definition: DgmOctree.h:78
GenericIndexedCloudPersist * associatedCloud() const
Returns the associated cloud.
Definition: DgmOctree.h:1191
static const int MAX_OCTREE_LEVEL
Max octree subdivision level.
Definition: DgmOctree.h:67
bool getPointsInCell(CellCode cellCode, unsigned char level, ReferenceCloud *subset, bool isCodeTruncated=false, bool clearOutputCloud=true) const
Returns the points lying in a specific cell.
Definition: DgmOctree.cpp:537
void computeCellLimits(CellCode code, unsigned char level, CCVector3 &cellMin, CCVector3 &cellMax, bool isCodeTruncated=false) const
Returns the spatial limits of a given cell.
Definition: DgmOctree.cpp:520
std::vector< IndexAndCode > cellsContainer
Container of 'IndexAndCode' structures.
Definition: DgmOctree.h:351
const cellsContainer & pointsAndTheirCellCodes() const
Returns the octree 'structure'.
Definition: DgmOctree.h:1196
static unsigned char GET_BIT_SHIFT(unsigned char level)
Returns the binary shift for a given level of subdivision.
Definition: DgmOctree.cpp:113
A generic 3D point cloud with index-based point access.
virtual const CCVector3 * getPoint(unsigned index) const =0
Returns the ith point.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
unsigned size() const override
Definition: PointCloudTpl.h:38
const CCVector3 * getPoint(unsigned index) const override
A very simple point cloud (no point duplication)
unsigned size() const override
Returns the number of points.
virtual unsigned getPointGlobalIndex(unsigned localIndex) const
const CCVector3 * getPoint(unsigned index) const override
Returns the ith point.
Scalar ** m_values
The matrix rows.
Definition: SquareMatrix.h:157
std::vector< Eigen::Vector3d > points_
Points coordinates.
Definition: LineSet.h:156
std::vector< Eigen::Vector3d > colors_
RGB colors of lines.
Definition: LineSet.h:160
std::vector< Eigen::Vector2i > lines_
Lines denoted by the index of points forming the line.
Definition: LineSet.h:158
virtual LineSet & Transform(const Eigen::Matrix4d &transformation) override
Apply transformation (4x4 matrix) to the geometry coordinates.
Definition: LineSet.cpp:67
RGB color structure.
Definition: ecvColorTypes.h:49
static Eigen::Vector3d ToEigen(const Type col[3])
Definition: ecvColorTypes.h:72
static void RemoveEntities(const ccHObject *obj)
static void Draw(const CC_DRAW_CONTEXT &context, const ccHObject *obj)
static void SetViewportParameters(const ecvViewportParameters &params)
static bool HideShowEntities(const ccHObject *obj, bool visible)
static QWidget * GetCurrentScreen()
static void UpdateScreen()
static const ecvViewportParameters & GetViewportParameters()
Standard parameters for GL displays/viewports.
float fov_deg
Camera F.O.V. (field of view) in degrees.
double zFar
Actual perspective 'zFar' value.
void setPivotPoint(const CCVector3d &P, bool autoUpdateFocal=true)
Sets the pivot point (for object-centered view mode)
void setCameraCenter(const CCVector3d &C, bool autoUpdateFocal=true)
Sets the camera center.
double zNear
Actual perspective 'zNear' value.
unsigned char ColorCompType
Default color components type (R,G and B)
Definition: ecvColorTypes.h:29
#define MACRO_Draw3D(context)
@ ECV_SURFACE_MODE
@ CC_ENTITY_PICKING
@ ECV_MESH
a[190]
GraphType data
Definition: graph_cut.cc:138
ImGuiContext * context
Definition: Window.cpp:76
normal_z y
normal_z rgb
normal_z x
normal_z z
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
Tensor Append(const Tensor &self, const Tensor &other, const utility::optional< int64_t > &axis)
Appends the two tensors, along the given axis into a new tensor. Both the tensors must have same data...
MiniVec< float, N > ceil(const MiniVec< float, N > &a)
Definition: MiniVec.h:89
float RadiansToDegrees(int radians)
Convert radians to degrees.
Definition: CVMath.h:71
bool LessThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
Definition: CVMath.h:23
Colors namespace.
Definition: ecvColorTypes.h:32
constexpr Rgb red(MAX, 0, 0)
constexpr Rgb green(0, MAX, 0)
constexpr Rgb yellow(MAX, MAX, 0)
Eigen::Matrix< Index, 2, 1 > Vector2i
Definition: knncpp.h:29
cloudViewer::DgmOctree * octree
unsigned char uchar
Definition: shpopen.c:319
Brown's distortion model + Linear Disparity.
static void GetKinectDefaults(BrownDistortionParameters &params)
float k3
3rd radial distortion coefficient
bool initFrustumCorners()
Reserves memory for the frustum corners cloud.
FrustumInformation()
Default initializer.
bool initFrustumHull()
Creates the frustum hull mesh.
Intrinsic parameters of the camera sensor.
IntrinsicParameters()
Default initializer.
static void GetKinectDefaults(IntrinsicParameters &params)
unsigned index
Index in associated point cloud.
float y
2D 'y' coordinate (in pixels)
float x
2D 'x' coordinate (in pixels)
QSharedPointer< LensDistortionParameters > Shared
Shared pointer type.
Simple radial distortion model.
float k2
2nd radial distortion coefficient
float k1
1st radial distortion coefficient
Display context.
int drawingFlags
Drawing options (see below)
ecvColor::Rgb defaultPolylineColor
Default color for polyline.
ecvColor::Rgb defaultMeshColor
Default color for mesh.
unsigned char currentLineWidth
MESH_RENDERING_MODE meshRenderingMode