diff --git a/perception/detection_by_tracker/CMakeLists.txt b/perception/detection_by_tracker/CMakeLists.txt new file mode 100644 index 0000000000000..72c74947372f9 --- /dev/null +++ b/perception/detection_by_tracker/CMakeLists.txt @@ -0,0 +1,65 @@ +cmake_minimum_required(VERSION 3.5) +project(detection_by_tracker) + +### Compile options +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) +endif() +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic -Werror) +endif() + +# Ignore -Wnonportable-include-path in Clang for mussp +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wno-nonportable-include-path) +endif() + +### Find Packages +find_package(ament_cmake_auto REQUIRED) + +### Find PCL Dependencies +find_package(PCL REQUIRED QUIET COMPONENTS common search filters segmentation) + +### Find Eigen Dependencies +find_package(eigen3_cmake_module REQUIRED) +find_package(Eigen3 REQUIRED) + +### Find dependencies listed in the package.xml +ament_auto_find_build_dependencies() + +include_directories( + include + ${PCL_COMMON_INCLUDE_DIRS} + ${PCL_INCLUDE_DIRS} +) + +# Generate exe file +set(DETECTION_BY_TRACKER_SRC + src/detection_by_tracker_core.cpp + src/utils.cpp +) + +ament_auto_add_library(detection_by_tracker_node SHARED + ${DETECTION_BY_TRACKER_SRC} +) + +target_link_libraries(detection_by_tracker_node + Eigen3::Eigen + ${PCL_LIBRARIES} +) + +rclcpp_components_register_node(detection_by_tracker_node + PLUGIN "DetectionByTracker" + EXECUTABLE detection_by_tracker +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_auto_package(INSTALL_TO_SHARE + launch +) diff --git a/perception/detection_by_tracker/README.md b/perception/detection_by_tracker/README.md new file mode 100644 index 0000000000000..fd5281587fbb0 --- /dev/null +++ b/perception/detection_by_tracker/README.md @@ -0,0 +1,63 @@ +# detection_by_tracker + +## Purpose + +This package feeds back the tracked objects to the detection module to keep it stable and keep detecting objects. +![purpose](image/purpose.svg) + +The detection by tracker takes as input an unknown object containing a cluster of points and a tracker. +The unknown object is optimized to fit the size of the tracker so that it can continue to be detected. + +## Inner-workings / Algorithms + +The detection by tracker receives an unknown object containing a point cloud and a tracker, where the unknown object is mainly shape-fitted using euclidean clustering. +Shape fitting using euclidean clustering and other methods has a problem called under segmentation and over segmentation. + +[![segmentation_fail](image/segmentation_fail.png)](https://www.researchgate.net/figure/Examples-of-an-undersegmentation-error-top-and-an-oversegmentation-error-bottom-Each_fig1_304533062) +_Adapted from [3]_ + +Simply looking at the overlap between the unknown object and the tracker does not work. We need to take measures for under segmentation and over segmentation. + +### Policy for dealing with over segmentation + +1. Merge the unknown objects in the tracker as a single object. +2. Shape fitting using the tracker information such as angle and size as reference information. + +### Policy for dealing with under segmentation + +1. Compare the tracker and unknown objects, and determine that those with large recall and small precision are under segmented objects. +2. In order to divide the cluster of under segmented objects, it iterate the parameters to make small clusters. +3. Adjust the parameters several times and adopt the one with the highest IoU. + +## Inputs / Outputs + +### Input + +| Name | Type | Description | +| ------------------------- | -------------------------------------------------------------- | --------------- | +| `~/input/initial_objects` | `autoware_perception_msgs::msg::DynamicObjectWithFeatureArray` | unknown objects | +| `~/input/tracked_objects` | `autoware_perception_msgs::msg::DynamicObjectArray` | trackers | + +### Output + +| Name | Type | Description | +| ---------- | -------------------------------------------------------------- | ----------- | +| `~/output` | `autoware_perception_msgs::msg::DynamicObjectWithFeatureArray` | objects | + +## Parameters + +## Assumptions / Known limits + +## (Optional) Error detection and handling + +## (Optional) Performance characterization + +## (Optional) References/External links + +[1] M. Himmelsbach, et al. "Tracking and classification of arbitrary objects with bottom-up/top-down detection." (2012). + +[2] Arya Senna Abdul Rachman, Arya. "3D-LIDAR Multi Object Tracking for Autonomous Driving: Multi-target Detection and Tracking under Urban Road Uncertainties." (2017). + +[3] David Held, et al. "A Probabilistic Framework for Real-time 3D Segmentation using Spatial, Temporal, and Semantic Cues." (2016). + +## (Optional) Future extensions / Unimplemented parts diff --git a/perception/detection_by_tracker/image/purpose.svg b/perception/detection_by_tracker/image/purpose.svg new file mode 100644 index 0000000000000..f92e7e511df94 --- /dev/null +++ b/perception/detection_by_tracker/image/purpose.svg @@ -0,0 +1,3 @@ + + +
detection by tracker
detection by tracker
detector
detector
multi object tracker
multi object tracker
merger
merger
Detection Module
Detection Module
Tracking Module
Tracking Module
unknown
objects
unknown...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/perception/detection_by_tracker/image/segmentation_fail.png b/perception/detection_by_tracker/image/segmentation_fail.png new file mode 100644 index 0000000000000..922578eaf5255 Binary files /dev/null and b/perception/detection_by_tracker/image/segmentation_fail.png differ diff --git a/perception/detection_by_tracker/include/detection_by_tracker/detection_by_tracker_core.hpp b/perception/detection_by_tracker/include/detection_by_tracker/detection_by_tracker_core.hpp new file mode 100644 index 0000000000000..ab53afb5f6f17 --- /dev/null +++ b/perception/detection_by_tracker/include/detection_by_tracker/detection_by_tracker_core.hpp @@ -0,0 +1,96 @@ +// Copyright 2021 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DETECTION_BY_TRACKER__DETECTION_BY_TRACKER_CORE_HPP_ +#define DETECTION_BY_TRACKER__DETECTION_BY_TRACKER_CORE_HPP_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +class TrackerHandler +{ +private: + std::deque objects_buffer_; + +public: + TrackerHandler() = default; + void onTrackedObjects( + const autoware_auto_perception_msgs::msg::TrackedObjects::ConstSharedPtr input_objects_msg); + bool estimateTrackedObjects( + const rclcpp::Time & time, autoware_auto_perception_msgs::msg::TrackedObjects & output); +}; + +class DetectionByTracker : public rclcpp::Node +{ +public: + explicit DetectionByTracker(const rclcpp::NodeOptions & node_options); + +private: + rclcpp::Publisher::SharedPtr objects_pub_; + rclcpp::Subscription::SharedPtr trackers_sub_; + rclcpp::Subscription::SharedPtr + initial_objects_sub_; + + tf2_ros::Buffer tf_buffer_; + tf2_ros::TransformListener tf_listener_; + + TrackerHandler tracker_handler_; + std::shared_ptr shape_estimator_; + std::shared_ptr cluster_; + + void onObjects( + const autoware_perception_msgs::msg::DetectedObjectsWithFeature::ConstSharedPtr input_msg); + + void divideUnderSegmentedObjects( + const autoware_auto_perception_msgs::msg::DetectedObjects & tracked_objects, + const autoware_perception_msgs::msg::DetectedObjectsWithFeature & in_objects, + autoware_auto_perception_msgs::msg::DetectedObjects & out_no_found_tracked_objects, + autoware_perception_msgs::msg::DetectedObjectsWithFeature & out_objects); + + float optimizeUnderSegmentedObject( + const autoware_auto_perception_msgs::msg::DetectedObject & target_object, + const sensor_msgs::msg::PointCloud2 & under_segmented_cluster, + autoware_perception_msgs::msg::DetectedObjectWithFeature & output); + + void mergeOverSegmentedObjects( + const autoware_auto_perception_msgs::msg::DetectedObjects & tracked_objects, + const autoware_perception_msgs::msg::DetectedObjectsWithFeature & in_objects, + autoware_auto_perception_msgs::msg::DetectedObjects & out_no_found_tracked_objects, + autoware_perception_msgs::msg::DetectedObjectsWithFeature & out_objects); +}; + +#endif // DETECTION_BY_TRACKER__DETECTION_BY_TRACKER_CORE_HPP_ diff --git a/perception/detection_by_tracker/include/detection_by_tracker/utils.hpp b/perception/detection_by_tracker/include/detection_by_tracker/utils.hpp new file mode 100644 index 0000000000000..1205c050f8921 --- /dev/null +++ b/perception/detection_by_tracker/include/detection_by_tracker/utils.hpp @@ -0,0 +1,44 @@ +// Copyright 2020 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DETECTION_BY_TRACKER__UTILS_HPP_ +#define DETECTION_BY_TRACKER__UTILS_HPP_ + +#include + +#include +#include +#include +#include + +#include + +namespace utils +{ +double getPolygonArea(const geometry_msgs::msg::Polygon & footprint); +double getRectangleArea(const geometry_msgs::msg::Vector3 & dimensions); +double getCircleArea(const geometry_msgs::msg::Vector3 & dimensions); +double getArea(const autoware_auto_perception_msgs::msg::Shape & shape); +double get2dIoU( + const autoware_auto_perception_msgs::msg::DetectedObject & object1, + const autoware_auto_perception_msgs::msg::DetectedObject & object2); +double get2dPrecision( + const autoware_auto_perception_msgs::msg::DetectedObject & source_object, + const autoware_auto_perception_msgs::msg::DetectedObject & target_object); +double get2dRecall( + const autoware_auto_perception_msgs::msg::DetectedObject & source_object, + const autoware_auto_perception_msgs::msg::DetectedObject & target_object); +} // namespace utils + +#endif // DETECTION_BY_TRACKER__UTILS_HPP_ diff --git a/perception/detection_by_tracker/launch/detection_by_tracker.launch.xml b/perception/detection_by_tracker/launch/detection_by_tracker.launch.xml new file mode 100644 index 0000000000000..c618b088d97fe --- /dev/null +++ b/perception/detection_by_tracker/launch/detection_by_tracker.launch.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/perception/detection_by_tracker/package.xml b/perception/detection_by_tracker/package.xml new file mode 100644 index 0000000000000..1321fbad5a880 --- /dev/null +++ b/perception/detection_by_tracker/package.xml @@ -0,0 +1,30 @@ + + + detection_by_tracker + 1.0.0 + The ROS2 detection_by_tracker package + Yukihiro Saito + Apache License 2.0 + + ament_cmake_auto + eigen3_cmake_module + + autoware_perception_msgs + autoware_utils + eigen + euclidean_cluster + libpcl-all-dev + pcl_conversions + rclcpp + rclcpp_components + shape_estimation + tf2 + tf2_ros + + ament_lint_auto + autoware_lint_common + + + ament_cmake + + diff --git a/perception/detection_by_tracker/src/detection_by_tracker_core.cpp b/perception/detection_by_tracker/src/detection_by_tracker_core.cpp new file mode 100644 index 0000000000000..f123acb3555ce --- /dev/null +++ b/perception/detection_by_tracker/src/detection_by_tracker_core.cpp @@ -0,0 +1,443 @@ +// Copyright 2021 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "detection_by_tracker/detection_by_tracker_core.hpp" + +#include "detection_by_tracker/utils.hpp" + +#include +#include +#include +#include +#include + +#define EIGEN_MPL2_ONLY +#include +#include + +namespace +{ +std::optional getTransform( + const tf2_ros::Buffer & tf_buffer, const std::string & source_frame_id, + const std::string & target_frame_id, const rclcpp::Time & time) +{ + try { + geometry_msgs::msg::TransformStamped transform_stamped; + transform_stamped = tf_buffer.lookupTransform( + /*target*/ target_frame_id, /*src*/ source_frame_id, time, + rclcpp::Duration::from_seconds(0.5)); + return transform_stamped.transform; + } catch (tf2::TransformException & ex) { + RCLCPP_WARN_STREAM(rclcpp::get_logger("detection_by_tracker"), ex.what()); + return std::nullopt; + } +} + +bool transformTrackedObjects( + const autoware_auto_perception_msgs::msg::TrackedObjects & input_msg, + const std::string & target_frame_id, const tf2_ros::Buffer & tf_buffer, + autoware_auto_perception_msgs::msg::TrackedObjects & output_msg) +{ + output_msg = input_msg; + + // Transform to world coordinate + if (output_msg.header.frame_id != target_frame_id) { + output_msg.header.frame_id = target_frame_id; + tf2::Transform tf_target2objects_world; + tf2::Transform tf_target2objects; + tf2::Transform tf_objects_world2objects; + { + const auto ros_target2objects_world = + getTransform(tf_buffer, input_msg.header.frame_id, target_frame_id, input_msg.header.stamp); + if (!ros_target2objects_world) { + return false; + } + tf2::fromMsg(*ros_target2objects_world, tf_target2objects_world); + } + for (auto & object : output_msg.objects) { + tf2::fromMsg(object.kinematics.pose_with_covariance.pose, tf_objects_world2objects); + tf_target2objects = tf_target2objects_world * tf_objects_world2objects; + tf2::toMsg(tf_target2objects, object.kinematics.pose_with_covariance.pose); + } + } + return true; +} + +void setClusterInObjectWithFeature( + const std_msgs::msg::Header & header, const pcl::PointCloud & cluster, + autoware_perception_msgs::msg::DetectedObjectWithFeature & feature_object) +{ + sensor_msgs::msg::PointCloud2 ros_pointcloud; + pcl::toROSMsg(cluster, ros_pointcloud); + ros_pointcloud.header = header; + feature_object.feature.cluster = ros_pointcloud; +} +autoware_auto_perception_msgs::msg::Shape extendShape( + const autoware_auto_perception_msgs::msg::Shape & shape, const float scale) +{ + autoware_auto_perception_msgs::msg::Shape output = shape; + output.dimensions.x *= scale; + output.dimensions.y *= scale; + output.dimensions.z *= scale; + for (auto & point : output.footprint.points) { + point.x *= scale; + point.y *= scale; + point.z *= scale; + } + return output; +} + +autoware_auto_perception_msgs::msg::DetectedObjects toDetectedObjects( + const autoware_auto_perception_msgs::msg::TrackedObjects & tracked_objects) +{ + autoware_auto_perception_msgs::msg::DetectedObjects out_objects; + out_objects.header = tracked_objects.header; + for (auto & tracked_object : tracked_objects.objects) { + autoware_auto_perception_msgs::msg::DetectedObject object; + object.existence_probability = tracked_object.existence_probability; + object.classification = tracked_object.classification; + object.kinematics.pose_with_covariance = tracked_object.kinematics.pose_with_covariance; + object.kinematics.has_position_covariance = true; + object.kinematics.orientation_availability = tracked_object.kinematics.orientation_availability; + object.kinematics.twist_with_covariance = tracked_object.kinematics.twist_with_covariance; + object.kinematics.has_twist = true; + object.kinematics.has_twist_covariance = true; + object.shape = tracked_object.shape; + out_objects.objects.push_back(object); + } + return out_objects; +} + +} // namespace + +void TrackerHandler::onTrackedObjects( + const autoware_auto_perception_msgs::msg::TrackedObjects::ConstSharedPtr msg) +{ + constexpr size_t max_buffer_size = 10; + + // Add tracked objects to buffer + objects_buffer_.push_front(*msg); + + // Remove old data + while (max_buffer_size < objects_buffer_.size()) { + objects_buffer_.pop_back(); + } +} + +bool TrackerHandler::estimateTrackedObjects( + const rclcpp::Time & time, autoware_auto_perception_msgs::msg::TrackedObjects & output) +{ + if (objects_buffer_.empty()) { + return false; + } + + // Get the objects closest to the target time. + const auto target_objects_iter = std::min_element( + objects_buffer_.cbegin(), objects_buffer_.cend(), + [&time]( + autoware_auto_perception_msgs::msg::TrackedObjects first, + autoware_auto_perception_msgs::msg::TrackedObjects second) { + return std::fabs((time - first.header.stamp).seconds()) < + std::fabs((time - second.header.stamp).seconds()); + }); + + // Estimate the pose of the object at the target time + const auto dt = time - target_objects_iter->header.stamp; + output.header.frame_id = target_objects_iter->header.frame_id; + output.header.stamp = time; + for (const auto & object : target_objects_iter->objects) { + const auto & pose_with_covariance = object.kinematics.pose_with_covariance; + const auto & x = pose_with_covariance.pose.position.x; + const auto & y = pose_with_covariance.pose.position.y; + const auto & z = pose_with_covariance.pose.position.z; + const float yaw = tf2::getYaw(pose_with_covariance.pose.orientation); + const auto & twist = object.kinematics.twist_with_covariance.twist; + const float & vx = twist.linear.x; + const float & wz = twist.angular.z; + + // build output + autoware_auto_perception_msgs::msg::TrackedObject estimated_object; + estimated_object.object_id = object.object_id; + estimated_object.existence_probability = object.existence_probability; + estimated_object.classification = object.classification; + estimated_object.shape = object.shape; + estimated_object.kinematics.pose_with_covariance.pose.position.x = + x + vx * std::cos(yaw) * dt.seconds(); + estimated_object.kinematics.pose_with_covariance.pose.position.y = + y + vx * std::sin(yaw) * dt.seconds(); + estimated_object.kinematics.pose_with_covariance.pose.position.z = z; + const float yaw_hat = autoware_utils::normalizeRadian(yaw + wz * dt.seconds()); + estimated_object.kinematics.pose_with_covariance.pose.orientation = + autoware_utils::createQuaternionFromYaw(yaw_hat); + output.objects.push_back(estimated_object); + } + return true; +} + +DetectionByTracker::DetectionByTracker(const rclcpp::NodeOptions & node_options) +: rclcpp::Node("detection_by_tracker", node_options), + tf_buffer_(this->get_clock()), + tf_listener_(tf_buffer_) +{ + // Create publishers and subscribers + trackers_sub_ = create_subscription( + "~/input/tracked_objects", rclcpp::QoS{1}, + std::bind(&TrackerHandler::onTrackedObjects, &tracker_handler_, std::placeholders::_1)); + initial_objects_sub_ = + create_subscription( + "~/input/initial_objects", rclcpp::QoS{1}, + std::bind(&DetectionByTracker::onObjects, this, std::placeholders::_1)); + objects_pub_ = create_publisher( + "~/output", rclcpp::QoS{1}); + + shape_estimator_ = std::make_shared(true, true); + cluster_ = std::make_shared( + false, 10, 10000, 0.7, 0.3, 0); +} + +void DetectionByTracker::onObjects( + const autoware_perception_msgs::msg::DetectedObjectsWithFeature::ConstSharedPtr input_msg) +{ + autoware_auto_perception_msgs::msg::DetectedObjects detected_objects; + detected_objects.header = input_msg->header; + + // get objects from tracking module + autoware_auto_perception_msgs::msg::TrackedObjects tracked_objects; + { + autoware_auto_perception_msgs::msg::TrackedObjects objects; + const bool available_trackers = + tracker_handler_.estimateTrackedObjects(input_msg->header.stamp, objects); + if ( + !available_trackers || + !transformTrackedObjects(objects, input_msg->header.frame_id, tf_buffer_, tracked_objects)) { + objects_pub_->publish(detected_objects); + return; + } + } + + // to simplify post processes, convert tracked_objects to DetectedObjects message. + autoware_auto_perception_msgs::msg::DetectedObjects tracked_objects_dt; + tracked_objects_dt = toDetectedObjects(tracked_objects); + + // merge over segmented objects + autoware_perception_msgs::msg::DetectedObjectsWithFeature merged_objects; + autoware_auto_perception_msgs::msg::DetectedObjects no_found_tracked_objects; + mergeOverSegmentedObjects( + tracked_objects_dt, *input_msg, no_found_tracked_objects, merged_objects); + + // divide under segmented objects + autoware_perception_msgs::msg::DetectedObjectsWithFeature divided_objects; + autoware_auto_perception_msgs::msg::DetectedObjects temp_no_found_tracked_objects; + divideUnderSegmentedObjects( + no_found_tracked_objects, *input_msg, temp_no_found_tracked_objects, divided_objects); + + // merge under/over segmented objects to build output objects + for (const auto & merged_object : merged_objects.feature_objects) { + detected_objects.objects.push_back(merged_object.object); + } + for (const auto & divided_object : divided_objects.feature_objects) { + detected_objects.objects.push_back(divided_object.object); + } + + objects_pub_->publish(detected_objects); +} + +void DetectionByTracker::divideUnderSegmentedObjects( + const autoware_auto_perception_msgs::msg::DetectedObjects & tracked_objects, + const autoware_perception_msgs::msg::DetectedObjectsWithFeature & in_cluster_objects, + autoware_auto_perception_msgs::msg::DetectedObjects & out_no_found_tracked_objects, + autoware_perception_msgs::msg::DetectedObjectsWithFeature & out_objects) +{ + constexpr float recall_min_threshold = 0.4; + constexpr float precision_max_threshold = 0.5; + constexpr float max_search_range = 6.0; + constexpr float min_score_threshold = 0.4; + + out_objects.header = in_cluster_objects.header; + out_no_found_tracked_objects.header = tracked_objects.header; + + for (const auto & tracked_object : tracked_objects.objects) { + std::optional + highest_score_divided_object = std::nullopt; + float highest_score = 0.0; + + for (const auto & initial_object : in_cluster_objects.feature_objects) { + // search near object + const float distance = autoware_utils::calcDistance2d( + tracked_object.kinematics.pose_with_covariance.pose, + initial_object.object.kinematics.pose_with_covariance.pose); + if (max_search_range < distance) { + continue; + } + // detect under segmented cluster + const float recall = utils::get2dRecall(initial_object.object, tracked_object); + const float precision = utils::get2dPrecision(initial_object.object, tracked_object); + const bool is_under_segmented = + (recall_min_threshold < recall && precision < precision_max_threshold); + if (!is_under_segmented) { + continue; + } + // optimize clustering + autoware_perception_msgs::msg::DetectedObjectWithFeature divided_object; + float score = optimizeUnderSegmentedObject( + tracked_object, initial_object.feature.cluster, divided_object); + if (score < min_score_threshold) { + continue; + } + + if (highest_score < score) { + highest_score = score; + highest_score_divided_object = divided_object; + } + } + if (highest_score_divided_object) { // found + out_objects.feature_objects.push_back(highest_score_divided_object.value()); + } else { // not found + out_no_found_tracked_objects.objects.push_back(tracked_object); + } + } +} + +float DetectionByTracker::optimizeUnderSegmentedObject( + const autoware_auto_perception_msgs::msg::DetectedObject & target_object, + const sensor_msgs::msg::PointCloud2 & under_segmented_cluster, + autoware_perception_msgs::msg::DetectedObjectWithFeature & output) +{ + constexpr float iter_rate = 0.8; + constexpr int iter_max_count = 5; + constexpr float initial_cluster_range = 0.7; + float cluster_range = initial_cluster_range; + constexpr float initial_voxel_size = initial_cluster_range / 2.0f; + float voxel_size = initial_voxel_size; + + // initialize clustering parameters + euclidean_cluster::VoxelGridBasedEuclideanCluster cluster( + false, 4, 10000, initial_cluster_range, initial_voxel_size, 0); + + // convert to pcl + pcl::PointCloud::Ptr pcl_cluster(new pcl::PointCloud); + pcl::fromROSMsg(under_segmented_cluster, *pcl_cluster); + + // iterate to find best fit divided object + float highest_iou = 0.0; + autoware_perception_msgs::msg::DetectedObjectWithFeature highest_iou_object; + for (int iter_count = 0; iter_count < iter_max_count; + ++iter_count, cluster_range *= iter_rate, voxel_size *= iter_rate) { + // divide under segmented cluster + std::vector> divided_clusters; + cluster.setTolerance(cluster_range); + cluster.setVoxelLeafSize(voxel_size); + cluster.cluster(pcl_cluster, divided_clusters); + + // find highest iou object in divided clusters + float highest_iou_in_current_iter = 0.0f; + autoware_perception_msgs::msg::DetectedObjectWithFeature highest_iou_object_in_current_iter; + highest_iou_object_in_current_iter.object.classification = target_object.classification; + for (const auto & divided_cluster : divided_clusters) { + bool is_shape_estimated = shape_estimator_->estimateShapeAndPose( + target_object.classification.front().label, divided_cluster, + tf2::getYaw(target_object.kinematics.pose_with_covariance.pose.orientation), + highest_iou_object_in_current_iter.object.shape, + highest_iou_object_in_current_iter.object.kinematics.pose_with_covariance.pose); + if (!is_shape_estimated) { + continue; + } + const float iou = utils::get2dIoU(highest_iou_object_in_current_iter.object, target_object); + if (highest_iou_in_current_iter < iou) { + highest_iou_in_current_iter = iou; + setClusterInObjectWithFeature( + under_segmented_cluster.header, divided_cluster, highest_iou_object_in_current_iter); + } + } + + // finish iteration when current score is under previous score + if (highest_iou_in_current_iter < highest_iou) { + break; + } + + // copy for next iteration + highest_iou = highest_iou_in_current_iter; + highest_iou_object = highest_iou_object_in_current_iter; + } + + // build output + highest_iou_object.object.classification = target_object.classification; + // TODO(yukkysaito): It is necessary to consider appropriate values in the future. + highest_iou_object.object.existence_probability = 0.1f; + output = highest_iou_object; + return highest_iou; +} + +void DetectionByTracker::mergeOverSegmentedObjects( + const autoware_auto_perception_msgs::msg::DetectedObjects & tracked_objects, + const autoware_perception_msgs::msg::DetectedObjectsWithFeature & in_cluster_objects, + autoware_auto_perception_msgs::msg::DetectedObjects & out_no_found_tracked_objects, + autoware_perception_msgs::msg::DetectedObjectsWithFeature & out_objects) +{ + constexpr float precision_threshold = 0.5; + constexpr float max_search_range = 5.0; + out_objects.header = in_cluster_objects.header; + out_no_found_tracked_objects.header = tracked_objects.header; + + for (const auto & tracked_object : tracked_objects.objects) { + // extend shape + autoware_auto_perception_msgs::msg::DetectedObject extended_tracked_object = tracked_object; + extended_tracked_object.shape = extendShape(tracked_object.shape, /*scale*/ 1.1); + + pcl::PointCloud pcl_merged_cluster; + for (const auto & initial_object : in_cluster_objects.feature_objects) { + const float distance = autoware_utils::calcDistance2d( + tracked_object.kinematics.pose_with_covariance.pose, + initial_object.object.kinematics.pose_with_covariance.pose); + + if (max_search_range < distance) { + continue; + } + + // If there is an initial object in the tracker, it will be merged. + const float precision = utils::get2dPrecision(initial_object.object, extended_tracked_object); + if (precision < precision_threshold) { + continue; + } + pcl::PointCloud pcl_cluster; + pcl::fromROSMsg(initial_object.feature.cluster, pcl_cluster); + pcl_merged_cluster += pcl_cluster; + } + + if (pcl_merged_cluster.points.empty()) { // if clusters aren't found + out_no_found_tracked_objects.objects.push_back(tracked_object); + continue; + } + + // build output clusters + autoware_perception_msgs::msg::DetectedObjectWithFeature feature_object; + feature_object.object.existence_probability = 0.1f; + feature_object.object.classification = tracked_object.classification; + + bool is_shape_estimated = shape_estimator_->estimateShapeAndPose( + tracked_object.classification.front().label, pcl_merged_cluster, + tf2::getYaw(tracked_object.kinematics.pose_with_covariance.pose.orientation), + feature_object.object.shape, feature_object.object.kinematics.pose_with_covariance.pose); + if (!is_shape_estimated) { + out_no_found_tracked_objects.objects.push_back(tracked_object); + continue; + } + + setClusterInObjectWithFeature(in_cluster_objects.header, pcl_merged_cluster, feature_object); + out_objects.feature_objects.push_back(feature_object); + } +} + +#include +RCLCPP_COMPONENTS_REGISTER_NODE(DetectionByTracker) diff --git a/perception/detection_by_tracker/src/utils.cpp b/perception/detection_by_tracker/src/utils.cpp new file mode 100644 index 0000000000000..a4a015ad846ac --- /dev/null +++ b/perception/detection_by_tracker/src/utils.cpp @@ -0,0 +1,218 @@ +// Copyright 2020 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "detection_by_tracker/utils.hpp" + +#include + +#include +#include + +#include +#include + +namespace utils +{ +void toPolygon2d( + const autoware_auto_perception_msgs::msg::DetectedObject & object, + autoware_utils::Polygon2d & output); +bool isClockWise(const autoware_utils::Polygon2d & polygon); +autoware_utils::Polygon2d inverseClockWise(const autoware_utils::Polygon2d & polygon); + +double getArea(const autoware_auto_perception_msgs::msg::Shape & shape) +{ + double area = 0.0; + if (shape.type == autoware_auto_perception_msgs::msg::Shape::BOUNDING_BOX) { + area = getRectangleArea(shape.dimensions); + } else if (shape.type == autoware_auto_perception_msgs::msg::Shape::CYLINDER) { + area = getCircleArea(shape.dimensions); + } else if (shape.type == autoware_auto_perception_msgs::msg::Shape::POLYGON) { + area = getPolygonArea(shape.footprint); + } + return area; +} + +double getPolygonArea(const geometry_msgs::msg::Polygon & footprint) +{ + double area = 0.0; + + for (size_t i = 0; i < footprint.points.size(); ++i) { + size_t j = (i + 1) % footprint.points.size(); + area += 0.5 * (footprint.points.at(i).x * footprint.points.at(j).y - + footprint.points.at(j).x * footprint.points.at(i).y); + } + + return area; +} + +double getRectangleArea(const geometry_msgs::msg::Vector3 & dimensions) +{ + return static_cast(dimensions.x * dimensions.y); +} + +double getCircleArea(const geometry_msgs::msg::Vector3 & dimensions) +{ + return static_cast((dimensions.x / 2.0) * (dimensions.x / 2.0) * M_PI); +} + +double get2dIoU( + const autoware_auto_perception_msgs::msg::DetectedObject & object1, + const autoware_auto_perception_msgs::msg::DetectedObject & object2) +{ + autoware_utils::Polygon2d polygon1, polygon2; + toPolygon2d(object1, polygon1); + toPolygon2d(object2, polygon2); + + std::vector union_polygons; + std::vector intersection_polygons; + boost::geometry::union_(polygon1, polygon2, union_polygons); + boost::geometry::intersection(polygon1, polygon2, intersection_polygons); + + double union_area = 0.0; + double intersection_area = 0.0; + for (const auto & union_polygon : union_polygons) { + union_area += boost::geometry::area(union_polygon); + } + for (const auto & intersection_polygon : intersection_polygons) { + intersection_area += boost::geometry::area(intersection_polygon); + } + const double iou = union_area < 0.01 ? 0.0 : std::min(1.0, intersection_area / union_area); + return iou; +} + +double get2dPrecision( + const autoware_auto_perception_msgs::msg::DetectedObject & source_object, + const autoware_auto_perception_msgs::msg::DetectedObject & target_object) +{ + autoware_utils::Polygon2d source_polygon, target_polygon; + toPolygon2d(source_object, source_polygon); + toPolygon2d(target_object, target_polygon); + + std::vector intersection_polygons; + boost::geometry::intersection(source_polygon, target_polygon, intersection_polygons); + + double intersection_area = 0.0; + double source_area = 0.0; + for (const auto & intersection_polygon : intersection_polygons) { + intersection_area += boost::geometry::area(intersection_polygon); + } + source_area = boost::geometry::area(source_polygon); + const double precision = std::min(1.0, intersection_area / source_area); + return precision; +} + +double get2dRecall( + const autoware_auto_perception_msgs::msg::DetectedObject & source_object, + const autoware_auto_perception_msgs::msg::DetectedObject & target_object) +{ + autoware_utils::Polygon2d source_polygon, target_polygon; + toPolygon2d(source_object, source_polygon); + toPolygon2d(target_object, target_polygon); + + std::vector intersection_polygons; + boost::geometry::union_(source_polygon, target_polygon, intersection_polygons); + + double intersection_area = 0.0; + double target_area = 0.0; + for (const auto & intersection_polygon : intersection_polygons) { + intersection_area += boost::geometry::area(intersection_polygon); + } + target_area += boost::geometry::area(target_polygon); + const double recall = std::min(1.0, intersection_area / target_area); + return recall; +} + +autoware_utils::Polygon2d inverseClockWise(const autoware_utils::Polygon2d & polygon) +{ + autoware_utils::Polygon2d inverted_polygon; + for (int i = polygon.outer().size() - 1; 0 <= i; --i) { + inverted_polygon.outer().push_back(polygon.outer().at(i)); + } + return inverted_polygon; +} + +bool isClockWise(const autoware_utils::Polygon2d & polygon) +{ + const int n = polygon.outer().size(); + const double x_offset = polygon.outer().at(0).x(); + const double y_offset = polygon.outer().at(0).y(); + double sum = 0.0; + for (std::size_t i = 0; i < polygon.outer().size(); ++i) { + sum += + (polygon.outer().at(i).x() - x_offset) * (polygon.outer().at((i + 1) % n).y() - y_offset) - + (polygon.outer().at(i).y() - y_offset) * (polygon.outer().at((i + 1) % n).x() - x_offset); + } + + return sum < 0.0; +} + +void toPolygon2d( + const autoware_auto_perception_msgs::msg::DetectedObject & object, + autoware_utils::Polygon2d & output) +{ + if (object.shape.type == autoware_auto_perception_msgs::msg::Shape::BOUNDING_BOX) { + const auto & pose = object.kinematics.pose_with_covariance.pose; + const double yaw = autoware_utils::normalizeRadian(tf2::getYaw(pose.orientation)); + Eigen::Matrix2d rotation; + rotation << std::cos(yaw), -std::sin(yaw), std::sin(yaw), std::cos(yaw); + Eigen::Vector2d offset0, offset1, offset2, offset3; + offset0 = rotation * + Eigen::Vector2d(object.shape.dimensions.x * 0.5f, object.shape.dimensions.y * 0.5f); + offset1 = rotation * + Eigen::Vector2d(object.shape.dimensions.x * 0.5f, -object.shape.dimensions.y * 0.5f); + offset2 = rotation * + Eigen::Vector2d(-object.shape.dimensions.x * 0.5f, -object.shape.dimensions.y * 0.5f); + offset3 = rotation * + Eigen::Vector2d(-object.shape.dimensions.x * 0.5f, object.shape.dimensions.y * 0.5f); + output.outer().push_back(boost::geometry::make( + pose.position.x + offset0.x(), pose.position.y + offset0.y())); + output.outer().push_back(boost::geometry::make( + pose.position.x + offset1.x(), pose.position.y + offset1.y())); + output.outer().push_back(boost::geometry::make( + pose.position.x + offset2.x(), pose.position.y + offset2.y())); + output.outer().push_back(boost::geometry::make( + pose.position.x + offset3.x(), pose.position.y + offset3.y())); + output.outer().push_back(output.outer().front()); + } else if (object.shape.type == autoware_auto_perception_msgs::msg::Shape::CYLINDER) { + const auto & center = object.kinematics.pose_with_covariance.pose.position; + const auto & radius = object.shape.dimensions.x * 0.5; + constexpr int n = 6; + for (int i = 0; i < n; ++i) { + Eigen::Vector2d point; + point.x() = std::cos( + (static_cast(i) / static_cast(n)) * 2.0 * M_PI + + M_PI / static_cast(n)) * + radius + + center.x; + point.y() = std::sin( + (static_cast(i) / static_cast(n)) * 2.0 * M_PI + + M_PI / static_cast(n)) * + radius + + center.y; + output.outer().push_back( + boost::geometry::make(point.x(), point.y())); + } + output.outer().push_back(output.outer().front()); + } else if (object.shape.type == autoware_auto_perception_msgs::msg::Shape::POLYGON) { + const auto & pose = object.kinematics.pose_with_covariance.pose; + for (const auto & point : object.shape.footprint.points) { + output.outer().push_back(boost::geometry::make( + pose.position.x + point.x, pose.position.y + point.y)); + } + output.outer().push_back(output.outer().front()); + } + output = isClockWise(output) ? output : inverseClockWise(output); +} + +} // namespace utils