Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
vpPoseRGBD.cpp
1/*
2 * ViSP, open source Visual Servoing Platform software.
3 * Copyright (C) 2005 - 2024 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
41
42void estimatePlaneEquationSVD(const std::vector<double> &point_cloud_face, vpPlane &plane_equation_estimated,
43 vpColVector &centroid, double &normalized_weights);
44
45// See also vpPlaneEstimation.cpp that implements the same functionaly in c++17
46void estimatePlaneEquationSVD(const std::vector<double> &point_cloud_face, vpPlane &plane_equation_estimated,
47 vpColVector &centroid, double &normalized_weights)
48{
49 const unsigned int max_iter = 10;
50 double prev_error = 1e3;
51 double error = 1e3 - 1;
52 const unsigned int size3DPt = 3;
53 unsigned int nPoints = static_cast<unsigned int>(point_cloud_face.size() / size3DPt);
54 const unsigned int idX = 0;
55 const unsigned int idY = 1;
56 const unsigned int idZ = 2;
57
58 vpColVector weights(nPoints, 1.0);
59 vpColVector residues(nPoints);
60 vpMatrix M(nPoints, size3DPt);
61 vpRobust tukey;
63 vpColVector normal;
64
65 double fabs_error_m_prev_error = std::fabs(error - prev_error);
66 unsigned int iter = 0;
67 while ((iter < max_iter) && (fabs_error_m_prev_error > 1e-6)) {
68 if (iter != 0) {
69 tukey.MEstimator(vpRobust::TUKEY, residues, weights);
70 }
71
72 // Compute centroid
73 double centroid_x = 0.0, centroid_y = 0.0, centroid_z = 0.0;
74 double total_w = 0.0;
75
76 for (unsigned int i = 0; i < nPoints; ++i) {
77 centroid_x += weights[i] * point_cloud_face[(size3DPt * i) + idX];
78 centroid_y += weights[i] * point_cloud_face[(size3DPt * i) + idY];
79 centroid_z += weights[i] * point_cloud_face[(size3DPt * i) + idZ];
80 total_w += weights[i];
81 }
82
83 centroid_x /= total_w;
84 centroid_y /= total_w;
85 centroid_z /= total_w;
86
87 // Minimization
88 for (unsigned int i = 0; i < nPoints; ++i) {
89 M[static_cast<unsigned int>(i)][idX] = weights[i] * (point_cloud_face[(size3DPt * i) + idX] - centroid_x);
90 M[static_cast<unsigned int>(i)][idY] = weights[i] * (point_cloud_face[(size3DPt * i) + idY] - centroid_y);
91 M[static_cast<unsigned int>(i)][idZ] = weights[i] * (point_cloud_face[(size3DPt * i) + idZ] - centroid_z);
92 }
93
95 vpMatrix V;
96 vpMatrix J = M.t() * M;
97 J.svd(W, V);
98
99 double smallestSv = W[0];
100 unsigned int indexSmallestSv = 0;
101 unsigned int w_size = W.size();
102 for (unsigned int i = 1; i < w_size; ++i) {
103 if (W[i] < smallestSv) {
104 smallestSv = W[i];
105 indexSmallestSv = i;
106 }
107 }
108
109 normal = V.getCol(indexSmallestSv);
110
111 // Compute plane equation
112 double A = normal[idX];
113 double B = normal[idY];
114 double C = normal[idZ];
115 double D = -((A * centroid_x) + (B * centroid_y) + (C * centroid_z));
116
117 // Compute error points to estimated plane
118 prev_error = error;
119 error = 0.0;
120 for (unsigned int i = 0; i < nPoints; ++i) {
121 residues[i] = std::fabs((A * point_cloud_face[size3DPt * i]) + (B * point_cloud_face[(size3DPt * i) + idY]) +
122 (C * point_cloud_face[(size3DPt * i) + idZ]) + D) /
123 sqrt((A * A) + (B * B) + (C * C));
124 error += weights[i] * residues[i];
125 }
126 error /= total_w;
127 // evaluate one of the end conditions of the for
128 fabs_error_m_prev_error = std::fabs(error - prev_error);
129
130 ++iter;
131 }
132
133 // Update final weights
134 tukey.MEstimator(vpRobust::TUKEY, residues, weights);
135
136 // Update final centroid
137 centroid.resize(size3DPt, false);
138 double total_w = 0.0;
139
140 for (unsigned int i = 0; i < nPoints; ++i) {
141 centroid[idX] += weights[i] * point_cloud_face[size3DPt * i];
142 centroid[idY] += weights[i] * point_cloud_face[(size3DPt * i) + idY];
143 centroid[idZ] += weights[i] * point_cloud_face[(size3DPt * i) + idZ];
144 total_w += weights[i];
145 }
146
147 centroid[idX] /= total_w;
148 centroid[idY] /= total_w;
149 centroid[idZ] /= total_w;
150
151 // Compute final plane equation
152 double A = normal[0], B = normal[1], C = normal[idZ];
153 double D = -((A * centroid[0]) + (B * centroid[1]) + (C * centroid[idZ]));
154
155 // Update final plane equation
156 plane_equation_estimated.setABCD(A, B, C, D);
157
158 normalized_weights = total_w / nPoints;
159}
160
161bool vpPose::computePlanarObjectPoseFromRGBD(const vpImage<float> &depthMap, const std::vector<vpImagePoint> &corners,
162 const vpCameraParameters &colorIntrinsics,
163 const std::vector<vpPoint> &point3d, vpHomogeneousMatrix &cMo,
164 double *confidence_index)
165{
166 if (corners.size() != point3d.size()) {
168 "Cannot compute pose from RGBD, 3D (%d) and 2D (%d) data doesn't have the same size",
169 point3d.size(), corners.size()));
170 }
171 std::vector<vpPoint> pose_points;
172 if (confidence_index != nullptr) {
173 *confidence_index = 0.0;
174 }
175
176 size_t point3d_size = point3d.size();
177 for (size_t i = 0; i < point3d_size; ++i) {
178 pose_points.push_back(point3d[i]);
179 }
180
181 vpPolygon polygon(corners);
182 vpRect bb = polygon.getBoundingBox();
183 unsigned int top = static_cast<unsigned int>(std::max<int>(0, static_cast<int>(bb.getTop())));
184 unsigned int bottom =
185 static_cast<unsigned int>(std::min<int>(static_cast<int>(depthMap.getHeight()) - 1, static_cast<int>(bb.getBottom())));
186 unsigned int left = static_cast<unsigned int>(std::max<int>(0, static_cast<int>(bb.getLeft())));
187 unsigned int right =
188 static_cast<unsigned int>(std::min<int>(static_cast<int>(depthMap.getWidth()) - 1, static_cast<int>(bb.getRight())));
189
190 std::vector<double> points_3d;
191 points_3d.reserve((bottom - top) * (right - left));
192 for (unsigned int idx_i = top; idx_i < bottom; ++idx_i) {
193 for (unsigned int idx_j = left; idx_j < right; ++idx_j) {
194 vpImagePoint imPt(idx_i, idx_j);
195 if ((depthMap[idx_i][idx_j] > 0) && (polygon.isInside(imPt))) {
196 double x = 0, y = 0;
197 vpPixelMeterConversion::convertPoint(colorIntrinsics, imPt.get_u(), imPt.get_v(), x, y);
198 double Z = depthMap[idx_i][idx_j];
199 points_3d.push_back(x * Z);
200 points_3d.push_back(y * Z);
201 points_3d.push_back(Z);
202 }
203 }
204 }
205
206 unsigned int nb_points_3d = static_cast<unsigned int>(points_3d.size() / 3);
207
208 const unsigned int minNbPoints = 4;
209 if (nb_points_3d > minNbPoints) {
210 std::vector<vpPoint> p, q;
211
212 // Plane equation
213 vpPlane plane_equation;
214 vpColVector centroid;
215 double normalized_weights = 0;
216 estimatePlaneEquationSVD(points_3d, plane_equation, centroid, normalized_weights);
217
218 size_t corners_size = corners.size();
219 for (size_t j = 0; j < corners_size; ++j) {
220 const vpImagePoint &imPt = corners[j];
221 double x = 0, y = 0;
222 vpPixelMeterConversion::convertPoint(colorIntrinsics, imPt.get_u(), imPt.get_v(), x, y);
223 double Z = plane_equation.computeZ(x, y);
224 if (Z < 0) {
225 Z = -Z;
226 }
227 p.push_back(vpPoint(x * Z, y * Z, Z));
228
229 pose_points[j].set_x(x);
230 pose_points[j].set_y(y);
231 }
232
233 size_t point3d_size = point3d.size();
234 for (size_t i = 0; i < point3d_size; ++i) {
235 q.push_back(point3d[i]);
236 }
237
239
240 if (cMo.isValid()) {
241 vpPose pose;
242 pose.addPoints(pose_points);
243 if (pose.computePose(vpPose::VIRTUAL_VS, cMo)) {
244 if (confidence_index != nullptr) {
245 *confidence_index = std::min<double>(1.0, (normalized_weights * static_cast<double>(nb_points_3d)) / polygon.getArea());
246 }
247 return true;
248 }
249 }
250 }
251
252 return false;
253}
254
256 const std::vector<std::vector<vpImagePoint> > &corners,
257 const vpCameraParameters &colorIntrinsics,
258 const std::vector<std::vector<vpPoint> > &point3d,
259 vpHomogeneousMatrix &cMo, double *confidence_index, bool coplanar_points)
260{
261 const size_t nb3dPoints = point3d.size();
262 const size_t nbCorners = corners.size();
263 if (nbCorners != nb3dPoints) {
265 "Cannot compute pose from RGBD, 3D (%d) and 2D (%d) data doesn't have the same size",
266 nb3dPoints, nbCorners));
267 }
268 std::vector<vpPoint> pose_points;
269 if (confidence_index != nullptr) {
270 *confidence_index = 0.0;
271 }
272
273 for (size_t i = 0; i < nb3dPoints; ++i) {
274 std::vector<vpPoint> tagPoint3d = point3d[i];
275 size_t tagpoint3d_size = tagPoint3d.size();
276 for (size_t j = 0; j < tagpoint3d_size; ++j) {
277 pose_points.push_back(tagPoint3d[j]);
278 }
279 }
280
281 // Total area of the polygon to estimate confidence
282 double totalArea = 0.0;
283
284 // If coplanar is true, the tags_points_3d will be used to compute one plane
285 std::vector<double> tag_points_3d;
286
287 // Otherwise the vector of planes will be used to compute each plane for each vector
288 std::vector<std::vector<double> > tag_points_3d_nonplanar;
289 size_t nb_points_3d_non_planar = 0;
290
291 // Loop through each object, compute 3d point cloud of each
292 size_t corners_size = corners.size();
293 for (size_t i = 0; i < corners_size; ++i) {
294 std::vector<double> points_3d;
295 vpPolygon polygon(corners[i]);
296 vpRect bb = polygon.getBoundingBox();
297
298 // The area to calculate final confidence index should be total area of the tags
299 totalArea += polygon.getArea();
300
301 unsigned int top = static_cast<unsigned int>(std::max<int>(0, static_cast<int>(bb.getTop())));
302 unsigned int bottom = static_cast<unsigned int>(
303 std::min<int>(static_cast<int>(depthMap.getHeight()) - 1, static_cast<int>(bb.getBottom())));
304 unsigned int left = static_cast<unsigned int>(std::max<int>(0, static_cast<int>(bb.getLeft())));
305 unsigned int right =
306 static_cast<unsigned int>(std::min<int>(static_cast<int>(depthMap.getWidth()) - 1, static_cast<int>(bb.getRight())));
307
308 points_3d.reserve((bottom - top) * (right - left));
309 for (unsigned int idx_i = top; idx_i < bottom; ++idx_i) {
310 for (unsigned int idx_j = left; idx_j < right; ++idx_j) {
311 vpImagePoint imPt(idx_i, idx_j);
312 if ((depthMap[idx_i][idx_j] > 0) && (polygon.isInside(imPt))) {
313 double x = 0, y = 0;
314 vpPixelMeterConversion::convertPoint(colorIntrinsics, imPt.get_u(), imPt.get_v(), x, y);
315 double Z = depthMap[idx_i][idx_j];
316 points_3d.push_back(x * Z);
317 points_3d.push_back(y * Z);
318 points_3d.push_back(Z);
319 }
320 }
321 }
322
323 // If coplanar_points is true, feed all 3d points to single vector
324 // Otherwise, each vector will hold 3d points for separate planes
325 if (coplanar_points) {
326 tag_points_3d.insert(tag_points_3d.end(), points_3d.begin(), points_3d.end());
327 }
328 else {
329 tag_points_3d_nonplanar.push_back(points_3d);
330 nb_points_3d_non_planar += points_3d.size();
331 }
332 }
333
334 size_t nb_points_3d = 0;
335 const size_t sizePt3D = 3;
336
337 if (coplanar_points) {
338 nb_points_3d = tag_points_3d.size() / sizePt3D;
339 }
340 else {
341 nb_points_3d = nb_points_3d_non_planar / sizePt3D;
342 }
343
344 const size_t minNbPts = 4;
345 if (nb_points_3d > minNbPts) {
346 std::vector<vpPoint> p, q;
347
348 // Plane equation
349 vpPlane plane_equation;
350 vpColVector centroid;
351 double normalized_weights = 0;
352
353 if (coplanar_points) {
354 // If all objects are coplanar, use points insides tag_points_3d to estimate the plane
355 estimatePlaneEquationSVD(tag_points_3d, plane_equation, centroid, normalized_weights);
356 int count = 0;
357 for (size_t j = 0; j < nbCorners; ++j) {
358 std::vector<vpImagePoint> tag_corner = corners[j];
359 size_t tag_corner_size = tag_corner.size();
360 for (size_t i = 0; i < tag_corner_size; ++i) {
361 const vpImagePoint &imPt = tag_corner[i];
362 double x = 0, y = 0;
363 vpPixelMeterConversion::convertPoint(colorIntrinsics, imPt.get_u(), imPt.get_v(), x, y);
364 double Z = plane_equation.computeZ(x, y);
365 std::cout << Z;
366 if (Z < 0) {
367 Z = -Z;
368 }
369 p.push_back(vpPoint(x * Z, y * Z, Z));
370 pose_points[count].set_x(x);
371 pose_points[count].set_y(y);
372 ++count;
373 }
374 }
375 }
376 else {
377 // If the tags is not coplanar, estimate the plane for each tags
378 size_t count = 0;
379
380 size_t tag_points_3d_nonplanar_size = tag_points_3d_nonplanar.size();
381 for (size_t k = 0; k < tag_points_3d_nonplanar_size; ++k) {
382 std::vector<double> rec_points_3d = tag_points_3d_nonplanar[k];
383 double tag_normalized_weights = 0;
384 const size_t minNbPtsForPlaneSVD = 3;
385 const size_t minSizeForPlaneSVD = minNbPtsForPlaneSVD * sizePt3D;
386 if (rec_points_3d.size() >= minSizeForPlaneSVD) {
387 // The array must has at least 3 points for the function estimatePlaneEquationSVD not to crash
388 estimatePlaneEquationSVD(rec_points_3d, plane_equation, centroid, tag_normalized_weights);
389 normalized_weights += tag_normalized_weights;
390
391 // Get the 2d points of the tag the plane just recomputed
392 std::vector<vpImagePoint> tag_corner = corners[k];
393
394 size_t tag_corner_size = tag_corner.size();
395 for (size_t i = 0; i < tag_corner_size; ++i) {
396 const vpImagePoint &imPt = tag_corner[i];
397 double x = 0, y = 0;
398 vpPixelMeterConversion::convertPoint(colorIntrinsics, imPt.get_u(), imPt.get_v(), x, y);
399 double Z = plane_equation.computeZ(x, y);
400
401 if (Z < 0) {
402 Z = -Z;
403 }
404 p.push_back(vpPoint(x * Z, y * Z, Z));
405 pose_points[count].set_x(x);
406 pose_points[count].set_y(y);
407 ++count;
408 }
409 }
410 else {
411 // Sometimes an object may do not have enough points registered due to small size or bad alignment btw depth
412 // and rgb. This behavior happens with Orbbec camera while Realsenses was fine. To prevent exception while
413 // computePose, skip recomputing the failed estimation tag's (4 point - corners)
414 count += corners[k].size();
415 }
416 }
417 normalized_weights = normalized_weights / tag_points_3d_nonplanar.size();
418 }
419
420 for (size_t i = 0; i < nb3dPoints; ++i) {
421 std::vector<vpPoint> tagPoint3d = point3d[i];
422 // Sometimes an object may do not have enough points registered due to small size.
423 // The issue happens with Orbbec camera while Realsenses was fine.
424 // To prevent wrong estimation or exception (p and q sizes are differents),
425 // ignore the recomputer vector (tag_points_3d_nonplanar) when size = 0
426 if (coplanar_points) {
427 size_t tag_point3d_size = tagPoint3d.size();
428 for (size_t j = 0; j < tag_point3d_size; ++j) {
429 q.push_back(tagPoint3d[j]);
430 }
431 }
432 else {
433 if (tag_points_3d_nonplanar[i].size() > 0) {
434 size_t tag_point3d_size = tagPoint3d.size();
435 for (size_t j = 0; j < tag_point3d_size; ++j) {
436 q.push_back(tagPoint3d[j]);
437 }
438 }
439 }
440 }
441
442 // Due to the possibility of q's size might less than p's, check their size should be identical
443 if (p.size() == q.size()) {
445
446 if (cMo.isValid()) {
447 vpPose pose;
448 pose.addPoints(pose_points);
449 if (pose.computePose(vpPose::VIRTUAL_VS, cMo)) {
450 if (confidence_index != nullptr) {
451 *confidence_index = std::min<double>(1.0, (normalized_weights * static_cast<double>(nb_points_3d)) / totalArea);
452 }
453 return true;
454 }
455 }
456 }
457 }
458 return false;
459}
460
461END_VISP_NAMESPACE
unsigned int size() const
Return the number of elements of the 2D array.
Definition vpArray2D.h:435
Generic class defining intrinsic camera parameters.
Implementation of column vector and the associated operations.
void resize(unsigned int i, bool flagNullify=true)
error that can be emitted by ViSP classes.
Definition vpException.h:60
@ fatalError
Fatal error.
Definition vpException.h:72
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 ...
double get_u() const
double get_v() const
Definition of the vpImage class member functions.
Definition vpImage.h:131
unsigned int getWidth() const
Definition vpImage.h:242
unsigned int getHeight() const
Definition vpImage.h:181
Implementation of a matrix and operations on matrices.
Definition vpMatrix.h:175
void svd(vpColVector &w, vpMatrix &V)
vpColVector getCol(unsigned int j) const
Definition vpMatrix.cpp:560
vpMatrix t() const
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:56
double computeZ(double x, double y) const
Definition vpPlane.cpp:298
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:79
Defines a generic 2D polygon.
Definition vpPolygon.h:103
vpRect getBoundingBox() const
Definition vpPolygon.h:164
double getArea() const
Definition vpPolygon.h:148
bool isInside(const vpImagePoint &iP, const PointInPolygonMethod &method=PnPolyRayCasting) const
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)
@ VIRTUAL_VS
Definition vpPose.h:97
bool computePose(vpPoseMethodType method, vpHomogeneousMatrix &cMo, FuncCheckValidityPose func=nullptr)
Definition vpPose.cpp:385
void addPoints(const std::vector< vpPoint > &lP)
Definition vpPose.cpp:103
vpPose()
Definition vpPose.cpp:65
Defines a rectangle in the plane.
Definition vpRect.h:79
Contains an M-estimator and various influence function.
Definition vpRobust.h:84
@ TUKEY
Tukey influence function.
Definition vpRobust.h:89
void MEstimator(const vpRobustEstimatorType method, const vpColVector &residues, vpColVector &weights)
Definition vpRobust.cpp:130
void setMinMedianAbsoluteDeviation(double mad_min)
Definition vpRobust.h:136