Skip to content

Commit

Permalink
improve the orientation filter, add tests (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikeferguson committed Jun 17, 2021
1 parent ec54f94 commit e581463
Show file tree
Hide file tree
Showing 5 changed files with 448 additions and 32 deletions.
12 changes: 12 additions & 0 deletions graceful_controller_ros/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ generate_dynamic_reconfigure_options(
)

catkin_package(
INCLUDE_DIRS
include
CATKIN_DEPENDS
base_local_planner
costmap_2d
Expand All @@ -48,11 +50,13 @@ if (CATKIN_ENABLE_TESTING AND ENABLE_COVERAGE_TESTING)
endif()

include_directories(
include
${catkin_INCLUDE_DIRS}
)

add_library(graceful_controller_ros
src/graceful_controller_ros.cpp
src/orientation_tools.cpp
)
target_link_libraries(graceful_controller_ros
${catkin_LIBRARIES}
Expand All @@ -75,6 +79,14 @@ if(CATKIN_ENABLE_TESTING)
add_dependencies(tests graceful_controller_tests)
add_rostest(test/graceful_controller.test)

catkin_add_gtest(orientation_filter_tests
src/orientation_tools.cpp
test/orientation_tools_tests.cpp
)
target_link_libraries(orientation_filter_tests
${catkin_LIBRARIES}
)

if(ENABLE_COVERAGE_TESTING)
set(COVERAGE_EXCLUDES "*/${PROJECT_NAME}/test*")
add_code_coverage(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*********************************************************************
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2021, Michael Ferguson
* Copyright (c) 2009, Willow Garage, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of Willow Garage, Inc. nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* Author: Michael Ferguson
*********************************************************************/

#ifndef GRACEFUL_CONTROLLER_ROS_ORIENTATION_TOOLS_HPP
#define GRACEFUL_CONTROLLER_ROS_ORIENTATION_TOOLS_HPP

#include <vector>
#include <geometry_msgs/PoseStamped.h>

namespace graceful_controller
{

/**
* @brief Add orientation to each pose in a path.
* @param path The path to have orientations added.
* @returns The oriented path.
*/
std::vector<geometry_msgs::PoseStamped>
addOrientations(const std::vector<geometry_msgs::PoseStamped>& path);

/**
* @brief Filter a path for orientation noise.
* @param path The path to be filtered.
* @param yaw_tolerance Maximum deviation allowed before a pose is filtered.
* @returns The filtered path.
*/
std::vector<geometry_msgs::PoseStamped>
applyOrientationFilter(const std::vector<geometry_msgs::PoseStamped>& path,
double yaw_tolerance);

} // namespace graceful_controller

#endif // GRACEFUL_CONTROLLER_ROS_ORIENTATION_TOOLS_HPP
36 changes: 4 additions & 32 deletions graceful_controller_ros/src/graceful_controller_ros.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#include <base_local_planner/odometry_helper_ros.h>
#include <costmap_2d/footprint.h>
#include <graceful_controller/graceful_controller.hpp>
#include <graceful_controller_ros/orientation_tools.hpp>
#include <std_msgs/Float32.h>
#include <tf2/utils.h>
#include <tf2_geometry_msgs/tf2_geometry_msgs.h>
Expand Down Expand Up @@ -543,42 +544,13 @@ class GracefulControllerROS : public nav_core::BaseLocalPlanner

// We need orientations on our poses
std::vector<geometry_msgs::PoseStamped> oriented_plan;
oriented_plan.resize(plan.size());

// Copy the only oriented pose
oriented_plan.back() = plan.back();

// For each pose, point at the next one
for (size_t i = 0; i < oriented_plan.size() - 1; ++i)
{
oriented_plan[i] = plan[i];
double dx = plan[i+1].pose.position.x - plan[i].pose.position.x;
double dy = plan[i+1].pose.position.y - plan[i].pose.position.y;
double yaw = std::atan2(dy, dx);
oriented_plan[i].pose.orientation.z = sin(yaw / 2.0);
oriented_plan[i].pose.orientation.w = cos(yaw / 2.0);
}
oriented_plan = addOrientations(plan);

// Filter noisy orientations
std::vector<geometry_msgs::PoseStamped> filtered_plan;
filtered_plan.reserve(oriented_plan.size());
filtered_plan.push_back(oriented_plan.front());
for (size_t i = 1; i < oriented_plan.size() - 1; ++i)
{
// Compare to before and after
if (angles::shortest_angular_distance(tf2::getYaw(filtered_plan.back().pose.orientation),
tf2::getYaw(oriented_plan[i].pose.orientation)) < yaw_filter_tolerance_)
{
filtered_plan.push_back(oriented_plan[i]);
}
else
{
ROS_DEBUG_NAMED("graceful_controller", "Filtering pose %lu", i);
}
}
filtered_plan.push_back(oriented_plan.back());
ROS_DEBUG_NAMED("graceful_controller", "Filtered %lu points from plan", oriented_plan.size() - filtered_plan.size());
filtered_plan = applyOrientationFilter(oriented_plan, yaw_filter_tolerance_);

// Store the plan for computeVelocityCommands
if (planner_util_.setPlan(filtered_plan))
{
has_new_path_ = true;
Expand Down
131 changes: 131 additions & 0 deletions graceful_controller_ros/src/orientation_tools.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*********************************************************************
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2021, Michael Ferguson
* Copyright (c) 2009, Willow Garage, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of Willow Garage, Inc. nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* Author: Michael Ferguson
*********************************************************************/

#include <ros/ros.h>
#include <angles/angles.h>
#include <tf2/utils.h> // getYaw
#include "graceful_controller_ros/orientation_tools.hpp"

namespace graceful_controller
{

// Helper function to get yaw if "from" were to point towards "to"
double getRelativeYaw(const geometry_msgs::PoseStamped& from,
const geometry_msgs::PoseStamped& to)
{
double dx = to.pose.position.x - from.pose.position.x;
double dy = to.pose.position.y - from.pose.position.y;
return std::atan2(dy, dx);
}

void setYaw(geometry_msgs::PoseStamped& pose, double yaw)
{
pose.pose.orientation.z = sin(yaw / 2.0);
pose.pose.orientation.w = cos(yaw / 2.0);
}

std::vector<geometry_msgs::PoseStamped>
addOrientations(const std::vector<geometry_msgs::PoseStamped>& path)
{
std::vector<geometry_msgs::PoseStamped> oriented_path;
oriented_path.resize(path.size());

// The last pose will already be oriented since it is our goal
oriented_path.back() = path.back();

// For each pose, point at the next one
for (size_t i = 0; i < oriented_path.size() - 1; ++i)
{
oriented_path[i] = path[i];
double yaw = getRelativeYaw(path[i], path[i + 1]);
setYaw(oriented_path[i], yaw);
}

return oriented_path;
}

std::vector<geometry_msgs::PoseStamped>
applyOrientationFilter(const std::vector<geometry_msgs::PoseStamped>& path,
double yaw_tolerance)
{
std::vector<geometry_msgs::PoseStamped> filtered_path;
filtered_path.reserve(path.size());

// Always keep the first pose
filtered_path.push_back(path.front());

// Possibly filter some intermediate poses
for (size_t i = 1; i < path.size() - 1; ++i)
{
// Get the yaw angle if the previous pose were to be pointing at this pose
// We need to recompute because we might have dropped poses
double yaw_previous = getRelativeYaw(filtered_path.back(), path[i]);

// Get the yaw angle of this pose pointing at next pose
double yaw_this = tf2::getYaw(path[i].pose.orientation);

// Get the yaw angle if previous pose were to be pointing at next pose, filtering this pose
double yaw_without = getRelativeYaw(filtered_path.back(), path[i+1]);

// Determine if this pose is far off a direct line between previous and next pose
if (fabs(angles::shortest_angular_distance(yaw_previous, yaw_without)) < yaw_tolerance &&
fabs(angles::shortest_angular_distance(yaw_this, yaw_without)) < yaw_tolerance)
{
// Update previous heading in case we dropped some poses
setYaw(filtered_path.back(), yaw_previous);
// Add this pose to the filtered plan
filtered_path.push_back(path[i]);
}
else
{
// Sorry pose, the plan is better without you :(
ROS_DEBUG_NAMED("orientation_filter", "Filtering pose %lu", i);
}
}

// Reset heading of what will be penultimate pose, in case we dropped some poses
setYaw(filtered_path.back(), getRelativeYaw(filtered_path.back(), path.back()));

// Always add the last pose, since this is our goal
filtered_path.push_back(path.back());

ROS_DEBUG_NAMED("orientation_filter", "Filtered %lu points from plan", path.size() - filtered_path.size());
return filtered_path;
}

} // namespace graceful_controller
Loading

0 comments on commit e581463

Please sign in to comment.