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