-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
523 Distance measurement of not detected objects for radar data #525
Changes from 4 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2,8 +2,10 @@ | |||||||||||||||||||||||||||||||||||||
import rospy | ||||||||||||||||||||||||||||||||||||||
import ros_numpy | ||||||||||||||||||||||||||||||||||||||
import numpy as np | ||||||||||||||||||||||||||||||||||||||
from std_msgs.msg import String | ||||||||||||||||||||||||||||||||||||||
from sensor_msgs.msg import PointCloud2 | ||||||||||||||||||||||||||||||||||||||
from std_msgs.msg import Float32 | ||||||||||||||||||||||||||||||||||||||
from sklearn.cluster import DBSCAN | ||||||||||||||||||||||||||||||||||||||
import json | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
class RadarNode: | ||||||||||||||||||||||||||||||||||||||
|
@@ -12,19 +14,19 @@ class RadarNode: | |||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
def callback(self, data): | ||||||||||||||||||||||||||||||||||||||
"""Process radar Point2Cloud data and publish minimum velocity. | ||||||||||||||||||||||||||||||||||||||
"""Process radar Point2Cloud data and publish clustered data points. | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
Extracts velocity information from radar data | ||||||||||||||||||||||||||||||||||||||
and publishes the minimum velocity as a | ||||||||||||||||||||||||||||||||||||||
Float32 message | ||||||||||||||||||||||||||||||||||||||
Extracts information from radar data | ||||||||||||||||||||||||||||||||||||||
and publishes the clustered radar data | ||||||||||||||||||||||||||||||||||||||
points as a String message. | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
Args: | ||||||||||||||||||||||||||||||||||||||
data: Point2Cloud message containing radar data with velocity field | ||||||||||||||||||||||||||||||||||||||
data: Point2Cloud message containing radar data | ||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
coordinates = ros_numpy.point_cloud2.pointcloud2_to_array(data) | ||||||||||||||||||||||||||||||||||||||
msg = np.min(coordinates["Velocity"]) | ||||||||||||||||||||||||||||||||||||||
self.dist_array_radar_publisher.publish(msg) | ||||||||||||||||||||||||||||||||||||||
clustered_points = cluster_radar_data_from_pointcloud(data, 10) | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Make clustering parameters configurable via ROS parameters The - clustered_points = cluster_radar_data_from_pointcloud(data, 10)
+ max_distance = rospy.get_param('~clustering/max_distance', 10.0)
+ eps = rospy.get_param('~clustering/eps', 1.0)
+ min_samples = rospy.get_param('~clustering/min_samples', 2)
+ clustered_points = cluster_radar_data_from_pointcloud(
+ data, max_distance, eps, min_samples
+ )
|
||||||||||||||||||||||||||||||||||||||
clustered_points_json = json.dumps(clustered_points) | ||||||||||||||||||||||||||||||||||||||
self.dist_array_radar_publisher.publish(clustered_points_json) | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+24
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for JSON serialization The callback should handle potential exceptions during clustering and JSON serialization to prevent node crashes. Apply this diff: - clustered_points = cluster_radar_data_from_pointcloud(data, 10)
- clustered_points_json = json.dumps(clustered_points)
- self.dist_array_radar_publisher.publish(clustered_points_json)
+ try:
+ clustered_points = cluster_radar_data_from_pointcloud(data, 10)
+ clustered_points_json = json.dumps(clustered_points)
+ self.dist_array_radar_publisher.publish(clustered_points_json)
+ except Exception as e:
+ rospy.logerr(f"Failed to process radar data: {e}") 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
def listener(self): | ||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||
|
@@ -35,8 +37,11 @@ def listener(self): | |||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
# publisher for radar dist_array | ||||||||||||||||||||||||||||||||||||||
self.dist_array_radar_publisher = rospy.Publisher( | ||||||||||||||||||||||||||||||||||||||
rospy.get_param("~image_distance_topic", "/paf/hero/Radar/velocity"), | ||||||||||||||||||||||||||||||||||||||
Float32, | ||||||||||||||||||||||||||||||||||||||
rospy.get_param( | ||||||||||||||||||||||||||||||||||||||
"~image_distance_topic", | ||||||||||||||||||||||||||||||||||||||
"/paf/hero/Radar/dist_array_unsegmented", | ||||||||||||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||||||||||||
String, | ||||||||||||||||||||||||||||||||||||||
queue_size=10, | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
|
@@ -49,6 +54,101 @@ def listener(self): | |||||||||||||||||||||||||||||||||||||
rospy.spin() | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
def pointcloud2_to_array(pointcloud_msg): | ||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||
Konvertiert eine ROS-PointCloud2-Nachricht in ein NumPy-Array und berechnet | ||||||||||||||||||||||||||||||||||||||
die euklidischen Entfernungen jedes Punkts vom Ursprung. | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
Parameter: | ||||||||||||||||||||||||||||||||||||||
- pointcloud_msg: sensor_msgs/PointCloud2 | ||||||||||||||||||||||||||||||||||||||
Die ROS-PointCloud2-Nachricht, die die 3D-Punkte enthält. | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
Rückgabewert: | ||||||||||||||||||||||||||||||||||||||
- np.ndarray | ||||||||||||||||||||||||||||||||||||||
Ein 2D-Array, in dem jede Zeile einem Punkt in der Punktwolke entspricht: | ||||||||||||||||||||||||||||||||||||||
[x, y, z, distance], wobei "distance" die Entfernung vom Ursprung ist. | ||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||
cloud_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud_msg) | ||||||||||||||||||||||||||||||||||||||
distances = np.sqrt( | ||||||||||||||||||||||||||||||||||||||
cloud_array["x"] ** 2 + cloud_array["y"] ** 2 + cloud_array["z"] ** 2 | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+60
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for numpy operations The numpy operations could raise exceptions with invalid input data. Consider adding error handling: - cloud_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud_msg)
- distances = np.sqrt(
- cloud_array["x"] ** 2 + cloud_array["y"] ** 2 + cloud_array["z"] ** 2
- )
+ try:
+ cloud_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud_msg)
+ distances = np.sqrt(
+ cloud_array["x"] ** 2 + cloud_array["y"] ** 2 + cloud_array["z"] ** 2
+ )
+ except (ValueError, KeyError) as e:
+ rospy.logerr(f"Error processing point cloud data: {e}")
+ return np.array([]) 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
return np.column_stack( | ||||||||||||||||||||||||||||||||||||||
(cloud_array["x"], cloud_array["y"], cloud_array["z"], distances) | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
def cluster_radar_data_from_pointcloud( | ||||||||||||||||||||||||||||||||||||||
pointcloud_msg, max_distance, eps=1.0, min_samples=2 | ||||||||||||||||||||||||||||||||||||||
): | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+69
to
+71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add input parameter validation The function should validate input parameters to prevent invalid configurations. Apply this diff: def cluster_radar_data_from_pointcloud(
pointcloud_msg, max_distance, eps=1.0, min_samples=2
):
+ if max_distance <= 0:
+ raise ValueError("max_distance must be positive")
+ if eps <= 0:
+ raise ValueError("eps must be positive")
+ if min_samples < 2:
+ raise ValueError("min_samples must be at least 2")
+ if not isinstance(pointcloud_msg, PointCloud2):
+ raise TypeError("pointcloud_msg must be a PointCloud2 message") 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||
Filtert und gruppiert Punkte aus einer ROS-PointCloud2-Nachricht | ||||||||||||||||||||||||||||||||||||||
basierend auf DBSCAN-Clustering. | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
Parameter: | ||||||||||||||||||||||||||||||||||||||
- pointcloud_msg: sensor_msgs/PointCloud2 | ||||||||||||||||||||||||||||||||||||||
Die ROS-PointCloud2-Nachricht mit den 3D-Punkten. | ||||||||||||||||||||||||||||||||||||||
- max_distance: float | ||||||||||||||||||||||||||||||||||||||
Maximale Entfernung, um Punkte zu berücksichtigen. Punkte außerhalb dieser | ||||||||||||||||||||||||||||||||||||||
Entfernung werden verworfen. | ||||||||||||||||||||||||||||||||||||||
- eps: float, optional (default: 1.0) | ||||||||||||||||||||||||||||||||||||||
Der maximale Abstand zwischen zwei Punkten, damit diese im selben Cluster | ||||||||||||||||||||||||||||||||||||||
sein können. | ||||||||||||||||||||||||||||||||||||||
- min_samples: int, optional (default: 2) | ||||||||||||||||||||||||||||||||||||||
Die minimale Anzahl von Punkten, die erforderlich sind, um einen Cluster | ||||||||||||||||||||||||||||||||||||||
zu bilden. | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
Rückgabewert: | ||||||||||||||||||||||||||||||||||||||
- dict | ||||||||||||||||||||||||||||||||||||||
Ein Dictionary, in dem die Schlüssel die Cluster-Labels (int) sind und die | ||||||||||||||||||||||||||||||||||||||
Werte die Anzahl der Punkte in jedem Cluster. Wenn keine Punkte vorhanden | ||||||||||||||||||||||||||||||||||||||
sind, wird ein leeres Dictionary zurückgegeben. | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
Funktionalität: | ||||||||||||||||||||||||||||||||||||||
- Die Funktion konvertiert die Punktwolke in ein Array mit Punktkoordinaten | ||||||||||||||||||||||||||||||||||||||
und berechneten Entfernungen. | ||||||||||||||||||||||||||||||||||||||
- Punkte außerhalb eines spezifizierten Entfernungsbereichs und räumlichen | ||||||||||||||||||||||||||||||||||||||
Filtergrenzen werden verworfen. | ||||||||||||||||||||||||||||||||||||||
- Anschließend wird DBSCAN-Clustering auf die 2D-Koordinaten (x, y) der Punkte | ||||||||||||||||||||||||||||||||||||||
angewendet. | ||||||||||||||||||||||||||||||||||||||
- Die Cluster-Labels und ihre Häufigkeiten werden als Dictionary zurückgegeben. | ||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||
data = pointcloud2_to_array(pointcloud_msg) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
# Filtere Punkte basierend auf der maximalen Entfernung | ||||||||||||||||||||||||||||||||||||||
filtered_data = data[data[:, 3] < max_distance] | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
# Zusätzlicher Filter auf Y- und Z-Werte | ||||||||||||||||||||||||||||||||||||||
filtered_data = filtered_data[ | ||||||||||||||||||||||||||||||||||||||
(filtered_data[:, 1] >= -1) | ||||||||||||||||||||||||||||||||||||||
& (filtered_data[:, 1] <= 1) | ||||||||||||||||||||||||||||||||||||||
& (filtered_data[:, 2] <= 1.3) | ||||||||||||||||||||||||||||||||||||||
& (filtered_data[:, 2] >= -0.7) | ||||||||||||||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+96
to
+101
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Make filter boundaries configurable via ROS parameters Hard-coded filter values should be configurable to allow for easy adjustment without code changes. Apply this diff: + # Get filter boundaries from ROS parameters with defaults
+ y_min = rospy.get_param("~filter/y_min", -1.0)
+ y_max = rospy.get_param("~filter/y_max", 1.0)
+ z_min = rospy.get_param("~filter/z_min", -0.7)
+ z_max = rospy.get_param("~filter/z_max", 1.3)
+
filtered_data = filtered_data[
- (filtered_data[:, 1] >= -1)
- & (filtered_data[:, 1] <= 1)
- & (filtered_data[:, 2] <= 1.3)
- & (filtered_data[:, 2] >= -0.7)
+ (filtered_data[:, 1] >= y_min)
+ & (filtered_data[:, 1] <= y_max)
+ & (filtered_data[:, 2] <= z_max)
+ & (filtered_data[:, 2] >= z_min)
] 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
# Rückgabe eines leeren Dictionaries, wenn keine Punkte übrig sind | ||||||||||||||||||||||||||||||||||||||
if len(filtered_data) == 0: | ||||||||||||||||||||||||||||||||||||||
return {} | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
# Extrahiere die 2D-Koordinaten (x, y) für das Clustering | ||||||||||||||||||||||||||||||||||||||
coordinates = filtered_data[:, :2] | ||||||||||||||||||||||||||||||||||||||
clustering = DBSCAN(eps=eps, min_samples=min_samples).fit(coordinates) | ||||||||||||||||||||||||||||||||||||||
labels = clustering.labels_ | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
# Zählen der Punktanzahl in jedem Cluster | ||||||||||||||||||||||||||||||||||||||
clustered_points = { | ||||||||||||||||||||||||||||||||||||||
label: list(labels).count(label) | ||||||||||||||||||||||||||||||||||||||
for label in set(labels) | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
# Konvertiere die Labels in Integer und gebe das Dictionary zurück | ||||||||||||||||||||||||||||||||||||||
clustered_points = { | ||||||||||||||||||||||||||||||||||||||
int(label): count | ||||||||||||||||||||||||||||||||||||||
for label, count in clustered_points.items() | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
return clustered_points | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
if __name__ == "__main__": | ||||||||||||||||||||||||||||||||||||||
radar_node = RadarNode() | ||||||||||||||||||||||||||||||||||||||
radar_node.listener() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure 'scikit-learn' is included in project dependencies
The
DBSCAN
class fromsklearn.cluster
is being used. Please ensure thatscikit-learn
is added to your project's dependencies inrequirements.txt
orsetup.py
so that it is installed during setup.