Skip to content

Commit fe4cabd

Browse files
author
corot
committed
Add minBoundingRect function
1 parent b7d904f commit fe4cabd

File tree

5 files changed

+164
-121
lines changed

5 files changed

+164
-121
lines changed

costmap_2d/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ add_library(costmap_2d
9393
src/costmap_math.cpp
9494
src/footprint.cpp
9595
src/costmap_layer.cpp
96-
src/rotating_calipers.cpp
9796
)
9897
add_dependencies(costmap_2d ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
9998
target_link_libraries(costmap_2d

costmap_2d/include/costmap_2d/costmap_math.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,4 @@ bool intersects(std::vector<geometry_msgs::Point>& polygon, float testx, float t
6666

6767
bool intersects(std::vector<geometry_msgs::Point>& polygon1, std::vector<geometry_msgs::Point>& polygon2);
6868

69-
double orientation(double x0, double y0, double x1, double y1);
70-
71-
double positiveAngle(double x0, double y0, double x1, double y1);
72-
7369
#endif // COSTMAP_2D_COSTMAP_MATH_H_

costmap_2d/include/costmap_2d/footprint.h

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
#include <geometry_msgs/Point.h>
4545
#include <geometry_msgs/Point32.h>
4646

47+
#include <Eigen/Dense>
48+
4749
namespace costmap_2d
4850
{
4951

@@ -57,25 +59,40 @@ namespace costmap_2d
5759
void calculateMinAndMaxDistances(const std::vector<geometry_msgs::Point>& footprint,
5860
double& min_dist, double& max_dist);
5961

62+
typedef struct
63+
{
64+
double rot_angle;
65+
double area;
66+
double width;
67+
double height;
68+
geometry_msgs::Point center;
69+
std::array<geometry_msgs::Point, 4> corners;
70+
} BoundingRect;
71+
6072
/**
61-
* @brief Calculate the orientation at which the footprint will sweep the smallest area when moving along +x direction
62-
* @warning This function only works under two assumptions:
63-
* * the footprint is symmetric wrt the x axis
64-
* * the closest edge is approximately parallel to either x or y axis
73+
* @brief Find the minimum-area bounding box of a footprint
74+
* We first find the rotation angles of each edge of the convex polygon, then tests the area
75+
* of a bounding box aligned with the unique angles in 90 degrees of the 1st Quadrant.
6576
* @param footprint The footprint to examine
66-
* @return Minimum footprint sweeping area orientation
77+
* @return strut containing the rotation angle, area, width, height, center and corners of the
78+
* minimum-area bounding box
6779
*/
68-
double minSweepingAreaOrientation(const std::vector<geometry_msgs::Point>& footprint);
80+
BoundingRect minBoundingRect(const std::vector<geometry_msgs::Point>& points);
6981

7082
/**
7183
* @brief Convert Point32 to Point
7284
*/
7385
geometry_msgs::Point toPoint(geometry_msgs::Point32 pt);
7486

87+
/**
88+
* @brief Convert Eigen Vector2d to Point
89+
*/
90+
geometry_msgs::Point toPoint(Eigen::Vector2d pt);
91+
7592
/**
7693
* @brief Convert Point to Point32
7794
*/
78-
geometry_msgs::Point32 toPoint32(geometry_msgs::Point pt);
95+
geometry_msgs::Point32 toPoint32(geometry_msgs::Point pt);
7996

8097
/**
8198
* @brief Convert vector of Points to Polygon msg
@@ -87,6 +104,11 @@ geometry_msgs::Polygon toPolygon(std::vector<geometry_msgs::Point> pt
87104
*/
88105
std::vector<geometry_msgs::Point> toPointVector(geometry_msgs::Polygon polygon);
89106

107+
/**
108+
* @brief Return a list of numbers as a space-separated string.
109+
*/
110+
std::string toString(const std::vector<double>& numbers);
111+
90112
/**
91113
* @brief Given a pose and base footprint, build the oriented footprint of the robot (list of Points)
92114
* @param x The x position of the robot

costmap_2d/src/costmap_math.cpp

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,3 @@ bool intersects(std::vector<geometry_msgs::Point>& polygon1, std::vector<geometr
8787
{
8888
return intersects_helper(polygon1, polygon2) || intersects_helper(polygon2, polygon1);
8989
}
90-
91-
double orientation(double x0, double y0, double x1, double y1)
92-
{
93-
const double dx = x1 - x0;
94-
const double dy = y1 - y0;
95-
return atan2(dy, dx);
96-
}
97-
98-
double positiveAngle(double x0, double y0, double x1, double y1)
99-
{
100-
const double angle1 = atan2(y0, x0);
101-
const double angle2 = atan2(y1, x1);
102-
return std::remainder(angle2 - angle1, M_PI);
103-
}

costmap_2d/src/footprint.cpp

Lines changed: 135 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@
2727
* POSSIBILITY OF SUCH DAMAGE.
2828
*/
2929

30-
#include<costmap_2d/costmap_math.h>
30+
#include <unordered_set>
31+
32+
#include <costmap_2d/costmap_math.h>
3133
#include <boost/tokenizer.hpp>
3234
#include <boost/foreach.hpp>
3335
#include <boost/algorithm/string.hpp>
3436
#include <costmap_2d/footprint.h>
3537
#include <costmap_2d/array_parser.h>
36-
#include<geometry_msgs/Point32.h>
38+
#include <geometry_msgs/Point32.h>
3739

3840
namespace costmap_2d
3941
{
@@ -66,107 +68,130 @@ void calculateMinAndMaxDistances(const std::vector<geometry_msgs::Point>& footpr
6668
max_dist = std::max(max_dist, std::max(vertex_dist, edge_dist));
6769
}
6870

69-
/* p[] is in standard form, ie, counterclockwise order,
70-
distinct vertices, no collinear vertices.
71-
ANGLE(m, n) is a procedure that returns the clockwise angle
72-
swept out by a ray as it rotates from a position parallel
73-
to the directed segment Pm,Pm+1 to a position parallel to Pn, Pn+1
74-
We assume all indices are reduced to mod N (so that N+1 = 1).
75-
*/
76-
std::vector<std::pair<int, int>> getAllAntipodalPairs(const std::vector<geometry_msgs::Point>& footprint)
71+
BoundingRect minBoundingRect(const std::vector<geometry_msgs::Point>& points)
7772
{
78-
std::vector<std::pair<int, int>> antipodal_pairs;
79-
80-
// Find first antipodal pair by locating vertex opposite P1
81-
int i = 0;
82-
int j = 1;
83-
while (positiveAngle(footprint[i].x, footprint[i].y, footprint[j].x, footprint[j].y) < M_PI)
84-
++j;
85-
antipodal_pairs.push_back({ i, j });
86-
87-
// Loop on j until all of P has been scanned
88-
while (j < footprint.size())
73+
Eigen::MatrixX2d hull_points_2d(points.size(), 2); // empty 2 column array
74+
for (size_t i = 0; i < points.size(); ++i)
75+
hull_points_2d.row(i) << points[i].x, points[i].y;
76+
ROS_INFO_STREAM("Input convex hull points:\n" << hull_points_2d);
77+
78+
// Compute edges (x2-x1,y2-y1)
79+
Eigen::MatrixX2d edges(hull_points_2d.rows() - 1, 2); // empty 2 column array
80+
edges.setZero();
81+
for (size_t i = 0; i < edges.rows(); ++i)
8982
{
90-
bool last_pt = j == (footprint.size() - 1);
91-
double a = 2 * M_PI - positiveAngle(footprint[i].x, footprint[i].y, footprint[j].x, footprint[j].y);
92-
if (a == M_PI) // Pi Pi+1 and Pj Pj+1 are parallel
93-
{
94-
antipodal_pairs.push_back({ i + 1, j });
95-
antipodal_pairs.push_back({ i, last_pt ? 0 : j + 1 });
96-
antipodal_pairs.push_back({ i + 1, last_pt ? 0 : j + 1 });
97-
98-
// Notice that (i, j) has been added to the result before being the pivots, so no need to yield i,j
99-
++i;
100-
++j;
101-
}
102-
else if (a < M_PI) // Will touch Pi Pi+1 first
103-
{
104-
antipodal_pairs.push_back({ i + 1, j });
105-
++i;
106-
}
107-
else
108-
{
109-
antipodal_pairs.push_back({ i, last_pt ? 0 : j + 1 }); // Will touch Pj Pj+1 first
110-
++j;
111-
}
83+
double edge_x = hull_points_2d(i + 1, 0) - hull_points_2d(i, 0);
84+
double edge_y = hull_points_2d(i + 1, 1) - hull_points_2d(i, 1);
85+
edges.row(i) << edge_x, edge_y;
11286
}
113-
114-
return antipodal_pairs;
115-
}
116-
117-
double minSweepingAreaOrientation(const std::vector<geometry_msgs::Point>& footprint)
118-
{
119-
double min_dist = std::numeric_limits<double>::max();
120-
std::array<geometry_msgs::Point, 2> closest_edge;
121-
122-
if (footprint.size() <= 2)
87+
ROS_INFO_STREAM("Edges:\n" << edges);
88+
89+
// Calculate edge angles with atan2(y/x)
90+
std::vector<double> edge_angles(edges.rows()); // empty 1 column array
91+
for (size_t i = 0; i < edge_angles.size(); ++i)
92+
edge_angles[i] = std::atan2(edges.row(i)[1], edges.row(i)[0]);
93+
ROS_INFO_STREAM("Edge angles:\n" << toString(edge_angles));
94+
95+
// Check for angles in 1st quadrant
96+
for (size_t i = 0; i < edge_angles.size(); ++i)
97+
edge_angles[i] = std::fmod(edge_angles[i] + M_PI, M_PI_2); // want strictly positive answers
98+
ROS_INFO_STREAM("Edge angles in 1st Quadrant:\n" << toString(edge_angles));
99+
100+
// Remove duplicate angles
101+
std::unordered_set<float> s;
102+
auto end = std::remove_if(edge_angles.begin(), edge_angles.end(), [&s](double v) { return !s.insert(v).second; });
103+
edge_angles.erase(end, edge_angles.end());
104+
// std::sort(edge_angles.begin(), edge_angles.end());
105+
// auto ip = std::unique(edge_angles.begin(), edge_angles.end());
106+
//edge_angles.resize(std::distance(edge_angles.begin(), ip));
107+
ROS_INFO_STREAM("Unique edge angles:\n" << toString(edge_angles));
108+
109+
// Test each angle to find bounding box with the smallest area
110+
// rot_angle, area, width, height, min_x, max_x, min_y, max_y
111+
std::array<double, 8> min_bbox{ 0.0, DBL_MAX, 0.0, 0.0, 0.0, 0.0, 0.0, 0 };
112+
ROS_INFO_STREAM("Testing " << edge_angles.size() << " possible rotations for bounding box...");
113+
for (size_t i = 0; i < edge_angles.size(); ++i)
123114
{
124-
return NAN;
125-
}
126-
127-
// check the distance from the robot center point to each footprint edged and keep the closest one
128-
for (unsigned int i = 0; i < footprint.size() - 1; ++i)
129-
{
130-
double edge_dist = distanceToLine(0, 0, footprint[i].x, footprint[i].y, footprint[i + 1].x, footprint[i + 1].y);
131-
if (edge_dist < min_dist)
115+
// Create rotation matrix to shift points to baseline
116+
// R = [ cos(theta) , cos(theta-PI/2)
117+
// cos(theta+PI/2) , cos(theta) ]
118+
// clang-format off
119+
Eigen::Matrix<double, 2, 2> R;
120+
R << std::cos(edge_angles[i]), std::cos(edge_angles[i] - M_PI_2),
121+
std::cos(edge_angles[i] + M_PI_2), std::cos(edge_angles[i]);
122+
// clang-format on
123+
ROS_INFO_STREAM("Rotation matrix for " << edge_angles[i] << " is\n" << R);
124+
125+
// Apply this rotation to convex hull points
126+
Eigen::MatrixX2d rot_points = (R * hull_points_2d.transpose()).transpose(); // 2x2 * 2xn
127+
ROS_INFO_STREAM("Rotated hull points are\n" << rot_points);
128+
129+
// Find min/max x,y points
130+
const double min_x = rot_points.col(0).minCoeff();
131+
const double max_x = rot_points.col(0).maxCoeff();
132+
const double min_y = rot_points.col(1).minCoeff();
133+
const double max_y = rot_points.col(1).maxCoeff();
134+
ROS_INFO_STREAM("Min x: " << min_x << " Max x: " << max_x << " Min y: " << min_y << " Max y: " << max_y);
135+
136+
// Calculate height/width/area of this bounding rectangle
137+
const double width = max_x - min_x;
138+
const double height = max_y - min_y;
139+
const double area = width * height;
140+
ROS_INFO_STREAM("Bounding box " << i << ": width: " << width << " height: " << height << " area: " << area);
141+
142+
// Store the smallest rect found first (a simple convex hull might have 2 answers with same area)
143+
// Note that we require a non-neglectable difference to favor smaller rotations
144+
if (min_bbox[1] - area > 1e-3)
132145
{
133-
min_dist = edge_dist;
134-
closest_edge = { footprint[i], footprint[i + 1] };
146+
ROS_INFO_STREAM("Area " << min_bbox[1] << " -> " << area);
147+
min_bbox = { edge_angles[i], area, width, height, min_x, max_x, min_y, max_y };
135148
}
136149
}
150+
// Re-create rotation matrix for smallest rect
151+
// clang-format off
152+
const double angle = min_bbox[0];
153+
Eigen::Matrix<double, 2, 2> R;
154+
R << std::cos(angle), std::cos(angle - M_PI_2),
155+
std::cos(angle + M_PI_2), std::cos(angle);
156+
// clang-format on
157+
ROS_INFO_STREAM("Projection matrix:\n" << R);
158+
159+
// Project convex hull points onto rotated frame
160+
Eigen::MatrixX2d proj_points = (R * hull_points_2d.transpose()).transpose(); // 2x2 * 2xn
161+
ROS_INFO_STREAM("Project hull points are\n" << proj_points);
162+
163+
// min/max x,y points are against baseline
164+
const double min_x = min_bbox[4];
165+
const double max_x = min_bbox[5];
166+
const double min_y = min_bbox[6];
167+
const double max_y = min_bbox[7];
168+
ROS_INFO_STREAM("Min x: " << min_x << " Max x: " << max_x << " Min y: " << min_y << " Max y: " << max_y);
169+
170+
// Calculate center point and project onto rotated frame
171+
Eigen::Vector2d center{ (min_x + max_x) / 2.0, (min_y + max_y) / 2.0 };
172+
Eigen::Vector2d center_point = center.transpose() * R;
173+
ROS_INFO_STREAM("Bounding box center point:\n" << center_point);
174+
175+
// Calculate corner points and project onto rotated frame
176+
Eigen::Matrix<double, 4, 2> corner_points; //// = zeros((4, 2)) // empty 2 column array
177+
corner_points.row(0) = (Eigen::Vector2d{ max_x, min_y }.transpose() * R).transpose();
178+
corner_points.row(1) = (Eigen::Vector2d{ min_x, min_y }.transpose() * R).transpose();
179+
corner_points.row(2) = (Eigen::Vector2d{ min_x, max_y }.transpose() * R).transpose();
180+
corner_points.row(3) = (Eigen::Vector2d{ max_x, max_y }.transpose() * R).transpose();
181+
ROS_INFO_STREAM("Bounding box corner points:\n" << corner_points);
182+
183+
ROS_INFO_STREAM("Angle of rotation: " << angle << "rad " << angle * (180 / M_PI) << "deg");
184+
185+
BoundingRect result;
186+
result.rot_angle = angle;
187+
result.area = min_bbox[1];
188+
result.width = min_bbox[2];
189+
result.height = min_bbox[3];
190+
result.center.x = center_point.x();
191+
result.center.y = center_point.y();
192+
for (int i = 0; i < corner_points.rows(); ++i)
193+
result.corners[i] = toPoint(corner_points.row(i));
137194

138-
// we also need to do the last vertex and the first vertex
139-
if (distanceToLine(0, 0, footprint.back().x, footprint.back().y, footprint.front().x, footprint.front().y) < min_dist)
140-
{
141-
closest_edge = { footprint.back(), footprint.front() };
142-
}
143-
144-
// return the orientation of the closest edge, directed from back to front (+x axis direction)
145-
std::sort(closest_edge.begin(), closest_edge.end(),
146-
[](const geometry_msgs::Point& p1, const geometry_msgs::Point& p2) { return p1.x < p2.x; });
147-
// return orientation(closest_edge.front().x, closest_edge.front().y, closest_edge.back().x, closest_edge.back().y);
148-
double result1 = orientation(closest_edge.front().x, closest_edge.front().y, closest_edge.back().x, closest_edge.back().y);
149-
150-
std::vector<std::pair<int, int>> antipodal_pairs = getAllAntipodalPairs(footprint);
151-
double footprint_width = INFINITY;
152-
size_t closest_ap_pair = INFINITY;
153-
for (int i = 0; i < antipodal_pairs.size(); ++i)
154-
{
155-
const auto& ap_pair = antipodal_pairs[i];
156-
const geometry_msgs::Point& p1 = footprint[ap_pair.first];
157-
const geometry_msgs::Point& p2 = footprint[ap_pair.second];
158-
const double dist = distance(p1.x, p1.y, p2.x, p2.y);
159-
if (dist < footprint_width)
160-
{
161-
footprint_width = dist;
162-
closest_ap_pair = i;
163-
}
164-
}
165-
const geometry_msgs::Point& p1 = footprint[antipodal_pairs[closest_ap_pair].first];
166-
const geometry_msgs::Point& p2 = footprint[antipodal_pairs[closest_ap_pair].second];
167-
double result = orientation(p1.x, p1.y, p2.x, p2.y);
168-
169-
ROS_WARN_STREAM(result1 << " " <<result);
170195
return result;
171196
}
172197

@@ -188,6 +213,14 @@ geometry_msgs::Point toPoint(geometry_msgs::Point32 pt)
188213
return point;
189214
}
190215

216+
geometry_msgs::Point toPoint(Eigen::Vector2d pt)
217+
{
218+
geometry_msgs::Point point;
219+
point.x = pt.x();
220+
point.y = pt.y();
221+
return point;
222+
}
223+
191224
geometry_msgs::Polygon toPolygon(std::vector<geometry_msgs::Point> pts)
192225
{
193226
geometry_msgs::Polygon polygon;
@@ -207,6 +240,13 @@ std::vector<geometry_msgs::Point> toPointVector(geometry_msgs::Polygon polygon)
207240
return pts;
208241
}
209242

243+
std::string toString(const std::vector<double>& numbers)
244+
{
245+
std::stringstream ss;
246+
std::for_each(numbers.begin(), numbers.end(), [&](double nb) { ss << nb << " "; });
247+
return ss.str();
248+
}
249+
210250
void transformFootprint(double x, double y, double theta, const std::vector<geometry_msgs::Point>& footprint_spec,
211251
std::vector<geometry_msgs::Point>& oriented_footprint)
212252
{

0 commit comments

Comments
 (0)