在Gmapping中的Bresenham算法解析

引入

在gmapping算法中,需要计算激光在每个粒子相应的位姿所扫描到的区域,这一步骤用到了Bresenham算法,他的实现函数是:

void GridLineTraversal::gridLineCore( IntPoint start, IntPoint end, GridLineTraversalLine *line )

刚好想了解一下这个算法具体实现流程,利用这个算法来完成其他工作。下面将从算法推导和代码实现来理解这个Bresenham算法。如果写的有问题,欢迎指出,谢谢。

参考资料:gmapping算法介绍
Bresenham算法理解

1、算法简介

bresenham算法是计算机图形学中为了“显示器(屏幕或打印机)系由像素构成”的这个特性而设计出来的算法,使得在求直线各点的过程中全部以整数来运算,因而大幅度提升计算速度。

原理
过各行、各列像素中心构造一组虚拟网格线,按直线从起点到终点的顺序计算直线各垂直网格线的交点,然后确定该列像素中与此交点最近的像素。

优点
该算法的优点在于可以采用增量计算,使得对于每一列,只要检查一个误差项的符号,就可以确定该列所求的像素。

2、算法推导

设start、end所在直线方程为y = kx + b,首先通过直线的斜率确定了在x方向进行单位步进还是y方向进行单位步进:当斜率k的绝对值|k|<1时,在x方向进行单位步进;当斜率k的绝对值|k|>1时,在y方向进行单位步进。
在这里插入图片描述

如上图,当我们通过斜率确定了是在x方向上进行单位步进的时候,理想情况下满足一个条件:当 x = X m x=X_m x=Xm 时, y = Y m y=Y_m y=Ym

但是,现实情况并不是这样,如上图所示,一条直线并不能完整地穿过那些点或栅格,所以需要通过某些计算来确定哪些点或栅格才能满足直线方程条件。

所以,问题来了。那么当x 执行一个单位步进时(即 x = X m + 1 x=X_m + 1 x=Xm+1时), 是 y = Y m y = Y_m y=Ym 还是 y = Y m + 1 y = Y_m + 1 y=Ym+1更符合这个直线方程呢?单凭肉眼我们很难得出结论,最好的办法当然是比较 Y m , Y m + 1 Y_m, Y_m + 1 YmYm+1和真实的方程的y值(即 Y r e a l = k ∗ ( X m + 1 ) + b Y_{real} = k* ( X_m +1) + b Yreal=k(Xm+1)+b)的差是多少,看看哪一个更靠近真实的方程的y值。

那么,我们先假设 D u p p e r Dupper Dupper表示 Y m + 1 Y_m + 1 Ym+1和方程真实值 Y r e a l Y_{real} Yreal的差, D d o w n Ddown Ddown表示 Y m Y_m Ym和方程真实值 Y r e a l Y_{real} Yreal的差 ,求得如下:
D u p p e r = Y m + 1 − Y r e a l = Y m + 1 − [ k ∗ ( X m + 1 ) + b ] Dupper = Y_m + 1 - Y_{real} = Y_m +1 - [ k*( X_m + 1) + b] Dupper=Ym+1Yreal=Ym+1[k(Xm+1)+b]
D d o w n = Y r e a l − Y m = [ k ∗ ( X m + 1 ) + b ] − Y m Ddown = Y_{real} - Y_m = [k*( X_m + 1) + b] - Y_m Ddown=YrealYm=[k(Xm+1)+b]Ym

那接着假设Diff为Dupper和Ddown之间的差:
D i f f = D u p p e r − D d o w n = Y m + 1 − [ k ∗ ( X m + 1 ) + b ] − [ [ k ∗ ( X m + 1 ) + b ] − Y m ] Diff = Dupper - Ddown = Y_m +1 - [ k*( X_m + 1) + b] - [ [k*( X_m + 1) + b] - Y_m ] Diff=DupperDdown=Ym+1[k(Xm+1)+b][[k(Xm+1)+b]Ym]

△ X {\triangle}X X 为线段x方向的间距(即 △ X = ∣ e n d . x − s t a r t . x ∣ {\triangle}X = |end.x - start.x| X=end.xstart.x), △ Y {\triangle}Y Y 为线段y方向的间距(即$ △ Y = ∣ e n d . y − s t a r t . y ∣ {\triangle}Y = |end.y - start.y| Y=end.ystart.y),则有:
P m = △ X ∗ D i f f = △ X ∗ [ Y m + 1 − [ k ∗ ( X m + 1 ) + b ] ] − △ X ∗ [ [ k ∗ ( X m + 1 ) + b ] − Y m ] P_m = {\triangle}X * Diff = {\triangle}X * [ Y_m +1 - [ k*( X_m + 1) + b] ] - {\triangle}X * [ [k*( X_m + 1) + b] - Y_m ] Pm=XDiff=X[Ym+1[k(Xm+1)+b]]X[[k(Xm+1)+b]Ym]

