FeatureExtractor.cpp
Go to the documentation of this file.
1#include "FeatureExtractor.h"
2
3#include <algorithm>
4#include <cmath>
5#include <exception>
6#include <iterator>
7#include <optional>
8#include <vector>
9
10#include <Eigen/Core>
11#include <Eigen/Geometry>
12
13#include <opencv2/core/types.hpp>
14#include <opencv2/imgproc.hpp>
15
16#include <VirtualRobot/MathTools.h>
17#include <VirtualRobot/VirtualRobot.h>
18#include <VirtualRobot/math/Helpers.h>
19
21
22#include <RobotAPI/interface/units/LaserScannerUnit.h>
24
29
30#include "Path.h"
31#include "conversions/opencv.h"
33
35{
36
37 // Features
38
39 std::vector<Ellipsoid>
40 Features::linesAsEllipsoids(const float axisLength) const
41 {
42 if (not chain)
43 {
44 return {};
45 }
46
47 const auto asEllipsoid = [&](const Path::Segment& segment) -> Ellipsoid
48 {
49 const Eigen::Vector2f center = (segment.first + segment.second) / 2;
50 const Eigen::Vector2f v = segment.second - center;
51 const float angle = math::Helpers::Angle(v);
52 const float r = v.norm();
53
54 Eigen::Isometry3f globalPose = Eigen::Isometry3f::Identity();
55 globalPose.translation().head<2>() = center;
56 globalPose.linear() =
57 Eigen::AngleAxisf(angle, Eigen::Vector3f::UnitZ()).toRotationMatrix();
58
59 return Ellipsoid{.pose = globalPose, .radii = Eigen::Vector2f{r, axisLength}};
60 };
61
62 const auto segments = Path{.points = *chain}.segments();
63
64 std::vector<Ellipsoid> ellipsoids;
65 std::transform(
66 segments.begin(), segments.end(), std::back_inserter(ellipsoids), asEllipsoid);
67
68 return ellipsoids;
69 }
70
71 // FeatureExtractor
72
74 const ChainApproximation::Params& chainApproximationParams) :
75 scanClusteringParams(scanClusteringParams),
76 chainApproximationParams(chainApproximationParams)
77 {
78 }
79
80 std::vector<Features>
82 {
83 ARMARX_DEBUG << "on data";
84 const auto clustersWithFeatures = features(data.data);
85
86 ARMARX_DEBUG << "callback";
87 return clustersWithFeatures;
88 }
89
90 std::vector<Features>
91 FeatureExtractor::features(const LaserScan& scan) const
92 {
93 const auto clusters = detectClusters(scan);
94
95 std::vector<Features> fs;
96 std::transform(clusters.begin(),
97 clusters.end(),
98 std::back_inserter(fs),
99 [&](const LaserScan& cluster)
100 {
101 const auto points = toCartesian<Eigen::Vector2f>(cluster);
102 const auto hull = convexHull(points);
103
104 return Features{
105 .convexHull = hull,
106 .circle = circle(points),
107 .ellipsoid = std::nullopt, //ellipsoid(hull),
108 .chain = chainApproximation(points, hull, chainApproximationParams),
109 .points = points};
110 });
111
112 return fs;
113 }
114
115 std::vector<LaserScan>
116 FeatureExtractor::detectClusters(const LaserScan& scan) const
117 {
118 ScanClustering clustering(scanClusteringParams);
119 return clustering.detectClusters(scan);
120 }
121
122 std::optional<VirtualRobot::MathTools::ConvexHull2D>
124 {
125
126 if (points.size() < 3) // no meaningful area
127 {
128 return std::nullopt;
129 }
130
131 try
132 {
133 return *VirtualRobot::MathTools::createConvexHull2D(points);
134 }
135 catch (std::exception& e)
136 {
137 ARMARX_WARNING << "Couldn't create convex hull: " << e.what();
138 }
139 return std::nullopt;
140 }
141
142 // Legacy OpenCV implementation. Didn't work as expected.
143 // Also, tries to fit an ellipsoid which is NOT the enclosing ellipsoid!
144
145 // std::optional<Ellipsoid> FeatureExtractor::ellipsoid(const PointCloud &pointCloud) const
146 // {
147 // // OpenCV::fitEllipse requirement
148 // // "There should be at least 5 points to fit the ellipse"
149 // // => Too few points will cause algorithmic issues
150 // if (pointCloud.size() < 100)
151 // {
152 // return std::nullopt;
153 // }
154
155 // const auto points2f = conversions::pcl2cv(pointCloud);
156 // const auto points2i = conversions::cast<cv::Point2i>(points2f);
157
158 // cv::RotatedRect rect = cv::fitEllipse(points2i);
159
160 // Eigen::Affine2f pose;
161 // pose.translation() = conversions::cv2eigen(rect.center);
162 // pose.linear() =
163 // Eigen::Rotation2Df(VirtualRobot::MathTools::deg2rad(rect.angle)).toRotationMatrix();
164
165 // return Ellipsoid{.axisLengths = conversions::cv2eigen(rect.size), .pose = pose};
166 // }
167
168 std::optional<Ellipsoid>
169 FeatureExtractor::ellipsoid(const std::optional<VirtualRobot::MathTools::ConvexHull2D>& hull)
170 {
171
172 if (not hull)
173 {
174 return std::nullopt;
175 }
176
177 // TODO(fabian.reister): it might result in multiple ellipsoids if hull->segments is considered
178
179 // If there are not enough points, don't even try to fit an ellipse ...
180 if (hull->vertices.size() < 5)
181 {
182 return std::nullopt;
183 }
184
185 EnclosingEllipsoid mvee(hull->vertices);
186 return mvee;
187 }
188
189 std::optional<Circle>
191 {
192 // Too few points will cause algorithmic issues
193 if (points.size() < 5)
194 {
195 return std::nullopt;
196 }
197
198 const auto points2f = conversions::eigen2cv(points);
199 const auto points2i = conversions::cast<cv::Point2i>(points2f);
200
201 cv::Point2f center;
202 float radius = NAN;
203 cv::minEnclosingCircle(points2i, center, radius);
204
205 return Circle{.center = conversions::cv2eigen(center), .radius = radius};
206 }
207
208 std::optional<FeatureExtractor::Points>
210 const Points& points,
211 const std::optional<VirtualRobot::MathTools::ConvexHull2D>& convexHull,
212 const ChainApproximation::Params& params)
213 {
214 if (not convexHull)
215 {
216 return std::nullopt;
217 }
218
219 ChainApproximation chApprx(points, params);
220 const auto result = chApprx.approximate();
221
222 if (result.condition !=
224 {
225 return std::nullopt;
226 }
227
228 return chApprx.approximatedChain();
229 }
230
231} // namespace armarx::navigation::components::laser_scanner_feature_extraction
ApproximationResult approximate()
detail::ChainApproximationParams Params
Minimum volume enclosing ellipsoid (MVEE) for a set of points.
static std::optional< Ellipsoid > ellipsoid(const std::optional< VirtualRobot::MathTools::ConvexHull2D > &hull)
static std::optional< VirtualRobot::MathTools::ConvexHull2D > convexHull(const Points &points)
static std::optional< Points > chainApproximation(const Points &points, const std::optional< VirtualRobot::MathTools::ConvexHull2D > &convexHull, const ChainApproximation::Params &params)
FeatureExtractor(const ScanClustering::Params &scanClusteringParams, const ChainApproximation::Params &chainApproximationParams)
std::vector< Features > onData(const armem::laser_scans::LaserScanStamped &data)
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
cv::Point2f eigen2cv(const Eigen::Vector2f &pt)
Eigen::Vector2f cv2eigen(const cv::Point2f &pt)
CvT cast(const auto &pt)
Definition opencv.h:33
double angle(const Point &a, const Point &b, const Point &c)
Definition point.hpp:109
std::pair< Eigen::Vector2f, Eigen::Vector2f > Segment
Definition Path.h:14