Skip to content

Commit 323d983

Browse files
Add object tracking to RadarTrackObjectsNode (#288)
* Add more details in documentation for object tracking node. * Remove not necessary overload. * Update type traits on RunningStats to be more precise. * Add covariance calculation. Add RunningStats specialization for Vector types. * Change function signature in RunningStats specialization. * Add prototype implementation of object tracking. * Add minor comments to RunningStats. * Move object position prediction to node. Fix predicted position calculation. Update prediction to include acceleration if available. Add getter for samples count to RunningStats. * Update velocity calculation. Add assert on delta time and object ID. * Fix object acceleration calculation. * Add object orientation and orientation rate calculation. * Make minor fixes to types in track objects node. * Fix typos and add missing include in RunningStats header. * Add axis-aligned bounding box implementation. * Add aliases and staic asserts for aabb. * Add max prediction time frame - how long object can be predicted until declared lost. * Add option to reset aabb. * Add keeping orientation from previous frame, when object did not move. * Add operator overloads to Aabb. Fix field names mistake. * Fix Aabb members naming. * Update RadarPostprocessPointsNode to provide cluster (detection) aabbs. * Add handling detection (and object) aabbs to RadarTrackObjectsNode. * Rename predictionSensitivity parameter to maxMatchingDistance. * Add remaining tracking parameters to API. * Add kinematic object tracking test. Make object tracking tests more clear. * Add runtime object tracking test for rviz2 preview, skipped by default. * Add linear and angular velocity getters to IPointsNode. Add relative velocity and relative acceleration calculation to RadarTrackObjectsNode. * Make changes according to review for #288. --------- Signed-off-by: Paweł Liberadzki <[email protected]>
1 parent c4ab95d commit 323d983

File tree

10 files changed

+667
-111
lines changed

10 files changed

+667
-111
lines changed

include/rgl/api/core.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -851,19 +851,23 @@ RGL_API rgl_status_t rgl_node_points_radar_postprocess(rgl_node_t* node, const r
851851
* Creates or modifies RadarTrackObjectsNode.
852852
* The Node takes radar detections point cloud as input (from rgl_node_points_radar_postprocess), groups detections into objects based on given thresholds (node parameters),
853853
* and calculates various characteristics of these objects. This Node is intended to be connected to object list publishing node (from rgl_node_publish_udp_objectlist).
854-
* The output from this Node is in the form of a point cloud, where point XYZ coordinates are tracked objects positions.
854+
* The output from this Node is in the form of a point cloud, where point XYZ coordinates are tracked objects positions. Additionally, cloud points have tracked object IDs.
855855
* Note that for correct calculation and publishing some of object characteristics (e.g. velocity) user has to call rgl_scene_set_time for current scene.
856856
* Graph input: point cloud, containing RGL_FIELD_DISTANCE_F32, RGL_FIELD_AZIMUTH_F32, RGL_FIELD_ELEVATION_F32 and RGL_FIELD_RADIAL_SPEED_F32
857-
* Graph output: point cloud
857+
* Graph output: point cloud, contains only XYZ_VEC3_F32 for tracked object positions and ENTITY_ID_I32 for their IDs.
858858
* @param node If (*node) == nullptr, a new Node will be created. Otherwise, (*node) will be modified.
859859
* @param object_distance_threshold The maximum distance between a radar detection and other closest detection classified as the same tracked object (in meters).
860860
* @param object_azimuth_threshold The maximum azimuth difference between a radar detection and other closest detection classified as the same tracked object (in radians).
861861
* @param object_elevation_threshold The maximum elevation difference between a radar detection and other closest detection classified as the same tracked object (in radians).
862862
* @param object_radial_speed_threshold The maximum radial speed difference between a radar detection and other closest detection classified as the same tracked object (in meters per second).
863+
* @param max_matching_distance The maximum distance between predicted (based on previous frame) and newly detected object position to match objects between frames (in meters).
864+
* @param max_prediction_time_frame The maximum time that object state can be predicted until it will be declared lost unless measured again (in milliseconds).
865+
* @param movement_sensitivity The maximum position change for an object to be qualified as stationary (in meters).
863866
*/
864867
RGL_API rgl_status_t rgl_node_points_radar_track_objects(rgl_node_t* node, float object_distance_threshold,
865868
float object_azimuth_threshold, float object_elevation_threshold,
866-
float object_radial_speed_threshold);
869+
float object_radial_speed_threshold, float max_matching_distance,
870+
float max_prediction_time_frame, float movement_sensitivity);
867871

868872
/**
869873
* Creates or modifies FilterGroundPointsNode.

src/api/apiCore.cpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,24 +1175,30 @@ void TapeCore::tape_node_points_radar_postprocess(const YAML::Node& yamlNode, Pl
11751175

11761176
RGL_API rgl_status_t rgl_node_points_radar_track_objects(rgl_node_t* node, float object_distance_threshold,
11771177
float object_azimuth_threshold, float object_elevation_threshold,
1178-
float object_radial_speed_threshold)
1178+
float object_radial_speed_threshold, float max_matching_distance,
1179+
float max_prediction_time_frame, float movement_sensitivity)
11791180
{
11801181
auto status = rglSafeCall([&]() {
11811182
RGL_API_LOG("rgl_node_points_radar_track_objects(node={}, object_distance_threshold={}, object_azimuth_threshold={}, "
1182-
"object_elevation_threshold={}, object_radial_speed_threshold={})",
1183+
"object_elevation_threshold={}, object_radial_speed_threshold={}, max_matching_distance={}, "
1184+
"max_prediction_time_frame={}, movement_sensitivity={})",
11831185
repr(node), object_distance_threshold, object_azimuth_threshold, object_elevation_threshold,
1184-
object_radial_speed_threshold);
1186+
object_radial_speed_threshold, max_matching_distance, max_prediction_time_frame, movement_sensitivity);
11851187
CHECK_ARG(node != nullptr);
11861188
CHECK_ARG(object_distance_threshold > 0.0f);
11871189
CHECK_ARG(object_azimuth_threshold > 0.0f);
11881190
CHECK_ARG(object_elevation_threshold > 0.0f);
11891191
CHECK_ARG(object_radial_speed_threshold > 0.0f);
1192+
CHECK_ARG(max_matching_distance > 0.0f);
1193+
CHECK_ARG(max_prediction_time_frame > 0.0f);
1194+
CHECK_ARG(movement_sensitivity >= 0.0f);
11901195

11911196
createOrUpdateNode<RadarTrackObjectsNode>(node, object_distance_threshold, object_azimuth_threshold,
1192-
object_elevation_threshold, object_radial_speed_threshold);
1197+
object_elevation_threshold, object_radial_speed_threshold,
1198+
max_matching_distance, max_prediction_time_frame, movement_sensitivity);
11931199
});
11941200
TAPE_HOOK(node, object_distance_threshold, object_azimuth_threshold, object_elevation_threshold,
1195-
object_radial_speed_threshold);
1201+
object_radial_speed_threshold, max_matching_distance, max_prediction_time_frame, movement_sensitivity);
11961202
return status;
11971203
}
11981204

@@ -1201,7 +1207,8 @@ void TapeCore::tape_node_points_radar_track_objects(const YAML::Node& yamlNode,
12011207
auto nodeId = yamlNode[0].as<TapeAPIObjectID>();
12021208
rgl_node_t node = state.nodes.contains(nodeId) ? state.nodes.at(nodeId) : nullptr;
12031209
rgl_node_points_radar_track_objects(&node, yamlNode[1].as<float>(), yamlNode[2].as<float>(), yamlNode[3].as<float>(),
1204-
yamlNode[4].as<float>());
1210+
yamlNode[4].as<float>(), yamlNode[5].as<float>(), yamlNode[6].as<float>(),
1211+
yamlNode[7].as<float>());
12051212
state.nodes.insert({nodeId, node});
12061213
}
12071214

src/graph/Interfaces.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ struct IPointsNode : virtual Node
8787
virtual std::size_t getPointCount() const { return getWidth() * getHeight(); }
8888

8989
virtual Mat3x4f getLookAtOriginTransform() const { return Mat3x4f::identity(); }
90+
virtual Vec3f getLinearVelocity() const { return Vec3f{0.0f}; }
91+
virtual Vec3f getAngularVelocity() const { return Vec3f{0.0f}; }
9092

9193
// Data getters
9294
virtual IAnyArray::ConstPtr getFieldData(rgl_field_t field) = 0;
@@ -120,6 +122,8 @@ struct IPointsNodeSingleInput : IPointsNode
120122
virtual size_t getHeight() const override { return input->getHeight(); }
121123

122124
virtual Mat3x4f getLookAtOriginTransform() const override { return input->getLookAtOriginTransform(); }
125+
virtual Vec3f getLinearVelocity() const { return input->getLinearVelocity(); }
126+
virtual Vec3f getAngularVelocity() const { return input->getAngularVelocity(); }
123127

124128
// Data getters
125129
virtual bool hasField(rgl_field_t field) const { return input->hasField(field); }

src/graph/NodesCore.hpp

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <ranges>
2323
#include <algorithm>
2424
#include <random>
25+
#include <queue>
2526
#include <curand_kernel.h>
2627

2728
#include <graph/Node.hpp>
@@ -30,6 +31,7 @@
3031
#include <gpu/nodeKernels.hpp>
3132
#include <CacheManager.hpp>
3233
#include <GPUFieldDescBuilder.hpp>
34+
#include <math/Aabb.h>
3335
#include <math/RunningStats.hpp>
3436
#include <gpu/MultiReturn.hpp>
3537

@@ -114,6 +116,8 @@ struct RaytraceNode : IPointsNode
114116
size_t getHeight() const override { return 1; } // TODO: implement height in use_rays
115117

116118
Mat3x4f getLookAtOriginTransform() const override { return raysNode->getCumulativeRayTransfrom().inverse(); }
119+
Vec3f getLinearVelocity() const override { return sensorLinearVelocityXYZ; }
120+
Vec3f getAngularVelocity() const override { return sensorAngularVelocityRPY; }
117121

118122
// Data getters
119123
IAnyArray::ConstPtr getFieldData(rgl_field_t field) override
@@ -532,11 +536,14 @@ struct RadarPostprocessPointsNode : IPointsNodeSingleInput
532536
// Data getters
533537
IAnyArray::ConstPtr getFieldData(rgl_field_t field) override;
534538

539+
const std::vector<Aabb3Df>& getClusterAabbs() const { return clusterAabbs; }
540+
535541
private:
536542
// Data containers
537543
std::vector<Field<RAY_IDX_U32>::type> filteredIndicesHost;
538544
DeviceAsyncArray<Field<RAY_IDX_U32>::type>::Ptr filteredIndices = DeviceAsyncArray<Field<RAY_IDX_U32>::type>::create(
539545
arrayMgr);
546+
HostPinnedArray<Field<XYZ_VEC3_F32>::type>::Ptr xyzInputHost = HostPinnedArray<Field<XYZ_VEC3_F32>::type>::create();
540547
HostPinnedArray<Field<DISTANCE_F32>::type>::Ptr distanceInputHost = HostPinnedArray<Field<DISTANCE_F32>::type>::create();
541548
HostPinnedArray<Field<AZIMUTH_F32>::type>::Ptr azimuthInputHost = HostPinnedArray<Field<AZIMUTH_F32>::type>::create();
542549
HostPinnedArray<Field<RADIAL_SPEED_F32>::type>::Ptr radialSpeedInputHost =
@@ -566,6 +573,7 @@ struct RadarPostprocessPointsNode : IPointsNodeSingleInput
566573
float receivedNoiseStDevDb;
567574

568575
std::vector<rgl_radar_scope_t> radarScopes;
576+
std::vector<Aabb3Df> clusterAabbs;
569577

570578
std::random_device randomDevice;
571579

@@ -613,6 +621,12 @@ struct RadarTrackObjectsNode : IPointsNodeSingleInput
613621
Invalid = 255
614622
};
615623

624+
struct ObjectBounds
625+
{
626+
Vec3f position{0};
627+
Aabb3Df aabb{};
628+
};
629+
616630
struct ClassificationProbabilities
617631
{
618632
float existence{100.0f};
@@ -629,8 +643,8 @@ struct RadarTrackObjectsNode : IPointsNodeSingleInput
629643
struct ObjectState
630644
{
631645
uint32_t id{0};
632-
uint32_t framesCount{0};
633646
uint32_t creationTime{0};
647+
uint32_t lastMeasuredTime{0};
634648
ObjectStatus objectStatus{ObjectStatus::Invalid};
635649
MovementStatus movementStatus{MovementStatus::Invalid};
636650
ClassificationProbabilities classificationProbabilities{};
@@ -648,15 +662,13 @@ struct RadarTrackObjectsNode : IPointsNodeSingleInput
648662

649663
RadarTrackObjectsNode();
650664

651-
void setParameters(float distanceThreshold, float azimuthThreshold, float elevationThreshold, float radialSpeedThreshold);
665+
void setParameters(float distanceThreshold, float azimuthThreshold, float elevationThreshold, float radialSpeedThreshold,
666+
float maxMatchingDistance, float maxPredictionTimeFrame, float movementSensitivity);
652667

653-
// Node
654-
void validateImpl() override;
655668
void enqueueExecImpl() override;
656669

657670
bool hasField(rgl_field_t field) const override { return fieldData.contains(field); }
658671

659-
// Node requirements
660672
std::vector<rgl_field_t> getRequiredFieldList() const override;
661673

662674
// Data getters
@@ -667,14 +679,30 @@ struct RadarTrackObjectsNode : IPointsNodeSingleInput
667679
const std::list<ObjectState>& getObjectStates() const { return objectStates; }
668680

669681
private:
682+
Vec3f predictObjectPosition(const ObjectState& objectState, double deltaTimeMs) const;
683+
void createObjectState(const ObjectBounds& objectBounds, double currentTimeMs);
684+
void updateObjectState(ObjectState& objectState, const Vec3f& updatedPosition, const Aabb3Df& updatedAabb,
685+
ObjectStatus objectStatus, double currentTimeMs, double deltaTimeMs);
686+
void updateOutputData();
687+
670688
std::list<ObjectState> objectStates;
671689
std::unordered_map<rgl_field_t, IAnyArray::Ptr> fieldData;
672690

691+
uint32_t objectIDCounter = 0; // Not static - I assume each ObjectTrackingNode is like a separate radar.
692+
std::queue<uint32_t> objectIDPoll;
693+
673694
float distanceThreshold;
674695
float azimuthThreshold;
675696
float elevationThreshold;
676697
float radialSpeedThreshold;
677698

699+
float maxMatchingDistance =
700+
1.0f; // Max distance between predicted and newly detected position to match objects between frames.
701+
float maxPredictionTimeFrame =
702+
500.0f; // Maximum time in milliseconds that can pass between two detections of the same object.
703+
// In other words, how long object state can be predicted until it will be declared lost.
704+
float movementSensitivity = 0.01f; // Max position change for an object to be qualified as MovementStatus::Stationary.
705+
678706
HostPinnedArray<Field<XYZ_VEC3_F32>::type>::Ptr xyzHostPtr = HostPinnedArray<Field<XYZ_VEC3_F32>::type>::create();
679707
HostPinnedArray<Field<DISTANCE_F32>::type>::Ptr distanceHostPtr = HostPinnedArray<Field<DISTANCE_F32>::type>::create();
680708
HostPinnedArray<Field<AZIMUTH_F32>::type>::Ptr azimuthHostPtr = HostPinnedArray<Field<AZIMUTH_F32>::type>::create();

src/graph/RadarPostprocessPointsNode.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ void RadarPostprocessPointsNode::enqueueExecImpl()
7878
return;
7979
}
8080

81+
xyzInputHost->copyFrom(input->getFieldData(XYZ_VEC3_F32));
8182
distanceInputHost->copyFrom(input->getFieldData(DISTANCE_F32));
8283
azimuthInputHost->copyFrom(input->getFieldData(AZIMUTH_F32));
8384
radialSpeedInputHost->copyFrom(input->getFieldData(RADIAL_SPEED_F32));
@@ -143,13 +144,19 @@ void RadarPostprocessPointsNode::enqueueExecImpl()
143144
clusterPowerHost->resize(filteredIndicesHost.size(), false, false);
144145
clusterNoiseHost->resize(filteredIndicesHost.size(), false, false);
145146
clusterSnrHost->resize(filteredIndicesHost.size(), false, false);
147+
146148
std::normal_distribution<float> gaussianNoise(receivedNoiseMeanDb, receivedNoiseStDevDb);
149+
clusterAabbs.resize(clusters.size());
150+
147151
for (int clusterIdx = 0; clusterIdx < clusters.size(); ++clusterIdx) {
148152
std::complex<float> AU = 0;
149153
std::complex<float> AR = 0;
150-
auto&& cluster = clusters[clusterIdx];
154+
auto& cluster = clusters[clusterIdx];
155+
auto& clusterAabb = clusterAabbs[clusterIdx];
156+
clusterAabb.reset();
151157

152158
for (const auto pointInCluster : cluster.indices) {
159+
clusterAabb.expand(xyzInputHost->at(pointInCluster));
153160
std::complex<float> BU = {outBUBRFactorHost->at(pointInCluster)[0].real(),
154161
outBUBRFactorHost->at(pointInCluster)[0].imag()};
155162
std::complex<float> BR = {outBUBRFactorHost->at(pointInCluster)[1].real(),

0 commit comments

Comments
 (0)