而且斜率 k = △ Y △ X k = {{ {\triangle}Y} \over { {\triangle}X}} k=XY,则有 △ Y = △ X ∗ k {\triangle}Y = {{\triangle}X} *k Y=Xk
P m = 2 ∗ △ X ∗ Y m − 2 ∗ △ X ∗ k ∗ X m − 2 ∗ △ X ∗ k − △ X ∗ ( 2 b − 1 ) P_m =2*{\triangle}X*Y_m -2*{\triangle}X*k*X_m - 2*{\triangle}X*k - {\triangle}X *(2b-1) Pm=2XYm2XkXm2XkX(2b1)
P m = 2 ∗ △ X ∗ Y m − 2 ∗ △ Y ∗ X m − 2 ∗ △ Y − △ X ∗ ( 2 b − 1 ) P_m = 2*{\triangle}X*Y_m -2*{\triangle}Y*X_m -2*{\triangle}Y - {\triangle}X*(2b-1) Pm=2XYm2YXm2YX(2b1)

那么,由 P m P_m Pm可以得到 P m + 1 P_{m+1} Pm+1
P m + 1 = 2 ∗ △ X ∗ Y m + 1 − 2 ∗ △ Y ∗ X m + 1 − 2 ∗ △ Y − △ X ∗ ( 2 b − 1 ) P_{m+1} = 2*{\triangle}X*Y_{m+1} -2*{\triangle}Y*X_{m+1} -2*{\triangle}Y - {\triangle}X*(2b-1) Pm+1=2XYm+12YXm+12YX(2b1)
此时,可以知道 X m + 1 = X m + 1 X_{m+1} = X_m + 1 Xm+1=Xm+1,则两式相减可以得到:
P m + 1 − P m = 2 ∗ △ X ∗ ( Y m + 1 − Y m ) − 2 ∗ △ Y P_{m+1} - P_m =2*{\triangle}X*(Y_{m+1} - Y_m) - 2*{\triangle}Y Pm+1Pm=2X(Ym+1Ym)2Y
也就是, P m + 1 = P m + 2 ∗ △ X ∗ ( Y m + 1 − Y m ) − 2 ∗ △ Y P_{m+1} = P_m + 2*{\triangle}X*(Y_{m+1} - Y_m) - 2*{\triangle}Y Pm+1=Pm+2X(Ym+1Ym)2Y,但是上面那个求差的等式的右边是非常重要的,需要记住!

接下来,就是最重要的时候了,要对 ( Y m + 1 − Y m ) (Y_{m+1} - Y_m) (Ym+1Ym)进行讨论:
首先,我们知道 P m = △ X ∗ D i f f = △ X ∗ ( D u p p e r − D d o w n ) P_m = {\triangle}X * Diff = {\triangle}X *(Dupper - Ddown) Pm=XDiff=X(DupperDdown),所以 P m P_m Pm ( D u p p e r − D d o w n ) (Dupper - Ddown) (DupperDdown)式子存在正相关关系。另外 D u p p e r Dupper Dupper值越小说明 Y m + 1 Y_m + 1 Ym+1 Y m Y_m Ym更接近 Y r e a l Y_{real} Yreal,此时应该取 Y m + 1 Y_m + 1 Ym+1;反之,就应该取 Y m Y_m Ym
(1)当 P m &gt; 0 P_m&gt;0 Pm>0时,则 Y m + 1 = Y m Y_{m+1} = Y_m Ym+1=Ym,那么 P m + 1 = P m − 2 ∗ △ Y P_{m+1} = P_m - 2*{\triangle}Y Pm+1=Pm2Y
(2)当 P m &lt; 0 P_m&lt;0 Pm<0时,则 Y m + 1 = Y m + 1 Y_{m+1} = Y_m +1 Ym+1=Ym+1,那么 P m + 1 = P m + 2 ∗ △ X − 2 ∗ △ Y P_{m+1} = P_m +2*{\triangle}X - 2*{\triangle}Y Pm+1=Pm+2X2Y

那么算法循环的重点就是循环判断 P m P_m Pm ,如果小于0,则下一个点的纵坐标y加1。

最后根据等式 D i f f = D u p p e r − D d o w n = Y m + 1 − [ k ∗ ( X m + 1 ) + b ] − [ [ k ∗ ( X m + 1 ) + b ] − Y m ] Diff = Dupper - Ddown = Y_m +1 - [ k*( X_m + 1) + b] - [ [k*( X_m + 1) + b] - Y_m ] Diff=DupperDdown=Ym+1[k(Xm+1)+b][[k(Xm+1)+b]Ym] k = △ Y △ X k = {{ {\triangle}Y} \over { {\triangle}X}} k=XY以及 y 0 = k ∗ x 0 + b y_0 = k*x_0 + b y0=kx0+b,我们可以得出起始像素 ( x 0 , y 0 ) (x_0,y_0) (x0y0)的参数 P m 0 P_{m0} Pm0的值为:
P m 0 = 2 ∗ △ X ∗ y 0 − 2 ∗ △ Y ∗ x 0 − 2 ∗ △ Y − △ X ∗ ( 2 b − 1 ) P_{m0} =2* {\triangle}X*y_0 -2* {\triangle}Y*x_0 - 2* {\triangle}Y - {\triangle}X *(2b-1) Pm0=2Xy02Yx02YX(2b1)
P m 0 = 2 ∗ △ X ∗ k ∗ x 0 + 2 ∗ △ X ∗ b − 2 ∗ △ Y ∗ x 0 − 2 ∗ △ Y − 2 ∗ △ X ∗ b + △ X P_{m0} =2* {\triangle}X*k*x_0 + 2* {\triangle}X*b -2* {\triangle}Y*x_0 - 2* {\triangle}Y - 2*{\triangle}X *b + {\triangle}X Pm0=2Xkx0+2Xb2Yx02Y2Xb+X
P m 0 = △ X − 2 ∗ △ Y P_{m0} = {\triangle}X - 2 * {\triangle}Y Pm0=X2Y

