ManagedObstacle.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * @package armar6_skills::ArmarXObjects::DynamicObstacleManager
17 * @author Fabian PK ( fabian dot peller-konrad at kit dot edu )
18 * @date 2020
19 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
23#include "ManagedObstacle.h"
24
25// STD/STL
26#include <string>
27#include <vector>
28
29// Ice
30#include <Ice/Current.h>
31using namespace Ice;
32
33// OpenCV
34#include <opencv2/core.hpp>
35
36// Eigen
37#include <Eigen/Core>
38
39// ArmarX
40#include <opencv2/core/core_c.h>
41
43
44#include <RobotAPI/interface/core/PoseBase.h>
46
47using namespace Eigen;
48using namespace armarx;
49
50namespace armarx
51{
52
53 double
54 ManagedObstacle::calculateObstacleArea(const obstacledetection::Obstacle& o)
55 {
56 return M_PI * std::abs(o.axisLengthX) * std::abs(o.axisLengthY);
57 }
58
59 bool
60 ManagedObstacle::point_ellipsis_coverage(const Eigen::Vector2f e_origin,
61 const float e_rx,
62 const float e_ry,
63 const float e_yaw,
64 const Eigen::Vector2f point)
65 {
66 // See https://stackoverflow.com/questions/7946187/point-and-ellipse-rotated-position-test-algorithm
67 const float sin = std::sin(e_yaw);
68 const float cos = std::cos(e_yaw);
69
70 const float a = cos * (point(0) - e_origin(0)) + sin * (point(1) - e_origin(1));
71 const float b = sin * (point(0) - e_origin(0)) - cos * (point(1) - e_origin(1));
72
73 const float e_rx_sq = e_rx * e_rx;
74 const float e_ry_sq = e_ry * e_ry;
75
76 return a * a * e_ry_sq + b * b * e_rx_sq <= e_rx_sq * e_ry_sq;
77 }
78
79 float
80 ManagedObstacle::ellipsis_ellipsis_coverage(const Eigen::Vector2f e1_origin,
81 const float e1_rx,
82 const float e1_ry,
83 const float e1_yaw,
84 const Eigen::Vector2f e2_origin,
85 const float e2_rx,
86 const float e2_ry,
87 const float e2_yaw)
88 {
89 // We use a very simple approach here: We sample points in one ellipsis and check whether it lies in the other (https://www.quora.com/Is-it-possible-to-generate-random-points-within-a-given-rotated-ellipse-without-using-rejection-sampling)
90 // For a real approach to intersect two ellipsis see https://arxiv.org/pdf/1106.3787.pdf
91
92 unsigned int SAMPLES = 100;
93 unsigned int matches = 0;
94 for (unsigned int i = 0; i < SAMPLES; ++i)
95 {
96 float theta = static_cast<float>(rand()) / static_cast<float>(RAND_MAX / 2 * M_PI);
97 float k = sqrt(static_cast<float>(rand()) / static_cast<float>(RAND_MAX));
98 Eigen::Vector2f sample(k * e2_rx * cos(theta), k * e2_ry * sin(theta));
99 //sample *= Eigen::Rotation2D(e2_yaw);
100 sample += e1_origin;
101
102 if (ManagedObstacle::point_ellipsis_coverage(e1_origin, e1_rx, e1_ry, e1_yaw, sample))
103 {
104 matches++;
105 }
106 }
107
108 return (1.0f * matches) / SAMPLES;
109 }
110
111 float
113 const float e_rx,
114 const float e_ry,
115 const float e_yaw,
116 const Eigen::Vector2f line_seg_start,
117 const Eigen::Vector2f line_seg_end)
118 {
119 // Again we discretize the line into n points and we check the coverage of those points
120 const Eigen::Vector2f difference_vec = line_seg_end - line_seg_start;
121 const Eigen::Vector2f difference_vec_normed = difference_vec.normalized();
122 const float distance = difference_vec.norm();
123 const unsigned int SAMPLES = std::max(distance / 10.0, 40.0);
124
125 const Vector2f difference_start_origin = e_origin - line_seg_start;
126 const Vector2f difference_end_origin = e_origin - line_seg_end;
127
128 if (difference_start_origin.norm() > std::max(e_rx, e_ry) &&
129 distance < std::max(e_rx, e_ry))
130 {
131 return 0.0;
132 }
133
134 if (difference_end_origin.norm() > std::max(e_rx, e_ry) && distance < std::max(e_rx, e_ry))
135 {
136 return 0.0;
137 }
138
139
140 const float step_size = distance / SAMPLES;
141 const Eigen::Vector2f dir = difference_vec_normed * step_size;
142
143 unsigned int samples_in_ellipsis = 0;
144
145 // Sample over line segment with a fixed size
146 for (unsigned int i = 0; i < SAMPLES * 0.5; ++i)
147 {
148 Eigen::Vector2f start_sample = line_seg_start + i * dir;
149 Eigen::Vector2f end_sample = line_seg_end - i * dir;
150 unsigned int samples_in_ellipsis_this_iteration = 0;
151
152 for (const auto& point : {start_sample, end_sample})
153 {
154 if (ManagedObstacle::point_ellipsis_coverage(e_origin, e_rx, e_ry, e_yaw, point))
155 {
156 ++samples_in_ellipsis;
157 ++samples_in_ellipsis_this_iteration;
158 }
159 }
160
161 if (samples_in_ellipsis_this_iteration == 2)
162 {
163 // Last two points (from the outside) were inside this ellipsis, so the
164 // remaining ones are as well simce ellipsis are concave. Consider this line
165 // segment as explained by the obstacle and bail early.
166 const unsigned int remaining_samples = SAMPLES - 2 * (i + 1);
167 return (1.0f * samples_in_ellipsis + remaining_samples) / SAMPLES;
168 }
169 }
170 return (1.0f * samples_in_ellipsis) / SAMPLES; // only if one point or no point in ellipsis
171 }
172
173 void
174 ManagedObstacle::update_position(const unsigned int thickness)
175 {
176 if (line_matches.size() < 2)
177 {
178 return;
179 }
180
181 ARMARX_DEBUG << "Obstacle " << m_obstacle.name << " has " << line_matches.size()
182 << " matching line segments since last update";
183
184 std::vector<float> x_pos;
185 std::vector<float> y_pos;
186 for (Eigen::Vector2f& match : line_matches)
187 {
188 ARMARX_DEBUG << "Match: " << match;
189 x_pos.push_back(match(0));
190 y_pos.push_back(match(1));
191 }
192
193 cv::Mat x(x_pos.size(), 1, CV_32F, x_pos.data());
194 cv::Mat y(y_pos.size(), 1, CV_32F, y_pos.data());
195
196 ARMARX_DEBUG << "X: " << x;
197 ARMARX_DEBUG << "Y: " << y;
198
199 cv::Mat data(x_pos.size(), 2, CV_32F);
200 x.col(0).copyTo(data.col(0));
201 y.col(0).copyTo(data.col(1));
202
203 ARMARX_DEBUG << "Data: " << data;
204
205 cv::PCA pca(data, cv::Mat(), CV_PCA_DATA_AS_ROW, 2);
206
207 cv::Mat mean = pca.mean;
208 ARMARX_DEBUG << "Mean: " << mean;
209 cv::Mat eigenvalues = pca.eigenvalues;
210 ARMARX_DEBUG << "Eigenvalues: " << eigenvalues;
211 cv::Mat eigenvectors = pca.eigenvectors;
212 ARMARX_DEBUG << "Eigenvectors: " << eigenvectors;
213
214 Eigen::Vector2f pca_center(mean.at<float>(0, 0), mean.at<float>(0, 1));
215 Eigen::Vector2f pca1_direction(eigenvectors.at<float>(0, 0), eigenvectors.at<float>(0, 1));
216 Eigen::Vector2f pca2_direction(eigenvectors.at<float>(1, 0), eigenvectors.at<float>(1, 1));
217 double pca1_eigenvalue = eigenvalues.at<float>(0, 0);
218 double pca2_eigenvalue = eigenvalues.at<float>(1, 0);
219
220 ARMARX_DEBUG << "PCA: Mean: " << pca_center;
221 ARMARX_DEBUG << "PCA1: Eigenvector: " << pca1_direction
222 << ", Eigenvalue: " << pca1_eigenvalue;
223 ARMARX_DEBUG << "PCA2: Eigenvector: " << pca2_direction
224 << ", Eigenvalue: " << pca2_eigenvalue;
225
226 // calculate yaw of best line (should be close to current yaw)
227 const Eigen::Vector2f difference_vec = pca1_direction;
228 const Eigen::Vector2f center_vec = pca_center;
229 //const Eigen::Vector2f difference_vec1_normed = difference_vec.normalized();
230
231 const float yaw1 = getXAxisAngle(difference_vec);
232 const float yaw2 = getXAxisAngle(-1 * difference_vec);
233
234 const float diff_origin_yaw1 = std::abs(yaw1 - m_obstacle.yaw);
235 const float diff_origin_yaw2 = std::abs(yaw2 - m_obstacle.yaw);
236 const float diff_origin_yaw1_opposite = std::abs(2 * M_PI - diff_origin_yaw1);
237 const float diff_origin_yaw2_opposite = std::abs(2 * M_PI - diff_origin_yaw2);
238
239
240 float yaw = yaw2;
241 if ((diff_origin_yaw1 < diff_origin_yaw2 && diff_origin_yaw1 < diff_origin_yaw2_opposite) ||
242 (diff_origin_yaw1_opposite < diff_origin_yaw2 &&
243 diff_origin_yaw1_opposite < diff_origin_yaw2_opposite))
244 {
245 yaw = yaw1;
246 }
247
248 // Print matches to debug drawer
249 //const std::string debug_line_name = "line_segment_input_" + std::to_string(m_input_index++);
250 //const armarx::Vector3BasePtr start_vec_3d{new Vector3(best_start(0), best_start(1), 50)};
251 //const armarx::Vector3BasePtr end_vec_3d{new Vector3(best_end(0), best_end(1), 50)};
252 //const armarx::Vector3BasePtr center_vec_3d{new Vector3(center_vec(0), center_vec(1), 50)};
253 //const armarx::Vector3BasePtr difference_vec_normed_3d{new Vector3(difference_vec1_normed(0), difference_vec1_normed(1), 0)};
254 //debug_drawer->setCylinderVisu(layer_name, debug_line_name, center_vec_3d, difference_vec_normed_3d, max_length, 15.f, armarx::DrawColor{1, 0, 0, 0.8});
255 //debug_drawer->setSphereVisu(layer_name, debug_line_name + "_ref", start_vec_3d, armarx::DrawColor{1, 1, 0, 0.8}, 35);
256 //debug_drawer->setSphereVisu(layer_name, debug_line_name + "_ref2", end_vec_3d, armarx::DrawColor{0, 1, 0, 0.8}, 35);
257
258 // Udate position buffer with new best line
260 std::make_tuple(center_vec(0),
261 center_vec(1),
262 yaw,
263 sqrt(pca1_eigenvalue) * 1.3,
264 std::max(1.0f * thickness,
265 static_cast<float>(sqrt(pca2_eigenvalue)) *
266 1.3f)); // add 30% security margin to length of pca
268
271 std::min(position_buffer_fillctr, static_cast<unsigned int>(position_buffer.size()));
272
273 // Calculate means from position buffer
274 double sum_x_pos = 0;
275 double sum_y_pos = 0;
276 double sum_yaw = 0;
277 double sum_sin_yaw = 0;
278 double sum_cos_yaw = 0;
279 double sum_axisX = 0;
280 double sum_axisY = 0;
281
282 std::string yaws;
283 for (unsigned int i = 0; i < position_buffer_fillctr; ++i)
284 {
285 double current_x_pos = std::get<0>(position_buffer[i]);
286 double current_y_pos = std::get<1>(position_buffer[i]);
287 double current_yaw = std::get<2>(position_buffer[i]);
288 double current_axisX = std::get<3>(position_buffer[i]);
289 double current_axisY = std::get<4>(position_buffer[i]);
290 sum_x_pos += current_x_pos;
291 sum_y_pos += current_y_pos;
292 sum_yaw += current_yaw;
293 sum_sin_yaw += sin(current_yaw);
294 sum_cos_yaw += cos(current_yaw);
295 sum_axisX += current_axisX;
296 sum_axisY += current_axisY;
297
298 yaws += std::to_string(current_yaw) + ", ";
299 }
300
301 double mean_x_pos = sum_x_pos / position_buffer_fillctr;
302 double mean_y_pos = sum_y_pos / position_buffer_fillctr;
303 double mean_yaw = atan2(
304 (1.0 / position_buffer_fillctr) * sum_sin_yaw,
306 sum_cos_yaw); //sum_yaw / position_buffer_fillctr; //std::fmod(sum_yaw, 2.0 * M_PI);
307 double mean_axisX = sum_axisX / position_buffer_fillctr;
308 double mean_axisY = sum_axisY / position_buffer_fillctr;
309 mean_yaw = normalizeYaw(mean_yaw);
310
311 // Update position and size of obstacle
312 m_obstacle.posX = mean_x_pos;
313 m_obstacle.posY = mean_y_pos;
314 m_obstacle.refPosX = mean_x_pos;
315 m_obstacle.refPosY = mean_y_pos;
316 m_obstacle.yaw = mean_yaw;
317 //if (mean_axisX > m_obstacle.axisLengthX)
318 //{
319 m_obstacle.axisLengthX = mean_axisX;
320 //}
321 //if (mean_axisY > m_obstacle.axisLengthY)
322 //{
323 m_obstacle.axisLengthY = mean_axisY;
324 //}
325
326 line_matches.clear();
327 m_updated = true;
328 }
329
330 float
331 ManagedObstacle::normalizeYaw(float yaw) const
332 {
333 float pi2 = 2.0 * M_PI;
334 while (yaw < 0)
335 {
336 yaw += pi2;
337 }
338 while (yaw > pi2)
339 {
340 yaw -= pi2;
341 }
342 return yaw;
343 }
344
345 float
346 ManagedObstacle::getAngleBetweenVectors(const Eigen::Vector2f& v1,
347 const Eigen::Vector2f& v2) const
348 {
349 // Returns an angle between 0 and 180 degree (the minimum angle between the two vectors)
350 const Eigen::Vector2f v1_vec_normed = v1.normalized();
351 const Eigen::Vector2f v2_vec_normed = v2.normalized();
352 const float dot_product_vec =
353 v1_vec_normed(0) * v2_vec_normed(0) + v1_vec_normed(1) * v2_vec_normed(1);
354 float yaw = acos(dot_product_vec);
355 return yaw;
356 }
357
358 float
359 ManagedObstacle::getXAxisAngle(const Eigen::Vector2f& v) const
360 {
361 // Returns an angle between 0 and 360 degree (counterclockwise from x axis)
362 const Eigen::Vector2f v_vec_normed = v.normalized();
363 const float dot_product_vec = v_vec_normed(0);
364 const float cross_product_vec = v_vec_normed(1);
365 float yaw = acos(dot_product_vec);
366 if (cross_product_vec < 0)
367 {
368 yaw = 2 * M_PI - yaw;
369 }
370 return yaw;
371 }
372} // namespace armarx
#define M_PI
Definition MathTools.h:17
unsigned int position_buffer_index
static double calculateObstacleArea(const obstacledetection::Obstacle &o)
std::array< std::tuple< double, double, double, double, double >, 6 > position_buffer
static float line_segment_ellipsis_coverage(const Eigen::Vector2f e_origin, const float e_rx, const float e_ry, const float e_yaw, const Eigen::Vector2f line_seg_start, const Eigen::Vector2f line_seg_end)
unsigned int position_buffer_fillctr
static bool point_ellipsis_coverage(const Eigen::Vector2f e_origin, const float e_rx, const float e_ry, const float e_yaw, const Eigen::Vector2f point)
static float ellipsis_ellipsis_coverage(const Eigen::Vector2f e1_origin, const float e1_rx, const float e1_ry, const float e1_yaw, const Eigen::Vector2f e2_origin, const float e2_rx, const float e2_ry, const float e2_yaw)
std::vector< Eigen::Vector2f > line_matches
obstacledetection::Obstacle m_obstacle
void update_position(const unsigned int)
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
double v(double t, double v0, double a0, double j)
Definition CtrlUtil.h:39
This file offers overloads of toIce() and fromIce() functions for STL container types.
std::optional< float > mean(const boost::circular_buffer< NameValueMap > &buffer, const std::string &key)
double distance(const Point &a, const Point &b)
Definition point.hpp:95