1 /****************************************************************************
2  *
3  * ViSP, open source Visual Servoing Platform software.
4  * Copyright (C) 2005 - 2019 by Inria. All rights reserved.
5  *
6  * This software is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  * See the file LICENSE.txt at the root directory of this source
11  * distribution for additional information about the GNU GPL.
12  *
13  * For using ViSP with software that can not be combined with the GNU
14  * GPL, please contact Inria about acquiring a ViSP Professional
15  * Edition License.
16  *
17  * See http://visp.inria.fr for more information.
18  *
19  * This software was developed at:
20  * Inria Rennes - Bretagne Atlantique
21  * Campus Universitaire de Beaulieu
22  * 35042 Rennes Cedex
23  * France
24  *
25  * If you have questions regarding the use of this file, please contact
26  * Inria at visp@inria.fr
27  *
28  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
29  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
30  *
31  * Description:
32  * Visual feature circle.
33  *
34  * Authors:
35  * Eric Marchand
36  *
37  *****************************************************************************/
38 
39 #include <visp3/core/vpCircle.h>
40 
41 #include <visp3/core/vpFeatureDisplay.h>
42 
init()43 void vpCircle::init()
44 {
45   oP.resize(7);
46   cP.resize(7);
47 
48   p.resize(5);
49 }
50 
51 /*!
52   Set the parameters of the 3D circle in the object frame.
53 
54   \param oP_ : This 7-dim vector defines the parameters oP[0], oP[1], oP[2] corresponding
55   to parameters oA, oB, oC of the plane with equation oA*(x-oX)+oB*(y-oY)+oC*(z-oZ)=0
56   passing through the 3D sphere center.  oP[3], oP[4], oP[5] correspond to oX, oY, oZ the
57   coordinates of the center of the sphere. oP[6] corresponds to the radius of
58   the sphere.
59 */
setWorldCoordinates(const vpColVector & oP_)60 void vpCircle::setWorldCoordinates(const vpColVector &oP_) { this->oP = oP_; }
61 
62 /*!
63   Set the 3D circle coordinates in the object frame.
64 
65   \param oA, oB, oC : Parameters of the plane with equation oA*(x-oX)+oB*(y-oY)+oC*(z-oZ)=0
66   passing through the 3D sphere center.
67   \param oX : Coordinate of the center of the sphere along X-axis in the object frame.
68   \param oY : Coordinate of the center of the sphere along Y-axis in the object frame.
69   \param oZ : Coordinate of the center of the sphere along Z-axis in the object frame.
70   \param R : Radius of the sphere.
71 */
setWorldCoordinates(double oA,double oB,double oC,double oX,double oY,double oZ,double R)72 void vpCircle::setWorldCoordinates(double oA, double oB, double oC, double oX, double oY, double oZ, double R)
73 {
74   oP[0] = oA;
75   oP[1] = oB;
76   oP[2] = oC;
77   oP[3] = oX;
78   oP[4] = oY;
79   oP[5] = oZ;
80   oP[6] = R;
81 }
82 
83 /*!
84  * Default constructor that initialize internal vectors.
85  */
vpCircle()86 vpCircle::vpCircle() { init(); }
87 
88 /*!
89   Construct the circle from the intersection of a plane and a sphere.
90 
91   \param oP_ : This 7-dim vector defines the parameters oP[0], oP[1], oP[2] corresponding
92   to parameters oA, oB, oC of the plane with equation oA*(x-oX)+oB*(y-oY)+oC*(z-oZ)=0
93   passing through the 3D sphere center.  oP[3], oP[4], oP[5] correspond to oX, oY, oZ the
94   coordinates of the center of the sphere. oP[6] corresponds to the radius of
95   the sphere.
96 
97   \sa setWorldCoordinates()
98 */
vpCircle(const vpColVector & oP_)99 vpCircle::vpCircle(const vpColVector &oP_)
100 {
101   init();
102   setWorldCoordinates(oP_);
103 }
104 
105 /*!
106   Construct the 3D circle from the intersection of a plane and a sphere with
107   coordinates expressed in the object frame.
108 
109   \param oA, oB, oC : Parameters of the plane with equation oA*(x-oX)+oB*(y-oY)+oC*(z-oZ)=0
110   passing through the 3D sphere center.
111   \param oX : Coordinate of the center of the sphere along X-axis in the object frame.
112   \param oY : Coordinate of the center of the sphere along Y-axis in the object frame.
113   \param oZ : Coordinate of the center of the sphere along Z-axis in the object frame.
114   \param R : Radius of the sphere.
115 
116   \sa setWorldCoordinates()
117 */
vpCircle(double oA,double oB,double oC,double oX,double oY,double oZ,double R)118 vpCircle::vpCircle(double oA, double oB, double oC, double oX, double oY, double oZ, double R)
119 {
120   init();
121   setWorldCoordinates(oA, oB, oC, oX, oY, oZ, R);
122 }
123 
124 /*!
125  * Default destructor that does nothing.
126  */
~vpCircle()127 vpCircle::~vpCircle() {}
128 
129 /*!
130   Perspective projection of the circle.
131 
132   From the 3D parameters of the circle in the camera frame available in cP,
133   computes the 2D parameters of the ellipse resulting from the perspective
134   projection in the image plane. Those 2D parameters are available in p
135   vector.
136 
137   See vpCircle::projection(const vpColVector &, vpColVector &) const for a more
138   detailed description of the parameters.
139   */
projection()140 void vpCircle::projection() { projection(cP, p); }
141 
142 /*!
143   Perspective projection of the circle.
144   \param cP_: 3D cercle input parameters expressed in the camera frame.
145   This 7-dim vector contains the following parameters: cA, cB, cC, cX, cY, cZ, R where
146   - cA, cB, cC are the parameters of the plane with equation cA*(x-cX)+cB*(y-cY)+cC*(z-cZ)=0
147   passing through the 3D sphere center.
148   - cX, cY, cZ are the 3D coordinates of the circle in the camera frame
149   - R is the circle radius in [m].
150 
151   \param p_: 2D circle output parameters. This is a 5 dimension vector. It
152   contains the following parameters: x, y, n20, n11, n02 where:
153   - x, y are the normalized coordinates of the ellipse centroid (ie the
154   perspective projection of a 3D circle becomes a 2D ellipse in the image) in
155   the image plane.
156   - n20, n11, n02 which are the second order centered moments of
157   the ellipse normalized by its area (i.e., such that \f$n_{ij} = \mu_{ij}/a\f$ where
158   \f$\mu_{ij}\f$ are the centered moments and a the area).
159   */
projection(const vpColVector & cP_,vpColVector & p_) const160 void vpCircle::projection(const vpColVector &cP_, vpColVector &p_) const
161 {
162   p_.resize(5, false);
163 
164   vpColVector K(6);
165   {
166     double A = cP_[0];
167     double B = cP_[1];
168     double C = cP_[2];
169 
170     double X0 = cP_[3];
171     double Y0 = cP_[4];
172     double Z0 = cP_[5];
173 
174     double r = cP_[6];
175 
176     // projection
177     double s = X0 * X0 + Y0 * Y0 + Z0 * Z0 - r * r;
178     double det = A * X0 + B * Y0 + C * Z0;
179     A = A / det;
180     B = B / det;
181     C = C / det;
182 
183     K[0] = 1 - 2 * A * X0 + A * A * s;
184     K[1] = 1 - 2 * B * Y0 + B * B * s;
185     K[2] = -A * Y0 - B * X0 + A * B * s;
186     K[3] = -C * X0 - A * Z0 + A * C * s;
187     K[4] = -C * Y0 - B * Z0 + B * C * s;
188     K[5] = 1 - 2 * C * Z0 + C * C * s;
189   }
190 
191   double det = K[2] * K[2] - K[0] * K[1];
192   if (fabs(det) < 1e-8) {
193     throw(vpException(vpException::divideByZeroError, "division par 0"));
194   }
195 
196   double xc = (K[1] * K[3] - K[2] * K[4]) / det;
197   double yc = (K[0] * K[4] - K[2] * K[3]) / det;
198 
199   double c = sqrt((K[0] - K[1]) * (K[0] - K[1]) + 4 * K[2] * K[2]);
200   double s = 2 * (K[0] * xc * xc + 2 * K[2] * xc * yc + K[1] * yc * yc - K[5]);
201 
202   double A, B, E;
203 
204   if (fabs(K[2]) < std::numeric_limits<double>::epsilon()) {
205     E = 0.0;
206     if (K[0] > K[1]) {
207       A = sqrt(s / (K[0] + K[1] + c));
208       B = sqrt(s / (K[0] + K[1] - c));
209     } else {
210       A = sqrt(s / (K[0] + K[1] - c));
211       B = sqrt(s / (K[0] + K[1] + c));
212     }
213   } else {
214     E = (K[1] - K[0] + c) / (2 * K[2]);
215     if (fabs(E) > 1.0) {
216       A = sqrt(s / (K[0] + K[1] + c));
217       B = sqrt(s / (K[0] + K[1] - c));
218     } else {
219       A = sqrt(s / (K[0] + K[1] - c));
220       B = sqrt(s / (K[0] + K[1] + c));
221       E = -1.0 / E;
222     }
223   }
224 
225   // Chaumette PhD Thesis 1990, eq 2.72 divided by 4 since n_ij = mu_ij_chaumette_thesis / 4
226   det = 4 * (1.0 + vpMath::sqr(E));
227   double n20 = (vpMath::sqr(A) + vpMath::sqr(B * E)) / det;
228   double n11 = (vpMath::sqr(A) - vpMath::sqr(B)) * E / det;
229   double n02 = (vpMath::sqr(B) + vpMath::sqr(A * E)) / det;
230 
231   p_[0] = xc;
232   p_[1] = yc;
233   p_[2] = n20;
234   p_[3] = n11;
235   p_[4] = n02;
236 }
237 
238 /*!
239   From the 3D coordinates of the circle in the object frame oP that are set using for
240   example vpCircle(double oA, double oB, double oC, double oX, double oY, double oZ, double R)
241   or setWorldCoordinates(), compute the 3D coordinates of the circle in the camera frame cP = cMo * oP.
242 
243   \param cMo : Transformation from camera to object frame.
244   \param cP_ : 3D normalized coordinates of the circle in the camera frame cP = (cA, cB, cC, cX, cY, cZ, R).
changeFrame(const vpHomogeneousMatrix & cMo,vpColVector & cP_) const245 */void vpCircle::changeFrame(const vpHomogeneousMatrix &cMo, vpColVector &cP_) const
246 {
247   cP_.resize(7, false);
248 
249   double A, B, C;
250   A = cMo[0][0] * oP[0] + cMo[0][1] * oP[1] + cMo[0][2] * oP[2];
251   B = cMo[1][0] * oP[0] + cMo[1][1] * oP[1] + cMo[1][2] * oP[2];
252   C = cMo[2][0] * oP[0] + cMo[2][1] * oP[1] + cMo[2][2] * oP[2];
253 
254   double X0, Y0, Z0;
255   X0 = cMo[0][3] + cMo[0][0] * oP[3] + cMo[0][1] * oP[4] + cMo[0][2] * oP[5];
256   Y0 = cMo[1][3] + cMo[1][0] * oP[3] + cMo[1][1] * oP[4] + cMo[1][2] * oP[5];
257   Z0 = cMo[2][3] + cMo[2][0] * oP[3] + cMo[2][1] * oP[4] + cMo[2][2] * oP[5];
258   double R = oP[6];
259 
260   cP_[0] = A;
261   cP_[1] = B;
262   cP_[2] = C;
263 
264   cP_[3] = X0;
265   cP_[4] = Y0;
266   cP_[5] = Z0;
267 
268   cP_[6] = R;
269 }
270 
271 /*!
272  * Perspective projection of the circle.
273  * Internal circle parameters are modified in cP.
274  *
275  * \param cMo : Homogeneous transformation from camera frame to object frame.
276  */
changeFrame(const vpHomogeneousMatrix & cMo)277 void vpCircle::changeFrame(const vpHomogeneousMatrix &cMo)
278 {
279   double A, B, C;
280   A = cMo[0][0] * oP[0] + cMo[0][1] * oP[1] + cMo[0][2] * oP[2];
281   B = cMo[1][0] * oP[0] + cMo[1][1] * oP[1] + cMo[1][2] * oP[2];
282   C = cMo[2][0] * oP[0] + cMo[2][1] * oP[1] + cMo[2][2] * oP[2];
283 
284   double X0, Y0, Z0;
285   X0 = cMo[0][3] + cMo[0][0] * oP[3] + cMo[0][1] * oP[4] + cMo[0][2] * oP[5];
286   Y0 = cMo[1][3] + cMo[1][0] * oP[3] + cMo[1][1] * oP[4] + cMo[1][2] * oP[5];
287   Z0 = cMo[2][3] + cMo[2][0] * oP[3] + cMo[2][1] * oP[4] + cMo[2][2] * oP[5];
288   double R = oP[6];
289 
290   cP[0] = A;
291   cP[1] = B;
292   cP[2] = C;
293 
294   cP[3] = X0;
295   cP[4] = Y0;
296   cP[5] = Z0;
297 
298   cP[6] = R;
299 }
300 
301 /*!
302  * Display the projection of a 3D circle in image \e I.
303  *
304  * \param I : Image used as background.
305  * \param cam : Camera parameters.
306  * \param color : Color used to draw the circle.
307  * \param thickness : Thickness used to draw the circle.
308  */
display(const vpImage<unsigned char> & I,const vpCameraParameters & cam,const vpColor & color,unsigned int thickness)309 void vpCircle::display(const vpImage<unsigned char> &I, const vpCameraParameters &cam, const vpColor &color,
310                        unsigned int thickness)
311 {
312   vpFeatureDisplay::displayEllipse(p[0], p[1], p[2], p[3], p[4], cam, I, color, thickness);
313 }
314 
315 /*!
316  * Display the projection of a sphere in image \e I.
317  * This method is non destructive wrt. cP and p internal circle parameters.
318  *
319  * \param I : Image used as background.
320  * \param cMo : Homogeneous transformation from camera frame to object frame.
321  * The circle is considered as viewed from this camera position.
322  * \param cam : Camera parameters.
323  * \param color : Color used to draw the sphere.
324  * \param thickness : Thickness used to draw the circle.
325  */
display(const vpImage<unsigned char> & I,const vpHomogeneousMatrix & cMo,const vpCameraParameters & cam,const vpColor & color,unsigned int thickness)326 void vpCircle::display(const vpImage<unsigned char> &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam,
327                        const vpColor &color, unsigned int thickness)
328 {
329   vpColVector _cP, _p;
330   changeFrame(cMo, _cP);
331   projection(_cP, _p);
332   vpFeatureDisplay::displayEllipse(_p[0], _p[1], _p[2], _p[3], _p[4], cam, I, color, thickness);
333 }
334 //! for memory issue (used by the vpServo class only)
duplicate() const335 vpCircle *vpCircle::duplicate() const
336 {
337   vpCircle *feature = new vpCircle(*this);
338   return feature;
339 }
340 
341 /*!
342   Computes the coordinates of the point corresponding to the intersection
343   between a circle and a line.
344 
345   \warning This functions assumes changeFrame() and projection() have already
346   been called.
347 
348   \sa changeFrame(), projection()
349 
350   \param circle : Circle to consider for the intersection.
351   \param cam : Camera parameters that have to be used for the intersection computation.
352   \param rho : The rho parameter of the line.
353   \param theta : The theta parameter of the line.
354   \param i : resulting i-coordinate of the intersection point.
355   \param j : resulting j-coordinate of the intersection
356   point.
357 */
computeIntersectionPoint(const vpCircle & circle,const vpCameraParameters & cam,const double & rho,const double & theta,double & i,double & j)358 void vpCircle::computeIntersectionPoint(const vpCircle &circle, const vpCameraParameters &cam, const double &rho,
359                                         const double &theta, double &i, double &j)
360 {
361   // This was taken from the code of art-v1. (from the artCylinder class)
362   double px = cam.get_px();
363   double py = cam.get_py();
364   double u0 = cam.get_u0();
365   double v0 = cam.get_v0();
366 
367   double n11 = circle.p[3];
368   double n02 = circle.p[4];
369   double n20 = circle.p[2];
370   double Xg = u0 + circle.p[0] * px;
371   double Yg = v0 + circle.p[1] * py;
372 
373   // Find Intersection between line and ellipse in the image.
374 
375   // Optimised calculation for X
376   double stheta = sin(theta);
377   double ctheta = cos(theta);
378   double sctheta = stheta * ctheta;
379   double m11yg = n11 * Yg;
380   double ctheta2 = vpMath::sqr(ctheta);
381   double m02xg = n02 * Xg;
382   double m11stheta = n11 * stheta;
383   j = ((n11 * Xg * sctheta - n20 * Yg * sctheta + n20 * rho * ctheta - m11yg + m11yg * ctheta2 + m02xg -
384         m02xg * ctheta2 + m11stheta * rho) /
385        (n20 * ctheta2 + 2.0 * m11stheta * ctheta + n02 - n02 * ctheta2));
386   // Optimised calculation for Y
387   double rhom02 = rho * n02;
388   double sctheta2 = stheta * ctheta2;
389   double ctheta3 = ctheta2 * ctheta;
390   i = (-(-rho * n11 * stheta * ctheta - rhom02 + rhom02 * ctheta2 + n11 * Xg * sctheta2 - n20 * Yg * sctheta2 -
391          ctheta * n11 * Yg + ctheta3 * n11 * Yg + ctheta * n02 * Xg - ctheta3 * n02 * Xg) /
392        (n20 * ctheta2 + 2.0 * n11 * stheta * ctheta + n02 - n02 * ctheta2) / stheta);
393 }
394