HumanTracker.cpp
Go to the documentation of this file.
1#include "HumanTracker.h"
2
3#include <algorithm>
4#include <cmath>
5#include <map>
6#include <string>
7#include <vector>
8
9#include <boost/geometry.hpp> // IWYU pragma: keep
10#include <boost/geometry/algorithms/assign.hpp>
11#include <boost/geometry/algorithms/centroid.hpp>
12#include <boost/geometry/geometries/point_xy.hpp>
13#include <boost/geometry/geometries/polygon.hpp>
14
18
23
25
26#include <range/v3/range/conversion.hpp>
27#include <range/v3/view/transform.hpp>
28
30{
31
32 void
34 {
35 prepareTrackedHumans(measurements.detectionTime);
36
37 // calculate the poses according to the new received measurements
38 std::vector<DetectedHuman> newPoses =
39 measurements.humanPoses |
40 ranges::views::transform(
41 [measurements, this](const armem::human::HumanPose& humanPose) -> DetectedHuman
42 {
43 return convertHumanPoseToDetectedHuman(measurements.detectionTime, humanPose);
44 }) |
45 ranges::to_vector;
46
47 // associate the new poses from the measurement with the tracked humans
48 associateHumans(newPoses);
49
50 // add all not associated poses as new tracked humans
51 for (const auto& detectedHuman : newPoses)
52 {
53 if (not detectedHuman.associated)
54 {
55 //add new tracked human to list of tracked humans
56 trackedHumans.push_back(
57 TrackedHuman{.humanFilter = HumanFilter{detectedHuman.pose,
58 detectedHuman.detectionTime,
59 parameters.useKalmanFilter},
60 .trackingId = detectedHuman.trackingId,
61 .associatedCluster = nullptr,
62 .associated = true,
63 .confidence = parameters.confidenceCamera});
64 }
65 }
66 }
67
68 std::vector<Cluster>
70 {
71
72 prepareTrackedHumans(measurements.detectionTime);
73
74 // only look at the clusters that have a size reasonable enough to contain at most two
75 // footprints
76 std::vector<AdvancedCluster> potentialFootprints;
77 std::vector<Cluster> unusedClusters;
78 for (const auto& cluster : measurements.clusters)
79 {
80 if(cluster.convexHull.empty())
81 {
82 ARMARX_VERBOSE << "Skipping cluster because convex hull is not available.";
83 continue;
84 }
85
86 if (getClusterSize(cluster) <= 2 * parameters.maxFootprintSize)
87 {
88 potentialFootprints.push_back(AdvancedCluster{
89 .center = getClusterCenter(cluster), .cluster = cluster, .associated = false});
90 }
91 else
92 {
93 unusedClusters.push_back(cluster);
94 }
95 }
96
97 // associate humans and clusters by their distances
98 const auto sortedDistances = getSortedDistances(trackedHumans, potentialFootprints);
99
100 for (auto& posDistance : sortedDistances)
101 {
102 if (posDistance.distance > parameters.maxAssociationDistance)
103 {
104 break;
105 }
106 if (posDistance.oldHuman->associated || posDistance.newCluster->associated)
107 {
108 continue;
109 }
110 // no footprint was found before, so associate first footprint
111 if (!posDistance.oldHuman->associatedCluster)
112 {
113 posDistance.oldHuman->associatedCluster = posDistance.newCluster;
114 posDistance.newCluster->associated = true;
115 }
116 // found both footprints, so associate both footprints and update human with new pose
117 else
118 {
119 //calculate center between both footprints
120 auto& footprint1 = posDistance.oldHuman->associatedCluster->cluster;
121 auto& footprint2 = posDistance.newCluster->cluster;
122
123 //create pose with orientation from old human
124 core::Pose2D pose = core::Pose2D::Identity();
125 pose.translation() =
126 (getClusterCenter(footprint1) + getClusterCenter(footprint2)) / 2;
127 pose.linear() = posDistance.oldHuman->humanFilter.get().pose.linear();
128
129
130 posDistance.oldHuman->humanFilter.update(pose, measurements.detectionTime);
131
132 posDistance.oldHuman->associatedCluster = nullptr;
133 posDistance.oldHuman->associated = true;
134 posDistance.newCluster->associated = true;
135 posDistance.oldHuman->confidence += parameters.confidenceLaser;
136 }
137 }
138
139 //associate humans where only one footprint was found
140 for (auto& human : trackedHumans)
141 {
142 if (human.associated)
143 {
144 continue;
145 }
146
147 if (human.associatedCluster)
148 {
149 auto& footprint = human.associatedCluster;
150 //create pose with orientation from old human
151 core::Pose2D pose = core::Pose2D::Identity();
152 pose.translation() = footprint->center;
153 pose.linear() = human.humanFilter.get().pose.linear();
154
155 human.humanFilter.update(pose, measurements.detectionTime);
156
157 human.associatedCluster = nullptr;
158 human.associated = true;
159 human.confidence += parameters.confidenceLaser;
160 }
161 }
162
163 // add unused clusters to return list
164 for (auto& cluster : potentialFootprints)
165 {
166 if (!cluster.associated)
167 {
168 unusedClusters.push_back(cluster.cluster);
169 }
170 }
171 return unusedClusters;
172 }
173
174 std::vector<human::Human>
176 {
177 return trackedHumans |
178 ranges::views::transform([](const TrackedHuman& h) -> human::Human
179 { return h.humanFilter.get(); }) |
180 ranges::to_vector;
181 }
182
183 void
185 {
186 trackedHumans.clear();
187 }
188
190 HumanTracker::convertHumanPoseToDetectedHuman(const DateTime& time,
191 const armem::human::HumanPose& humanPose)
192 {
193 const std::map<std::string, armem::human::PoseKeypoint>& keypoints = humanPose.keypoints;
194 ARMARX_CHECK_NOT_EMPTY(keypoints);
195
196 // calculate the arithmetic mean of all keypoint positions
197 Eigen::Vector3f centerPos = Eigen::Vector3f::Zero();
198 int size = 0;
199 for (const auto& [_, v] : keypoints)
200 {
201 if (v.positionGlobal.has_value())
202 {
203 centerPos += v.positionGlobal.value().toEigen();
204 size++;
205 }
206 }
207 centerPos /= size;
208
209 // calculate the yaw of the specified keypoint representing the orientation if it exists
210 double yaw = 0;
211 auto head = humanPose.keypoints.find(parameters.rotationKeypoint);
212 if (head != humanPose.keypoints.end())
213 {
214 const armem::human::PoseKeypoint& refKeypoint = head->second;
215 ARMARX_CHECK(refKeypoint.orientationGlobal.has_value());
216 Eigen::Quaternionf qhead = refKeypoint.orientationGlobal->toEigenQuaternion();
217 Eigen::Vector3f vec(1, 0, 0);
218 Eigen::Vector3f rot3 = qhead._transformVector(vec);
219 Eigen::Vector2f rot2(rot3.x(), rot3.y());
220 if (rot2.norm() != 0)
221 {
222 // calculate angle of rot2
223 yaw = atan2(rot2.y(), rot2.x());
224 }
225 }
226
227 // create the new pose with the calculated position and yaw
228 core::Pose2D pose = core::Pose2D::Identity();
229 pose.translation() = conv::to2D(centerPos);
230 pose.linear() = Eigen::Rotation2Df(yaw).toRotationMatrix();
231
232 return {pose, humanPose.humanTrackingId, time, false};
233 }
234
235 std::vector<HumanTracker::PosHumanDistance>
236 HumanTracker::getSortedDistances(std::vector<HumanTracker::TrackedHuman>& oldHumans,
237 std::vector<HumanTracker::DetectedHuman>& newHumans)
238 {
239 std::vector<PosHumanDistance> posDistances;
240
241 for (auto& oldHuman : oldHumans)
242 {
243 if (oldHuman.associated)
244 {
245 continue;
246 }
247 for (auto& newHuman : newHumans)
248 {
249 if (newHuman.associated)
250 {
251 continue;
252 }
253 // calculate distance between every possible combination of tracked and detected
254 // humans where none of them was associated
255 posDistances.push_back(
256 {&oldHuman,
257 &newHuman,
258 (newHuman.pose.translation() - oldHuman.humanFilter.get().pose.translation())
259 .norm()});
260 }
261 }
262
263 // sort the distances ascending by their numeric value
264 std::sort(posDistances.begin(),
265 posDistances.end(),
266 [](const PosHumanDistance& a, const PosHumanDistance& b) -> bool
267 { return a.distance < b.distance; });
268
269 return posDistances;
270 }
271
272 std::vector<HumanTracker::PosLaserDistance>
273 HumanTracker::getSortedDistances(std::vector<TrackedHuman>& oldHumans,
274 std::vector<AdvancedCluster>& newClusters)
275 {
276 std::vector<PosLaserDistance> posDistances;
277
278 for (auto& oldHuman : oldHumans)
279 {
280 for (auto& newCluster : newClusters)
281 {
282 // calculate distance between every possible combination of tracked human and cluster
283 posDistances.push_back(
284 {&oldHuman,
285 &newCluster,
286 (newCluster.center - oldHuman.humanFilter.get().pose.translation()).norm()});
287 }
288 }
289
290 // sort the distances ascending by their numeric value
291 std::sort(posDistances.begin(),
292 posDistances.end(),
293 [](const PosLaserDistance& a, const PosLaserDistance& b) -> bool
294 { return a.distance < b.distance; });
295
296 return posDistances;
297 }
298
299 void
300 HumanTracker::associateHumans(std::vector<DetectedHuman>& detectedHumans)
301 {
302 // first, associate humans by their tracking id
303 for (auto& oldHuman : trackedHumans)
304 {
305 if (oldHuman.associated || not oldHuman.trackingId)
306 {
307 continue;
308 }
309 for (auto& newHuman : detectedHumans)
310 {
311 if (newHuman.associated || not newHuman.trackingId)
312 {
313 continue;
314 }
315 // check for every possible combination of old and new humans (that were not already
316 // associated and have a tracking id) if their tracking id is the same
317 if (oldHuman.trackingId.value() == newHuman.trackingId.value())
318 {
319 associate(&oldHuman, &newHuman);
320 }
321 }
322 }
323
324 // second, associate leftover humans by their distances
325 const auto sortedDistances = getSortedDistances(trackedHumans, detectedHumans);
326
327 for (auto& posDistance : sortedDistances)
328 {
329 if (posDistance.distance > parameters.maxAssociationDistance)
330 {
331 break;
332 }
333 if (posDistance.oldHuman->associated || posDistance.newHuman->associated)
334 {
335 continue;
336 }
337 // associate the pair with the currently smallest distance between non-associated humans
338 associate(posDistance.oldHuman, posDistance.newHuman);
339 }
340 }
341
342 void
343 HumanTracker::associate(TrackedHuman* trackedHuman, DetectedHuman* detectedHuman)
344 {
345 ARMARX_CHECK(not trackedHuman->associated);
346 ARMARX_CHECK(not detectedHuman->associated);
347
348
349 trackedHuman->associated = true;
350 detectedHuman->associated = true;
351
352 trackedHuman->humanFilter.update(detectedHuman->pose, detectedHuman->detectionTime);
353 trackedHuman->trackingId = detectedHuman->trackingId;
354
355 trackedHuman->confidence = parameters.confidenceCamera;
356 }
357
358 float
359 HumanTracker::getClusterSize(Cluster cluster)
360 {
361 return 2 * cluster.circle.radius;
362 }
363
364 Eigen::Vector2f
365 HumanTracker::getClusterCenter(Cluster cluster)
366 {
367 typedef boost::geometry::model::d2::point_xy<double> boost_point;
368 typedef boost::geometry::model::polygon<boost_point> boost_polygon;
369
370 ARMARX_CHECK(!cluster.convexHull.empty());
371 std::vector<boost_point> points;
372 for (auto& point : cluster.convexHull)
373 {
374 points.push_back(boost_point(point.x(), point.y()));
375 }
376 //make polygon enclosed
377 points.push_back(
378 boost_point(cluster.convexHull.front().x(), cluster.convexHull.front().y()));
379 boost_polygon poly;
380 boost::geometry::assign_points(poly, points);
381
382 boost_point p = {};
383 boost::geometry::centroid(poly, p);
384
385 return Eigen::Vector2f{p.x(), p.y()};
386 }
387
388 void
389 HumanTracker::prepareTrackedHumans(DateTime time)
390 {
391 // iterate over all existing tracked humans
392 for (auto it = trackedHumans.begin(); it != trackedHumans.end();)
393 {
394 auto& human = *it;
395 // when the tracked human recieved no new measurement for too long or
396 // wasn't detected by the camera for too long, remove it
397 if (human.humanFilter.detectionAge(time) >= parameters.maxTrackingAge ||
398 human.confidence <= 0)
399 {
400 it = trackedHumans.erase(it);
401 }
402 // otherwise the tracked human is prepared for association at the current point in time
403 else
404 {
405 human.associatedCluster = nullptr;
406 human.associated = false;
407 human.humanFilter.propagation(time);
408 it++;
409 }
410 }
411 }
412
413} // namespace armarx::navigation::human
#define ARMARX_CHECK_NOT_EMPTY(c)
Represents a point in time.
Definition DateTime.h:25
The HumanFilter class can be used to track and filter the state of a single human.
Definition HumanFilter.h:47
const Human & get() const
HumanFilter::get returns the human whose pose is filtered containing the most recent state.
void update(const CameraMeasurement &measurements)
HumanTracker::update Updates the tracked humans with the human measurements from a camera.
void reset()
HumanTracker::reset Resets this instance to the same state as if it just would have been created.
std::vector< human::Human > getTrackedHumans() const
HumanTracker::getTrackedHumans Returns all humans that are currently tracked.
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
Quaternion< float, 0 > Quaternionf
std::vector< Eigen::Vector2f > to2D(const std::vector< Eigen::Vector3f > &v)
Definition eigen.cpp:29
Eigen::Isometry2f Pose2D
Definition basic_types.h:34
This file is part of ArmarX.
memory::LaserScannerFeature Cluster
std::optional< std::string > humanTrackingId
Definition types.h:47
std::optional< armarx::FramedOrientation > orientationGlobal
Definition types.h:38
std::vector< armem::human::HumanPose > humanPoses