Visual Servoing Platform  version 3.6.1 under development (2024-04-28)
vpPoseRGBD.cpp
1 /*
2  * ViSP, open source Visual Servoing Platform software.
3  * Copyright (C) 2005 - 2023 by Inria. All rights reserved.
4  *
5  * This software is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  * See the file LICENSE.txt at the root directory of this source
10  * distribution for additional information about the GNU GPL.
11  *
12  * For using ViSP with software that can not be combined with the GNU
13  * GPL, please contact Inria about acquiring a ViSP Professional
14  * Edition License.
15  *
16  * See https://visp.inria.fr for more information.
17  *
18  * This software was developed at:
19  * Inria Rennes - Bretagne Atlantique
20  * Campus Universitaire de Beaulieu
21  * 35042 Rennes Cedex
22  * France
23  *
24  * If you have questions regarding the use of this file, please contact
25  * Inria at visp@inria.fr
26  *
27  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
28  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29  *
30  * Description:
31  * Pose computation from RGBD.
32  */
33 
34 #include <visp3/core/vpPixelMeterConversion.h>
35 #include <visp3/core/vpPlane.h>
36 #include <visp3/core/vpPolygon.h>
37 #include <visp3/core/vpRobust.h>
38 #include <visp3/vision/vpPose.h>
39 
40 namespace
41 {
42 // See also vpPlaneEstimation.cpp that implements the same functionaly in c++17
43 void estimatePlaneEquationSVD(const std::vector<double> &point_cloud_face, vpPlane &plane_equation_estimated,
44  vpColVector &centroid, double &normalized_weights)
45 {
46  unsigned int max_iter = 10;
47  double prev_error = 1e3;
48  double error = 1e3 - 1;
49  unsigned int nPoints = static_cast<unsigned int>(point_cloud_face.size() / 3);
50 
51  vpColVector weights(nPoints, 1.0);
52  vpColVector residues(nPoints);
53  vpMatrix M(nPoints, 3);
54  vpRobust tukey;
56  vpColVector normal;
57 
58  double fabs_error_m_prev_error = std::fabs(error - prev_error);
59  for (unsigned int iter = 0; (iter < max_iter) && (fabs_error_m_prev_error > 1e-6); ++iter) {
60  if (iter != 0) {
61  tukey.MEstimator(vpRobust::TUKEY, residues, weights);
62  }
63 
64  // Compute centroid
65  double centroid_x = 0.0, centroid_y = 0.0, centroid_z = 0.0;
66  double total_w = 0.0;
67 
68  for (unsigned int i = 0; i < nPoints; ++i) {
69  centroid_x += weights[i] * point_cloud_face[(3 * i) + 0];
70  centroid_y += weights[i] * point_cloud_face[(3 * i) + 1];
71  centroid_z += weights[i] * point_cloud_face[(3 * i) + 2];
72  total_w += weights[i];
73  }
74 
75  centroid_x /= total_w;
76  centroid_y /= total_w;
77  centroid_z /= total_w;
78 
79  // Minimization
80  for (unsigned int i = 0; i < nPoints; ++i) {
81  M[static_cast<unsigned int>(i)][0] = weights[i] * (point_cloud_face[(3 * i) + 0] - centroid_x);
82  M[static_cast<unsigned int>(i)][1] = weights[i] * (point_cloud_face[(3 * i) + 1] - centroid_y);
83  M[static_cast<unsigned int>(i)][2] = weights[i] * (point_cloud_face[(3 * i) + 2] - centroid_z);
84  }
85 
86  vpColVector W;
87  vpMatrix V;
88  vpMatrix J = M.t() * M;
89  J.svd(W, V);
90 
91  double smallestSv = W[0];
92  unsigned int indexSmallestSv = 0;
93  unsigned int w_size = W.size();
94  for (unsigned int i = 1; i < w_size; ++i) {
95  if (W[i] < smallestSv) {
96  smallestSv = W[i];
97  indexSmallestSv = i;
98  }
99  }
100 
101  normal = V.getCol(indexSmallestSv);
102 
103  // Compute plane equation
104  double A = normal[0], B = normal[1], C = normal[2];
105  double D = -((A * centroid_x) + (B * centroid_y) + (C * centroid_z));
106 
107  // Compute error points to estimated plane
108  prev_error = error;
109  error = 0.0;
110  for (unsigned int i = 0; i < nPoints; ++i) {
111  residues[i] = std::fabs((A * point_cloud_face[3 * i]) + (B * point_cloud_face[(3 * i) + 1]) +
112  (C * point_cloud_face[(3 * i) + 2]) + D) /
113  sqrt((A * A) + (B * B) + (C * C));
114  error += weights[i] * residues[i];
115  }
116  error /= total_w;
117  // evaluate one of the end conditions of the for
118  fabs_error_m_prev_error = std::fabs(error - prev_error);
119  }
120 
121  // Update final weights
122  tukey.MEstimator(vpRobust::TUKEY, residues, weights);
123 
124  // Update final centroid
125  centroid.resize(3, false);
126  double total_w = 0.0;
127 
128  for (unsigned int i = 0; i < nPoints; ++i) {
129  centroid[0] += weights[i] * point_cloud_face[3 * i];
130  centroid[1] += weights[i] * point_cloud_face[(3 * i) + 1];
131  centroid[2] += weights[i] * point_cloud_face[(3 * i) + 2];
132  total_w += weights[i];
133  }
134 
135  centroid[0] /= total_w;
136  centroid[1] /= total_w;
137  centroid[2] /= total_w;
138 
139  // Compute final plane equation
140  double A = normal[0], B = normal[1], C = normal[2];
141  double D = -((A * centroid[0]) + (B * centroid[1]) + (C * centroid[2]));
142 
143  // Update final plane equation
144  plane_equation_estimated.setABCD(A, B, C, D);
145 
146  normalized_weights = total_w / nPoints;
147 }
148 
149 } // namespace
150 
151 bool vpPose::computePlanarObjectPoseFromRGBD(const vpImage<float> &depthMap, const std::vector<vpImagePoint> &corners,
152  const vpCameraParameters &colorIntrinsics,
153  const std::vector<vpPoint> &point3d, vpHomogeneousMatrix &cMo,
154  double *confidence_index)
155 {
156  if (corners.size() != point3d.size()) {
158  "Cannot compute pose from RGBD, 3D (%d) and 2D (%d) data doesn't have the same size",
159  point3d.size(), corners.size()));
160  }
161  std::vector<vpPoint> pose_points;
162  if (confidence_index != nullptr) {
163  *confidence_index = 0.0;
164  }
165 
166  size_t point3d_size = point3d.size();
167  for (size_t i = 0; i < point3d_size; ++i) {
168  pose_points.push_back(point3d[i]);
169  }
170 
171  vpPolygon polygon(corners);
172  vpRect bb = polygon.getBoundingBox();
173  unsigned int top = static_cast<unsigned int>(std::max<int>(0, static_cast<int>(bb.getTop())));
174  unsigned int bottom =
175  static_cast<unsigned int>(std::min<int>(static_cast<int>(depthMap.getHeight()) - 1, static_cast<int>(bb.getBottom())));
176  unsigned int left = static_cast<unsigned int>(std::max<int>(0, static_cast<int>(bb.getLeft())));
177  unsigned int right =
178  static_cast<unsigned int>(std::min<int>(static_cast<int>(depthMap.getWidth()) - 1, static_cast<int>(bb.getRight())));
179 
180  std::vector<double> points_3d;
181  points_3d.reserve((bottom - top) * (right - left));
182  for (unsigned int idx_i = top; idx_i < bottom; ++idx_i) {
183  for (unsigned int idx_j = left; idx_j < right; ++idx_j) {
184  vpImagePoint imPt(idx_i, idx_j);
185  if ((depthMap[idx_i][idx_j] > 0) && (polygon.isInside(imPt))) {
186  double x = 0, y = 0;
187  vpPixelMeterConversion::convertPoint(colorIntrinsics, imPt.get_u(), imPt.get_v(), x, y);
188  double Z = depthMap[idx_i][idx_j];
189  points_3d.push_back(x * Z);
190  points_3d.push_back(y * Z);
191  points_3d.push_back(Z);
192  }
193  }
194  }
195 
196  unsigned int nb_points_3d = static_cast<unsigned int>(points_3d.size() / 3);
197 
198  if (nb_points_3d > 4) {
199  std::vector<vpPoint> p, q;
200 
201  // Plane equation
202  vpPlane plane_equation;
203  vpColVector centroid;
204  double normalized_weights = 0;
205  estimatePlaneEquationSVD(points_3d, plane_equation, centroid, normalized_weights);
206 
207  size_t corners_size = corners.size();
208  for (size_t j = 0; j < corners_size; ++j) {
209  const vpImagePoint &imPt = corners[j];
210  double x = 0, y = 0;
211  vpPixelMeterConversion::convertPoint(colorIntrinsics, imPt.get_u(), imPt.get_v(), x, y);
212  double Z = plane_equation.computeZ(x, y);
213  if (Z < 0) {
214  Z = -Z;
215  }
216  p.push_back(vpPoint(x * Z, y * Z, Z));
217 
218  pose_points[j].set_x(x);
219  pose_points[j].set_y(y);
220  }
221 
222  size_t point3d_size = point3d.size();
223  for (size_t i = 0; i < point3d_size; ++i) {
224  q.push_back(point3d[i]);
225  }
226 
228 
229  if (cMo.isValid()) {
230  vpPose pose;
231  pose.addPoints(pose_points);
232  if (pose.computePose(vpPose::VIRTUAL_VS, cMo)) {
233  if (confidence_index != nullptr) {
234  *confidence_index = std::min<double>(1.0, (normalized_weights * static_cast<double>(nb_points_3d)) / polygon.getArea());
235  }
236  return true;
237  }
238  }
239  }
240 
241  return false;
242 }
243 
245  const std::vector<std::vector<vpImagePoint> > &corners,
246  const vpCameraParameters &colorIntrinsics,
247  const std::vector<std::vector<vpPoint> > &point3d,
248  vpHomogeneousMatrix &cMo, double *confidence_index, bool coplanar_points)
249 {
250 
251  if (corners.size() != point3d.size()) {
253  "Cannot compute pose from RGBD, 3D (%d) and 2D (%d) data doesn't have the same size",
254  point3d.size(), corners.size()));
255  }
256  std::vector<vpPoint> pose_points;
257  if (confidence_index != nullptr) {
258  *confidence_index = 0.0;
259  }
260 
261  size_t point3d_size = point3d.size();
262  for (size_t i = 0; i < point3d_size; ++i) {
263  std::vector<vpPoint> tagPoint3d = point3d[i];
264  size_t tagpoint3d_size = tagPoint3d.size();
265  for (size_t j = 0; j < tagpoint3d_size; ++j) {
266  pose_points.push_back(tagPoint3d[j]);
267  }
268  }
269 
270  // Total area of the polygon to estimate confidence
271  double totalArea = 0.0;
272 
273  // If coplanar is true, the tags_points_3d will be used to compute one plane
274  std::vector<double> tag_points_3d;
275 
276  // Otherwise the vector of planes will be used to compute each plane for each vector
277  std::vector<std::vector<double> > tag_points_3d_nonplanar;
278  size_t nb_points_3d_non_planar = 0;
279 
280  // Loop through each object, compute 3d point cloud of each
281  size_t corners_size = corners.size();
282  for (size_t i = 0; i < corners_size; ++i) {
283  std::vector<double> points_3d;
284  vpPolygon polygon(corners[i]);
285  vpRect bb = polygon.getBoundingBox();
286 
287  // The area to calculate final confidence index should be total area of the tags
288  totalArea += polygon.getArea();
289 
290  unsigned int top = static_cast<unsigned int>(std::max<int>(0, static_cast<int>(bb.getTop())));
291  unsigned int bottom = static_cast<unsigned int>(
292  std::min<int>(static_cast<int>(depthMap.getHeight()) - 1, static_cast<int>(bb.getBottom())));
293  unsigned int left = static_cast<unsigned int>(std::max<int>(0, static_cast<int>(bb.getLeft())));
294  unsigned int right =
295  static_cast<unsigned int>(std::min<int>(static_cast<int>(depthMap.getWidth()) - 1, static_cast<int>(bb.getRight())));
296 
297  points_3d.reserve((bottom - top) * (right - left));
298  for (unsigned int idx_i = top; idx_i < bottom; ++idx_i) {
299  for (unsigned int idx_j = left; idx_j < right; ++idx_j) {
300  vpImagePoint imPt(idx_i, idx_j);
301  if ((depthMap[idx_i][idx_j] > 0) && (polygon.isInside(imPt))) {
302  double x = 0, y = 0;
303  vpPixelMeterConversion::convertPoint(colorIntrinsics, imPt.get_u(), imPt.get_v(), x, y);
304  double Z = depthMap[idx_i][idx_j];
305  points_3d.push_back(x * Z);
306  points_3d.push_back(y * Z);
307  points_3d.push_back(Z);
308  }
309  }
310  }
311 
312  // If coplanar_points is true, feed all 3d points to single vector
313  // Otherwise, each vector will hold 3d points for separate planes
314  if (coplanar_points) {
315  tag_points_3d.insert(tag_points_3d.end(), points_3d.begin(), points_3d.end());
316  }
317  else {
318  tag_points_3d_nonplanar.push_back(points_3d);
319  nb_points_3d_non_planar += points_3d.size();
320  }
321  }
322 
323  size_t nb_points_3d = 0;
324 
325  if (coplanar_points) {
326  nb_points_3d = tag_points_3d.size() / 3;
327  }
328  else {
329  nb_points_3d = nb_points_3d_non_planar / 3;
330  }
331 
332  if (nb_points_3d > 4) {
333  std::vector<vpPoint> p, q;
334 
335  // Plane equation
336  vpPlane plane_equation;
337  vpColVector centroid;
338  double normalized_weights = 0;
339 
340  if (coplanar_points) {
341  // If all objects are coplanar, use points insides tag_points_3d to estimate the plane
342  estimatePlaneEquationSVD(tag_points_3d, plane_equation, centroid, normalized_weights);
343  int count = 0;
344  size_t corners_size = corners.size();
345  for (size_t j = 0; j < corners_size; ++j) {
346  std::vector<vpImagePoint> tag_corner = corners[j];
347  size_t tag_corner_size = tag_corner.size();
348  for (size_t i = 0; i < tag_corner_size; ++i) {
349  const vpImagePoint &imPt = tag_corner[i];
350  double x = 0, y = 0;
351  vpPixelMeterConversion::convertPoint(colorIntrinsics, imPt.get_u(), imPt.get_v(), x, y);
352  double Z = plane_equation.computeZ(x, y);
353  std::cout << Z;
354  if (Z < 0) {
355  Z = -Z;
356  }
357  p.push_back(vpPoint(x * Z, y * Z, Z));
358  pose_points[count].set_x(x);
359  pose_points[count].set_y(y);
360  ++count;
361  }
362  }
363  }
364  else {
365  // If the tags is not coplanar, estimate the plane for each tags
366  size_t count = 0;
367 
368  size_t tag_points_3d_nonplanar_size = tag_points_3d_nonplanar.size();
369  for (size_t k = 0; k < tag_points_3d_nonplanar_size; ++k) {
370  std::vector<double> rec_points_3d = tag_points_3d_nonplanar[k];
371  double tag_normalized_weights = 0;
372 
373  if (rec_points_3d.size() >= 9) {
374  // The array must has at least 3 points for the function estimatePlaneEquationSVD not to crash
375  estimatePlaneEquationSVD(rec_points_3d, plane_equation, centroid, tag_normalized_weights);
376  normalized_weights += tag_normalized_weights;
377 
378  // Get the 2d points of the tag the plane just recomputed
379  std::vector<vpImagePoint> tag_corner = corners[k];
380 
381  size_t tag_corner_size = tag_corner.size();
382  for (size_t i = 0; i < tag_corner_size; ++i) {
383  const vpImagePoint &imPt = tag_corner[i];
384  double x = 0, y = 0;
385  vpPixelMeterConversion::convertPoint(colorIntrinsics, imPt.get_u(), imPt.get_v(), x, y);
386  double Z = plane_equation.computeZ(x, y);
387 
388  if (Z < 0) {
389  Z = -Z;
390  }
391  p.push_back(vpPoint(x * Z, y * Z, Z));
392  pose_points[count].set_x(x);
393  pose_points[count].set_y(y);
394  ++count;
395  }
396  }
397  else {
398  // Sometimes an object may do not have enough points registered due to small size or bad alignment btw depth
399  // and rgb. This behavior happens with Orbbec camera while Realsenses was fine. To prevent exception while
400  // computePose, skip recomputing the failed estimation tag's (4 point - corners)
401  count += corners[k].size();
402  }
403  }
404  normalized_weights = normalized_weights / tag_points_3d_nonplanar.size();
405  }
406 
407  size_t point3d_size = point3d.size();
408  for (size_t i = 0; i < point3d_size; ++i) {
409  std::vector<vpPoint> tagPoint3d = point3d[i];
410  // Sometimes an object may do not have enough points registered due to small size.
411  // The issue happens with Orbbec camera while Realsenses was fine.
412  // To prevent wrong estimation or exception (p and q sizes are differents),
413  // ignore the recomputer vector (tag_points_3d_nonplanar) when size = 0
414  if (coplanar_points) {
415  size_t tag_point3d_size = tagPoint3d.size();
416  for (size_t j = 0; j < tag_point3d_size; ++j) {
417  q.push_back(tagPoint3d[j]);
418  }
419  }
420  else {
421  if (tag_points_3d_nonplanar[i].size() > 0) {
422  size_t tag_point3d_size = tagPoint3d.size();
423  for (size_t j = 0; j < tag_point3d_size; ++j) {
424  q.push_back(tagPoint3d[j]);
425  }
426  }
427  }
428  }
429 
430  // Due to the possibility of q's size might less than p's, check their size should be identical
431  if (p.size() == q.size()) {
433 
434  if (cMo.isValid()) {
435  vpPose pose;
436  pose.addPoints(pose_points);
437  if (pose.computePose(vpPose::VIRTUAL_VS, cMo)) {
438  if (confidence_index != nullptr) {
439  *confidence_index = std::min<double>(1.0, (normalized_weights * static_cast<double>(nb_points_3d)) / totalArea);
440  }
441  return true;
442  }
443  }
444  }
445  }
446  return false;
447 }
unsigned int size() const
Return the number of elements of the 2D array.
Definition: vpArray2D.h:339
Generic class defining intrinsic camera parameters.
Implementation of column vector and the associated operations.
Definition: vpColVector.h:163
void resize(unsigned int i, bool flagNullify=true)
Definition: vpColVector.h:1056
error that can be emitted by ViSP classes.
Definition: vpException.h:59
@ fatalError
Fatal error.
Definition: vpException.h:84
Implementation of an homogeneous matrix and operations on such kind of matrices.
static vpHomogeneousMatrix compute3d3dTransformation(const std::vector< vpPoint > &p, const std::vector< vpPoint > &q)
Class that defines a 2D point in an image. This class is useful for image processing and stores only ...
Definition: vpImagePoint.h:82
double get_u() const
Definition: vpImagePoint.h:136
double get_v() const
Definition: vpImagePoint.h:147
unsigned int getWidth() const
Definition: vpImage.h:245
void insert(const vpImage< Type > &src, const vpImagePoint &topLeft)
Definition: vpImage.h:1533
unsigned int getHeight() const
Definition: vpImage.h:184
Implementation of a matrix and operations on matrices.
Definition: vpMatrix.h:146
void svd(vpColVector &w, vpMatrix &V)
Definition: vpMatrix.cpp:2132
vpMatrix t() const
Definition: vpMatrix.cpp:465
vpColVector getCol(unsigned int j) const
Definition: vpMatrix.cpp:4975
static void convertPoint(const vpCameraParameters &cam, const double &u, const double &v, double &x, double &y)
This class defines the container for a plane geometrical structure.
Definition: vpPlane.h:54
double computeZ(double x, double y) const
Definition: vpPlane.cpp:236
void setABCD(double a, double b, double c, double d)
Definition: vpPlane.h:88
Class that defines a 3D point in the object frame and allows forward projection of a 3D point in the ...
Definition: vpPoint.h:77
Defines a generic 2D polygon.
Definition: vpPolygon.h:97
vpRect getBoundingBox() const
Definition: vpPolygon.h:171
double getArea() const
Definition: vpPolygon.h:155
bool isInside(const vpImagePoint &iP, const PointInPolygonMethod &method=PnPolyRayCasting) const
Definition: vpPolygon.cpp:394
Class used for pose computation from N points (pose from point only). Some of the algorithms implemen...
Definition: vpPose.h:78
static bool computePlanarObjectPoseFromRGBD(const vpImage< float > &depthMap, const std::vector< vpImagePoint > &corners, const vpCameraParameters &colorIntrinsics, const std::vector< vpPoint > &point3d, vpHomogeneousMatrix &cMo, double *confidence_index=nullptr)
Definition: vpPoseRGBD.cpp:151
@ VIRTUAL_VS
Definition: vpPose.h:93
void addPoints(const std::vector< vpPoint > &lP)
Definition: vpPose.cpp:100
bool computePose(vpPoseMethodType method, vpHomogeneousMatrix &cMo, bool(*func)(const vpHomogeneousMatrix &)=nullptr)
Definition: vpPose.cpp:339
Defines a rectangle in the plane.
Definition: vpRect.h:76
double getLeft() const
Definition: vpRect.h:170
double getRight() const
Definition: vpRect.h:176
double getBottom() const
Definition: vpRect.h:94
double getTop() const
Definition: vpRect.h:189
Contains an M-estimator and various influence function.
Definition: vpRobust.h:83
@ TUKEY
Tukey influence function.
Definition: vpRobust.h:88
void MEstimator(const vpRobustEstimatorType method, const vpColVector &residues, vpColVector &weights)
Definition: vpRobust.cpp:136
void setMinMedianAbsoluteDeviation(double mad_min)
Definition: vpRobust.h:156