ROS生成弓字形覆盖路径点逻辑分析

        前面提到,在经过分区,计算区间的遍历顺序后,接下来的操作就是按照遍历顺序,依次生成每一个子区间内的弓字形覆盖路径点,路径点的生成选取策略直接影响到后续路径跟随的效果。

        前面提到过,对每一个区间生成弓字形覆盖路径前,会对每一个区间计算最佳旋转角度,并在进行区间分割后,从图上依次获取每一个子区间,旋转到最佳角度(可简单理解为该角度下生成的覆盖直线最长,数量最小)。生成弓字形覆盖路径的过程分为两部分:

      (1)从上到下生成直线覆盖路径,每一个区间都会生成一或多条覆盖线。

      (2)根据当前点到区间四个顶点的距离长短,选用距离当前点最近的顶点作为弓字形路径起始点,连接当前覆盖直线最后一个路径点与下一条。

        其中,(1)中生成的多条覆盖直线是一开始根据机器人轮廓计算得出的,(2)中在生成一个区间内的覆盖直线点后,会取最后一个点作为当前点计算与该点距离最近的区间。下面就对这两部分的实现进行分析。

        调用这部分的代码如下:

	// point that keeps track of the last point after the boustrophedon path in each cell
	cv::Point robot_pos = rotated_starting_point;	
	// this is the trajectory of centers of the robot footprint or the field of view	
	std::vector<cv::Point2f> fov_middlepoint_path;	
	for(size_t cell=0; cell<cell_polygons.size(); ++cell)
	{
		computeBoustrophedonPath(rotated_room_map, map_resolution, 
	       cell_polygons[optimal_order[cell]], fov_middlepoint_path,
	       robot_pos, grid_spacing_as_int, 
	       half_grid_spacing_as_int, path_eps,
	       max_deviation_from_track, grid_obstacle_offset/map_resolution);
	}

        函数接口声明如下:

	// computes the Boustrophedon path pattern for a single cell
	void computeBoustrophedonPath(const cv::Mat& room_map, const float map_resolution,
		const GeneralizedPolygon& cell,std::vector<cv::Point2f>& fov_middlepoint_path,
		cv::Point& robot_pos,
		const int grid_spacing_as_int, const int half_grid_spacing_as_int,
		const double path_eps, const int max_deviation_from_track, const int 
		grid_obstacle_offset=0);

        需要注意的是robot_pos是指当前点,在每一次执行完都会修改该点数据。该函数完整的实现代码比较长,为了方便理解我把里面的代码按照步骤划分为两个部分,并把一些无关紧要的代码去点,想要看完整代码可以自行下载源文件阅读。

        首先是第一个步骤:计算得出所有连续的路径点并保存:

	// compute the basic Boustrophedon grid lines
	BoustrophedonGrid grid_lines;
	GridGenerator::generateBoustrophedonGrid(rotated_cell_map, rotated_inflated_cell_map, 
-1, grid_lines, cv::Vec4i(-1, -1, -1, -1),
grid_spacing_as_int, half_grid_spacing_as_int,
 1, max_deviation_from_track);

        保存覆盖线的数据结构定义如下:

class BoustrophedonLine
{
public:
	// points of the upper part of the line (always filled first)
	std::vector<cv::Point> upper_line;
	// points of the potentially existing lower part of the line
	std::vector<cv::Point> lower_line;
	// true if both lines, upper and lower,
	// concurrently provide valid alternative points at some locations 
	// (i.e. only if this is true, there are two individual lines of places to visit)
	bool has_two_valid_lines;

	BoustrophedonLine()
	: has_two_valid_lines(false)
	{
	}
};

class BoustrophedonGrid : public std::vector<BoustrophedonLine>
{
};

        这里定义了两条覆盖线,upper_linelower_line,在遇到小障碍物时会生成两条线保证连续,但是在实际应用场景中,不太可能为了中间的一个小的障碍物就来回重复清扫两次,所以推荐根据实际应用场景修改这部分的逻辑。

       GridGenerator::generateBoustrophedonGrid()的功能是生成覆盖间隔线,其实现逻辑也很简单,为了方便理解我对代码进行了一些简化和解释如下:

	// 第一步,遍历全图,计算得出区间的上下左右四个边界点
	int min_x=inflated_room_map.cols, max_x=-1, min_y=inflated_room_map.rows, max_y=-1;

	for (int v=0; v<inflated_room_map.rows; ++v)
	{
		for (int u=0; u<inflated_room_map.cols; ++u)
		{
			if (inflated_room_map.at<uchar>(v,u) == 255)
			{
				if (min_x > u)
					min_x = u;
				if (max_x < u)
					max_x = u;
				if (min_y > v)
					min_y = v;
				if (max_y < v)
					max_y = v;
			}
		}
	}

	int y=min_y;
	// loop through the vertical grid lines with regular grid spacing
	for (; y<=max_y+half_grid_spacing; y += grid_spacing)
	{
		if (y > max_y)	// this should happen at most once for the bottom line
			y = max_y;

		BoustrophedonLine line;
		const cv::Point invalid_point(-1,-1);
		// for keeping the horizontal grid distance
		cv::Point last_added_grid_point_above(-10000,-10000), last_added_grid_point_below(-10000,-10000);
		// for adding the rightmost possible point
		cv::Point last_valid_grid_point_above(-1,-1), last_valid_grid_point_below(-1,-1);
		// loop through the horizontal grid points with horizontal grid spacing length
		for (int x=min_x; x<=max_x; x+=1)
		{
			// 从左往右,逐个像素点判断,将该 Y 坐标下的非障碍物点添加到覆盖线 line 里面
			// 此处省略,需要可以下载源码阅读
			... ...
		}

		// 上面生成的是一条覆盖线,下面则是把当前生成的覆盖直线添加到覆盖线数组里面
		// 需要注意的是这里用到了upper_line和lower_line两条线
		BoustrophedonLine cleaned_line;
		if (line.upper_line.size()>0 && line.lower_line.size()>0)
		{
			... ...

			// add cleaned line to the grid
			grid_points.push_back(cleaned_line);
		}
	}

        生成连续覆盖直线后,执行第二个步骤:按照给定的点间距取点并按照弓字形连接生成路径点。这部分代码比较多,所以只取其中最关键的两个函数来说明。

	// downsamples a given path original_path to waypoint distances of 
	// path_eps and appends the resulting path to downsampled_path
	void downsamplePath(const std::vector<cv::Point>& original_path, 
		std::vector<cv::Point>& downsampled_path,
		cv::Point& cell_robot_pos, const double path_eps);

	// downsamples a given path original_path to waypoint distances 
	// of path_eps in reverse order as given in original_path
	// and appends the resulting path to downsampled_path
	void downsamplePathReverse(const std::vector<cv::Point>& original_path,
		std::vector<cv::Point>& downsampled_path,
		cv::Point& robot_pos, const double path_eps);

        这其中 downsamplePath是从左往右取点,downsamplePathReverse是从右往左取点,path_eps是路径点的间隔,即第一条直线从左往右取点,下一条直线从右往左取点,再下一条从左往右取点,这样依次循环执行,就生成弓字形的覆盖路径点。

  • 5
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值