算法就推导到这里了,下面看Gmapping里面的实现代码。

3、代码实现

其中,算法推导中的 P m P_m Pm相当于程序中的变量d;
变量incr1相当于算法推导中的情况(1),变量incr2相当于算法推导中的情况(2)。

void GridLineTraversal::gridLineCore( IntPoint start, IntPoint end, GridLineTraversalLine *line )
{
	int dx, dy;			//横纵坐标间距
	int incr1, incr2;	//P_m增量
	int d;					//P_m
	int x, y,xend, yend;//直线增长的首末端点坐标
	int xdirflag, ydirflag;//横纵坐标增长方向
	int cnt = 0;			//直线过点的点的序号
	
	dx = abs(end.x-start.x); 
	dy = abs(end.y-start.y);
  
	if (dy <= dx) 
	{//斜率k的绝对值|k|<1时,在x方向进行单位步进
		d = dx - 2*dy; ·	//初始点P_m0值
		incr1 = - 2 * dy;		//情况(1)
		incr2 = 2 * (dx - dy);//情况(2)
		if (start.x > end.x)
		{//起点横坐标比终点横坐标大,xdirflag = -1(负号可以理解为增长方向与直线始终点方向相反)
			x = end.x; y = end.y;	//设置增长起点,注意这里要选择小横坐标作为起点,用于增量时好理解
			ydirflag = (-1);				//此时默认(希望)纵坐标也是start.y > end.y的情况
			xend = start.x;		//设置增长终点横坐标
		} else {//xdirflag = 1 ,ydirflag = 1
			x = start.x; y = start.y;
			ydirflag = 1;
			xend = end.x;
		}
		line->points[cnt].x=x;	//加入起点坐标
		line->points[cnt].y=y;
		cnt++;
		if (((end.y - start.y) * ydirflag) > 0)
		 {//是预料到的情况,start.y > end.y
			while (x < xend)//开始进行增量递增
			{//x > xend,y > yend,处理向右上角爬升的直线群,下图1号所示直线
				x++;
				if (d > 0)
	  				d+=incr1;
				else {
	 				 y++; d+=incr2;		//纵坐标向正方向增长
				}
				line->points[cnt].x=x;//添加新的点
				line->points[cnt].y=y;
				cnt++;
     		}
    	} else {//x > xend,y < yend,处理向右下角降落的直线群,下图2号所示直线
      		while (x < xend) 
      		{
				x++;
				if (d > 0) 
	 				 d+=incr1;
				else {
	  				y--; d+=incr2;	//纵坐标向负方向增长
				}
				line->points[cnt].x=x;
				line->points[cnt].y=y;
				cnt++;
     		 }
   		 }		
 	 } else {//dy > dx,当斜率k的绝对值|k|>1时,在y方向进行单位步进
		d = dy - 2*dx;	//P_m0初值
		incr1 = -2*dx; 	//形同算法推导情况1
		incr2 = 2 * (dy - dx);
   	 	if (start.y > end.y) 
   	 	{
		      y = end.y; x = end.x;	//取最小的纵坐标作为起点
		      yend = start.y;
		      xdirflag = (-1);		//期望start.x > end.x
   		 } else {//start.y < end.y
		      y = start.y; x = start.x;
		      yend = end.y;
		      xdirflag = 1;
    	}
	    line->points[cnt].x=x;	//添加起点
	    line->points[cnt].y=y;
	    cnt++;
	    if (((end.x - start.x) * xdirflag) > 0) 
    	{//x > xend ,y > yend,处理向右上角爬升的直线群,下图3号所示直线
      		while (y < yend)
       		{
				y++;
				if (d > 0) 
					d+=incr1;
				else {
					x++; d+=incr2;		//横坐标向正方向增长
				}
				line->points[cnt].x=x;	//添加新的点
				line->points[cnt].y=y;
				cnt++;
     		 }
   		 } else 
   		 {//x < xend,y > yend,处理向左上角爬升的直线群,下图4号所示直线
			while (y < yend)
			{
			y++;
			if (d > 0) 
				d+=incr1;
			else {
				x--; d+=incr2;	横坐标向负方向增长
			}
			line->points[cnt].x=x;
			line->points[cnt].y=y;
			cnt++;
			}
    	}
 	 }
 	 line->num_points = cnt;		//记录添加所有点的数目
  }

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值