-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRobotMapPlan.cpp
More file actions
357 lines (310 loc) · 10.7 KB
/
Copy pathRobotMapPlan.cpp
File metadata and controls
357 lines (310 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
// RobotMapPlan.cpp
// Kushal Jaligama
#include "RobotMapPlan.h"
#include <cmath>
#include <iostream>
#include <opencv2/highgui/highgui.hpp> // imshow()
#include <opencv2/imgproc/imgproc.hpp> // cv::circle
#include <queue>
#include <vector>
using cv::Point2i;
const uchar ROBOT_GREY = 255;
const cv::Scalar ROBOT_COLOR(0, 255, 0);
const uchar OBSTACLE_GREY = 128;
const cv::Scalar OBSTACLE_COLOR(0, 0, 255);
const uchar END_GREY = 255;
const cv::Scalar END_COLOR(255, 255, 255);
const uchar PATH_GREY = 200;
const cv::Scalar PATH_COLOR(255, 0, 0);
const uchar TEXT_GREY = 255;
const cv::Scalar TEXT_COLOR(255, 255, 255);
RobotMapPlan::RobotMapPlan(size_t map_rows, size_t map_cols,
Point2i robotStart, int robotRadius, Point2i end)
: theVisualMap(cv::Mat::zeros(cv::Size(map_rows, map_cols), CV_8U)),
theConfigurationSpace(
cv::Mat::zeros(cv::Size(map_rows, map_cols), CV_8U)),
theCostMap(map_rows, std::vector<Tile>(map_cols)),
robot(Obstacle{robotStart, robotRadius}),
startLoc(robotStart),
endLoc(end),
map_size_rows(map_rows),
map_size_cols(map_cols)
{
buildInitialMap();
}
RobotMapPlan::RobotMapPlan(std::ifstream &is)
{
is >> map_size_rows >> map_size_cols;
is >> startLoc.x >> startLoc.y;
robot.center.x = startLoc.x;
robot.center.y = startLoc.y;
is >> robot.radius;
is >> endLoc.x >> endLoc.y;
theVisualMap = cv::Mat::zeros(cv::Size(map_size_rows, map_size_cols), CV_8U);
theConfigurationSpace =
cv::Mat::zeros(cv::Size(map_size_rows, map_size_cols), CV_8U);
theCostMap.resize(map_size_rows);
for (size_t i = 0; i < map_size_rows; ++i)
{
theCostMap[i].resize(map_size_cols);
}
int r, c, rad;
while (is >> r >> c >> rad)
{
addObstacle(Obstacle{Point2i(r, c), rad});
}
buildInitialMap();
}
void RobotMapPlan::buildInitialMap()
{
// Function to add the walls, robot, and end location to the map
// Fill in the coordinates of the occupancy grid and also
for (size_t i = 0; i < map_size_rows; ++i)
{
for (size_t j = 0; j < map_size_cols; ++j)
{
theCostMap[i][j].x = i;
theCostMap[i][j].y = j;
}
}
// pad the borders of the configuration space by the robot's radius
// so that it cannot go out of bounds
for (size_t r = 0; r < robot.radius; ++r)
{
for (size_t c = 0; c < map_size_cols; ++c)
{
theConfigurationSpace.at<uchar>(r, c) = OBSTACLE_GREY;
}
}
for (size_t r = 0; r < map_size_rows; ++r)
{
for (size_t c = 0; c < robot.radius; ++c)
{
theConfigurationSpace.at<uchar>(r, c) = OBSTACLE_GREY;
}
}
for (size_t r = map_size_rows - robot.radius; r < map_size_rows; ++r)
{
for (size_t c = 0; c < map_size_cols; ++c)
{
theConfigurationSpace.at<uchar>(r, c) = OBSTACLE_GREY;
}
}
for (size_t r = 0; r < map_size_rows; ++r)
{
for (size_t c = map_size_cols - robot.radius; c < map_size_cols; ++c)
{
theConfigurationSpace.at<uchar>(r, c) = OBSTACLE_GREY;
}
}
// Draw the robot at the start location
drawRobot(startLoc, robot.radius);
// Draw the end location
drawEndLoc(endLoc);
}
void RobotMapPlan::drawRobot(Point2i robotStart, int robotRadius)
{
// Initialize the robot's starting position and size
robot = Obstacle{robotStart, robotRadius};
startLoc = robotStart;
cv::circle(theVisualMap, robotStart, robotRadius, ROBOT_GREY, CV_FILLED);
// Robot is just a point mass in the configuration space (radius 1)
cv::circle(theConfigurationSpace, robotStart, 1, ROBOT_GREY, CV_FILLED);
cv::putText(theVisualMap, "ROBOT", robotStart, cv::FONT_HERSHEY_SIMPLEX, .5,
TEXT_GREY);
std::cout << "Added robot to the map." << std::endl;
}
void RobotMapPlan::drawEndLoc(Point2i end)
{
endLoc = end;
cv::circle(theVisualMap, end, robot.radius, END_GREY);
cv::circle(theConfigurationSpace, end, robot.radius, END_GREY);
cv::putText(theVisualMap, "END", end, cv::FONT_HERSHEY_SIMPLEX, .5,
TEXT_GREY);
std::cout << "Set the target location." << std::endl;
}
void RobotMapPlan::addObstacle(Obstacle o)
{
obstacles.push_back(o);
cv::circle(theVisualMap, o.center, o.radius, OBSTACLE_GREY, CV_FILLED);
// maintain obstacles in the configuration space for planning
// scale the robot down to point mass
// increase obstacle size by robot radius
cv::circle(theConfigurationSpace, o.center, o.radius + robot.radius,
OBSTACLE_GREY, CV_FILLED);
cv::putText(theVisualMap, "OBSTACLE", o.center, cv::FONT_HERSHEY_SIMPLEX, 0.5,
TEXT_GREY);
// std::cout << "Added obstacle " << obstacles.size() << std::endl;
}
static double euclidDist(Tile a, Point2i endLoc)
{
return sqrt((a.x - endLoc.x) * (a.x - endLoc.x) +
(a.y - endLoc.y) * (a.y - endLoc.y));
}
static double euclidDist(Tile a, Tile b)
{
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
// Comparator used for priority queue in path planning algorithms
// Want to prioritize Tiles with lower cost, so must make a > comparator
// since STL pq uses in an opposite manner
// Alternative is to use
struct TileComparator
{
bool operator()(Tile a, Tile b) { return (a.cost > b.cost); }
};
void RobotMapPlan::planPathDijkstra()
{
std::cout << "Planning path from robot location to target end location with "
"Dijkstra's Algorithm..."
<< std::endl;
// function to find a path from startloc to endloc
// perform Dijkstra's algorithm on the configuration space vector
// implementation based on
// https://en.wikipedia.org/wiki/Dijkstra's_algorithm#Algorithm
// also referenced Computerphile's YouTube video on Dijkstra's
std::priority_queue<Tile, std::vector<Tile>, TileComparator> candidates;
// set cost of initial start location to zero
theCostMap[startLoc.x][startLoc.y].cost = 0;
// add the start location to pq
candidates.push(theCostMap[startLoc.x][startLoc.y]);
bool pathExists = false;
while (!candidates.empty())
{
Tile curr = candidates.top();
candidates.pop();
// terminate Dijkstra's if the node we're visiting now is end location
// with a valid state (path is found)
if (curr.x == endLoc.x && curr.y == endLoc.y)
{
pathExists = true;
break;
}
// terminate Dijkstra's if smallest cost in pq is infinity
// with an invalid state (path is not found)
if (curr.cost == std::numeric_limits<double>::infinity())
{
pathExists = false;
break;
}
// add neighbors (n, s, e, w, nw, ne, se, sw)
for (int row = -1; row <= 1; ++row)
{
for (int col = -1; col <= 1; ++col)
{
int neighbor_row = curr.x + row;
int neighbor_col = curr.y + col;
// ensure neighbor is unvisited, within bounds, and not an obstacle
if (isValidPoint(neighbor_row, neighbor_col))
{
const int diagonal_neighbor = 2;
const int vertical_neighbor = 1;
int cost = 0;
if (abs(row) == 1 && abs(col) == 1)
{
cost = diagonal_neighbor;
}
else
{
cost = vertical_neighbor;
}
// closest obstacles
double minDist = std::numeric_limits<double>::infinity();
for (size_t i = 0; i < obstacles.size(); ++i)
{
double dist = euclidDist(curr, obstacles[i].center);
if (euclidDist(theCostMap[neighbor_row][neighbor_col],
obstacles[i].center) < minDist)
{
minDist = dist;
}
}
// assign the cost of going to this tile (every node has cost of 1) if
// current cost of node is greater than new cost
double tentative_cost = curr.cost + cost + minDist;
if (theCostMap[neighbor_row][neighbor_col].cost > tentative_cost)
{
theCostMap[neighbor_row][neighbor_col].cost = tentative_cost;
// set the parent of the neighbor
theCostMap[neighbor_row][neighbor_col].par_x = curr.x;
theCostMap[neighbor_row][neighbor_col].par_y = curr.y;
// add valid neighbors to the pq only if we have a new cost for this
// tile
candidates.push(theCostMap[neighbor_row][neighbor_col]);
}
}
}
}
// mark current node as visited
theCostMap[curr.x][curr.y].visited = true;
} // end while
if (pathExists)
{
std::cout << "Path exists, building and storing..." << std::endl;
// backtrack through from end location to start to get the path
Tile it = theCostMap[endLoc.x][endLoc.y];
while (it.par_x != -1 && it.par_y != -1)
{
Point2i p(it.x, it.y);
path.push_back(p);
it = theCostMap[it.par_x][it.par_y];
}
// push on the start tile
path.push_back(Point2i(startLoc.x, startLoc.y));
}
else
{
std::cout << "No path found from start (" << startLoc.x << ", "
<< startLoc.y << ") to end " << endLoc.x << ", " << endLoc.y
<< ")" << std::endl;
}
}
void RobotMapPlan::visualize()
{
std::cout << "Drawing path onto the map and visualizing..." << std::endl;
size_t pathDist = 0;
if (!path.empty())
{
for (size_t i = 0; i < path.size() - 1; ++i)
{
pathDist++;
cv::line(theVisualMap, path[i], path[i + 1], PATH_GREY);
cv::line(theConfigurationSpace, path[i], path[i + 1], PATH_GREY);
}
}
std::string distText = "Path Distance: " + std::to_string(pathDist);
Point2i distTextPoint(map_size_cols / 2 - 100, map_size_rows - 50);
cv::putText(theVisualMap, distText, distTextPoint, cv::FONT_HERSHEY_SIMPLEX,
1, TEXT_GREY);
cv::imwrite("Path on Map.jpg", theVisualMap);
// Pop up a graph that shows the 2D map
cv::imshow("theVisualMap", theVisualMap);
cv::imshow("theConfigurationSpace", theConfigurationSpace);
cv::waitKey(0);
}
void RobotMapPlan::writeMapFile(std::string file_name)
{
std::ofstream ofs;
ofs.open(file_name);
// Add map dimensions to file
ofs << map_size_rows << " " << map_size_cols << std::endl;
// Add robot start location and radius to file
ofs << robot.center.x << " " << robot.center.y << " " << robot.radius
<< std::endl;
// Add end location to file
ofs << endLoc.x << " " << endLoc.y << std::endl;
for (size_t i = 0; i < obstacles.size(); ++i)
{
ofs << obstacles[i].center.x << " " << obstacles[i].center.y << " "
<< obstacles[i].radius << std::endl;
}
ofs.close();
}
bool RobotMapPlan::isValidPoint(int row, int col)
{
// ensure neighbor is unvisited, within bounds, and not an obstacle
// OpenCV Mat::at function takes the column as first argument
return (row >= 0 && row < map_size_rows && col >= 0 && col < map_size_cols &&
!theCostMap[row][col].visited &&
theConfigurationSpace.at<uchar>(col, row) != OBSTACLE_GREY);
}