转载:https://www.cnblogs.com/icmzn/p/5065306.html

【平面区域填充算法】是计算机图形学领域的一个很重要的算法,区域填充即给出一个区域的边界 (也可以是没有边界,只是给出指定颜色),要求将边界范围内的所有象素单元都修改成指定的颜色(也可能是图案填充)。区域填充中最常用的是多边形填色,本文讨论种子填充算法(Seed Filling)如果要填充的区域是以图像元数据方式给出的,通常使用种子填充算法(Seed Filling)进行区域填充。种子填充算法需要给出图像数据的区域,以及区域内的一个点,这种算法比较适合人机交互方式进行的图像填充操作,不适合计算 机自动处理和判断填色。根据对图像区域边界定义方式以及对点的颜色修改方式,种子填充又可细分为几类:

  比如:①注入填充算法(Flood Fill Algorithm)

       ②边界填充算法(Boundary Fill Algorithm)以及

     ③为减少递归和压栈次数而改进的扫描线种子填充算法等等。

所有种子填充算法的核心其实就是一个递归算法,都是从指定的种子点开始,向各个方向上搜索,逐个像素进行处理,直到遇到边界,各种种子填充算法只是在处理 颜色和边界的方式上有所不同。

  在开始介绍种子填充算法之前,首先也介绍两个概念,就是“4-联通算法”和“8-联通算法”。

  既然是搜索就涉及到搜索的方向 问题,从区域内任意一点出发,如果只是通过上、下、左、右四个方向搜索到达区域内的任意像素,则用这种方法填充的区域就称为四连通域,这种填充方法就称为 “4-联通算法”。如果从区域内任意一点出发,通过上、下、左、右、左上、左下、右上和右下全部八个方向到达区域内的任意像素,则这种方法填充的区域就称 为八连通域,这种填充方法就称为“8-联通算法”。

  

  如图1(a)所示,假设中心的蓝色点是当前处理的点,如果是“4-联通算法”,则只搜索处理周围蓝色标 识的四个点,如果是“8-联通算法”则除了处理上、下、左、右四个蓝色标识的点,还搜索处理四个红色标识的点。两种搜索算法的填充效果分别如如图1(b) 和图1(c)所示,假如都是从黄色点开始填充,则“4-联通算法”如图1(b)所示只搜索填充左下角的区域,而“8-联通算法”则如图1(c)所示,将左 下角和右上角的区域都填充了。

1. 注入填充算法(Flood Fill Algorithm)

  边界填充算法与注入填充算法的本质其实是一样的,都是递归和搜索,区别只在于对边界的确 认,也就是递归的结束条件不一样。注入填充算法没有边界的概念,只是对联通区域内指定的颜色进行替换,而边界填充算法恰恰强调边界的存在,只要是边界内的 点无论是什么颜色,都替换成指定的颜色。边界填充算法在应用上也非常的广泛,画图软件中的“油漆桶”功能就是边界填充算法的例子。以下就是边界填充算法的 一个实现:

void FloodSeedFill(int x, int y, int old_color, int new_color){if(GetPixelColor(x, y) == old_color){SetPixelColor(x, y, new_color);for(int i = 0; i < COUNT_OF(direction_8); i++){FloodSeedFill(x + direction_8[i].x_offset,y + direction_8[i].y_offset, old_color, new_color);}}
}

   for循环实现了向8个联通方向的递归搜索,秘密就在direction_8的定义:

typedef struct tagDIRECTION
{int x_offset;int y_offset;
}DIRECTION;DIRECTION direction_8[] = { {-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}, {-1, -1} };

  下图就是应用本算法实现的“4-联通”和“8-联通”填充效果:

      

2. 边界填充算法(Boundary Fill Algorithm)

   边界填充算法与注入填充算法的本质其实是一样的,都是递归和搜索,区别只在于对边界的确认,也就是递归的结束条件不一样。注入填充算法没有边界的概念,只 是对联通区域内指定的颜色进行替换,而边界填充算法恰恰强调边界的存在,只要是边界内的点无论是什么颜色,都替换成指定的颜色。边界填充算法在应用上也非 常的广泛,画图软件中的“油漆桶”功能就是边界填充算法的例子。以下就是边界填充算法的一个实现:

void BoundarySeedFill(int x, int y, int new_color, int boundary_color)
{int curColor = GetPixelColor(x, y);if( (curColor != boundary_color)&& (curColor != new_color) ){SetPixelColor(x, y, new_color);for(int i = 0; i < COUNT_OF(direction_8); i++){BoundarySeedFill(x + direction_8[i].x_offset,y + direction_8[i].y_offset, new_color, boundary_color);}}
}

  关于direction_8的说明请参考上一节,图3就是应用本算法实现的“4-联通”和“8-联通”填充效果(其中颜色值是1的点就是指定的边界):

  

3. 扫描线种子填充算法(ScanLineSeed Fill Algorithm)

  前面介绍的1与2,介绍的两种种子填充算法的优点是非常简单,缺点是使用了递归算法,这不但 需要大量栈空间来存储相邻的点,而且效率不高。为了减少算法中的递归调用,节省栈空间的使用,人们提出了很多改进算法,其中一种就是扫描线种子填充算法。

  扫描线种子填充算法不再采用递归的方式处理“4-联通”和“8-联通”的相邻点,而是通过沿水平扫描线填充像素段,一段一段地来处理“4-联通”和“8- 联通”的相邻点。这样算法处理过程中就只需要将每个水平像素段的起始点位置压入一个特殊的栈,而不需要象递归算法那样将当前位置周围尚未处理的所有相邻点 都压入堆栈,从而可以节省堆栈空间。应该说,扫描线填充算法只是一种避免递归,提高效率的思想,前面提到的注入填充算法和边界填充算法都可以改进成扫描线 填充算法,下面介绍的就是结合了边界填充算法的扫描线种子填充算法。

   扫描线种子填充算法的基本过程如下:当给定种子点(x, y)时,首先分别向左和向右两个方向填充种子点所在扫描线上的位于给定区域的一个区段,同时记下这个区段的范围[xLeft, xRight],然后确定与这一区段相连通的上、下两条扫描线上位于给定区域内的区段,并依次保存下来。反复这个过程,直到填充结束。

  扫描线种子填充算法可由下列四个步骤实现:

  (1) 初始化一个空的栈用于存放种子点,将种子点(x, y)入栈;

  (2)判断栈是否为空,如果栈为空则结束算法,否则取出栈顶元素作为当前扫描线的种子点(x, y),y是当前的扫描线;

  (3) 从种子点(x, y)出发,沿当前扫描线向左、右两个方向填充,直到边界。分别标记区段的左、右端点坐标为xLeft和xRight;

  (4)分别检查与当前扫描线相邻的y - 1和y + 1两条扫描线在区间[xLeft, xRight]中的像素,从xLeft开始向xRight方向搜索,若存在非边界且未填充的像素点,则找出这些相邻的像素点中最右边的一个,并将其作为种 子点压入栈中,然后返回第(2)步;

     如果新扫描线上实际点的区间比当前扫描线的[xLeft, xRight]区间大,而且是连续的情况下,算法的第(3)步就处理了这种情况。如下图所示:

  

  假设当前处理的扫描线是黄色点所在的第7行,则经过第3步处理后可以得到一个区间 [6,10]。然后第4步操作,从相邻的第6行和第8行两条扫描线的第6列开始向右搜索,确定红色的两个点分别是第6行和第8行的种子点,于是按照顺序将 (6, 10)和(8, 10)???两个种子点入栈。接下来的循环会处理(8, 10)这个种子点,根据算法第3步说明,会从(8, 10)开始向左和向右填充,由于中间没有边界点,因此填充会直到遇到边界为止,所以尽管第8行实际区域比第7行的区间[6,10]大,但是仍然得到了正确 的填充。

     如果新扫描线上实际点的区间比当前扫描线的[xLeft, xRight]区间大,而且中间有边界点的情况,算法又是怎么处理呢?算法描述中虽然没有明确对这种情况的处理方法,但是第4步确定上、下相邻扫描线的种 子点的方法,以及靠右取点的原则,实际上暗含了从相邻扫描线绕过障碍点的方法。下面以下图为例说明:

  

  算法第3步处理完第5行后,确定了区间[7, 9],相邻的第4行虽然实际范围比区间[7, 9]大,但是因为被(4, 6)这个边界点阻碍,使得在确定种子点(4, 9)后向左填充只能填充右边的第7列到第10列之间的区域,而左边的第3列到第5列之间的区域没有填充。虽然作为第5行的相邻行,第一次对第4行的扫描根 据靠右原则只确定了(4, 9)一个种子点。但是对第3行处理完后,第4行的左边部分作为第3行下边的相邻行,再次得到扫描的机会。第3行的区间是[3, 9],向左跨过了第6列这个障碍点,第2次扫描第4行的时候就从第3列开始,向右找,可以确定种子点(4, 5)。这样第4行就有了两个种子点,就可以被完整地填充了。

   由此可见,对于有障碍点的行,通过相邻边的关系,可以跨越障碍点,通过多次扫描得到完整的填充,算法已经隐含了对这种情况的处理。

4. 根据本节总结的四个步骤实现并运行通过程序片段如下:

//扫描线绘制算法,提高填充效率,种子是基于鼠标点击的像素位置
inline void CScanLineSeedFill::scanLineSeedFill(CPoint clickPos, GLfloat (&frameWork)[4][3] )
{//首先判定当前点是否有效,无效直接返回assert(isInialDone);//鼠标的点击点必须在外围框之内GLdouble winFramePos[4][3];getViewPortCoordPos(frameWork, winFramePos);int frameLeft = (int)winFramePos[0][0];int frameDown = (int)winFramePos[0][1];int frameRight = (int)winFramePos[2][0];int frameUp = (int)winFramePos[2][1];//对绘制的范围进行限定:点击框架之外,或者原点无效//计算click位置在视口中的坐标PointInt clickViewPos(clickPos.x, (this->winHigh) - (clickPos.y));if ((clickPos.x==0 && clickPos.y==0) ||!(clickViewPos.x>frameLeft && clickViewPos.x<frameRight && clickViewPos.y>frameDown && clickViewPos.y<frameUp))return ;GLubyte    readColor[3];glReadPixels(clickViewPos.x, clickViewPos.y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, readColor);if (sameColor(readColor, boundryColor) || sameColor(readColor, fillColor))//如果种子是边界点或填充颜色则直接退出return;glColor3ubv(fillColor);//一开始非边界的种子,必然可以绘制扫描描线//PointInt startPoint;//startPoint.x = clickViewPos.x;//startPoint.y = clickViewPos.y;seedsStack.push(clickViewPos);//pixelStack.push(point);float viewPosition[3];double worldPosition[3];//世界坐标系int saveX;int xRight,xLeft;int x,y;//如果栈不为空    while(!seedsStack.empty()){//获取最顶端的元素PointInt tempPoint=seedsStack.top();//删除最顶端的元素seedsStack.pop();saveX = tempPoint.x;//绘制点的X坐标,从当前点向左向右扫描x = tempPoint.x;y = tempPoint.y;glReadPixels(x, y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &readColor);//向右:如果没有到达右边界,就填充while(!sameColor(readColor, boundryColor)){x=x+1;glReadPixels(x, y, 1, 1,GL_RGB,GL_UNSIGNED_BYTE, &readColor);}xRight=x-1;x=saveX-1;glReadPixels(x, y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &readColor);//向左:如果没有到达左边界,就填充while(!sameColor(readColor, boundryColor)){x=x-1;glReadPixels(x, y, 1, 1, GL_RGB,GL_UNSIGNED_BYTE, &readColor);}//保存左端点xLeft=x+1;//把当前像素点还原为世界坐标系中的点然后再绘制glBegin(GL_LINES);viewPosition[0] = xLeft;viewPosition[1] = y;viewPosition[2] = 0;getWorldCoordPos(viewPosition, worldPosition);glVertex3f(worldPosition[0], worldPosition[1], worldPosition[2]);viewPosition[0] = xRight;getWorldCoordPos(viewPosition, worldPosition);glVertex3f(worldPosition[0], worldPosition[1], worldPosition[2]);glEnd();//从右边的点开始x=xRight;//检查上端的扫描线searchNewLineSeed(seedsStack, xLeft, xRight, y+1);searchNewLineSeed(seedsStack, xLeft, xRight, y-1);}
//查找新扫描线的种子
inline void CScanLineSeedFill::searchNewLineSeed(stack<PointInt>& stk, int xLeft, int xRight, int y)
{unsigned char readColor[3];int xt = xLeft;bool findNewSeed = false;while(xt <= xRight){findNewSeed = false;glReadPixels(xt, y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, readColor);while(!sameColor(readColor, boundryColor) && !sameColor(readColor, fillColor) && (xt < xRight)){findNewSeed = true;xt++;glReadPixels(xt, y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &readColor);}if(findNewSeed){if(!sameColor(readColor, boundryColor) && !sameColor(readColor, fillColor) && (xt == xRight))//到达边界{stk.push(PointInt(xt, y));}elsestk.push(PointInt(xt - 1, y));//不到边界}/*向右跳过内部的无效点(边界点), 即与边界相同颜色的点(处理区间右端有障碍点的情况)*///xt或者在障碍点上,或者为右边端点int tag = xt;while((sameColor(readColor, boundryColor) || sameColor(readColor, fillColor)) && (xt < xRight)){xt++;glReadPixels(xt, y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &readColor);}if (tag == xt)//如果xt>= xRight or 都不为边界或已经填充则右移动xt += 1;}
}

  程序运行结果如下:

  

 

【Algorithm】种子填充算法相关推荐

  1. 图形学初步--------种子填充算法

    上篇博文讲到了填充算法的扫描线填充,这篇博文讲解另一大算法思路----------种子填充. 一.概念 种子填充算法假设在多边形或区域内部至少有一个像素是已知的.然后设法找到区域内所有其他像素,并对它 ...

  2. c语言 连通域算法 递归,VC++ 6.0编写计算机图形学中的种子填充算法,想用递归的八向连通域,求助!...

    VC++ 6.0编写计算机图形学中的种子填充算法,想用递归的八向连通域,求助!0 填充函数代码如下: void CComputerGraphicsView::PolygonFill2()//区域填充函 ...

  3. c++实现种子填充算法与扫描线算法

    前言 默认您已经配置好相关环境. 如若没有,可以自行搜索"EasyX"下载安装. 本教程主要从代码方面讲解算法的实现. 至于理论部分还请大家自行搜索其他文章.(该类文章非常多) 种 ...

  4. 多边形区域填充算法--扫描线种子填充算法

    分享一下我老师大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow http://bl ...

  5. matlab中种子填充算法

    因为作业要求用简单种子填充和线扫描填充,所以在网上找了一个用matlab写的简单种子填充算法. https://www.cnblogs.com/tiandsp/archive/2012/12/06/2 ...

  6. 基于队列和哈希的种子填充算法

    继 https://blog.csdn.net/u013749051/article/details/84553642 之后, 我又对种子填充算法进行了改进,主要利用了哈希的思想,以空间换时间,把这个 ...

  7. 种子填充算法----计算机图形学

    种子填充算法: 种子填充算法的基本思想是:从多边形区域的一个内点开始,由内向外用给定的颜色画点直到边界为止. 区域 可以由内部点或边界来定义,一般都采用边界定义,即区域边界上所有像素被置为特定值,而区 ...

  8. java实现种子填充算法,Java编写图形学的种子填充算法

    用C写的图形学填充算法已经很多了,看到不少帖子都是在问关于如何用Java编写图形学的填充算法,说来也巧,我刚好要做一个这个方面的实验,用的是扫描线种子填充算法,由于时间仓促,代码质量可能不算很高,希望 ...

  9. 区域颜色填充-四连通种子填充算法

    首先确定封闭区域内的一个像素,称为种子点.将种子点率先引入栈中,并重复以下3个步骤:1.栈顶像素出栈:2.将出栈的像素填充上目标颜色:3.根据左上右下的顺序检查周围的像素,如果不为边缘且还未入栈则将该 ...

最新文章

  1. 用启明云端支持ESP32的GUI做了一个测温HMI交互界面
  2. springboot+maven实现模块化编程
  3. ITK:向量值图像像素的变换幅度
  4. Jmeter_初步认识随笔
  5. chrome同步_如何在Chrome中打开或关闭同步
  6. laravel允许所有网站进行跨域操作
  7. LeetCode 670 最大交换 (暴力+贪心、Python)
  8. PHP删除数组中的空值
  9. 如何查看Windows 10的具体版本号?
  10. explict关键字
  11. 从模糊搜索 1.0 到 3.0 的算法迭代历程 | 技术头条
  12. Linux服务器开机自动启动服务或脚本的方法
  13. 9. JavaScript 事件参考手册
  14. Notepad2 v4.22.03 (r4130) 轻量级文本编辑器。可替换系统记事本
  15. w3school和w3cschool两个网站有什么关系和区别?(转)
  16. 天猫盒子android tv,天猫魔盒刷机教程 把天猫魔盒刷成安卓系统教程(附刷机包)...
  17. 概率论与数理统计张宇9讲 第七讲 大数定律与中心极限定理
  18. 古典微分几何 近代微分几何资料
  19. 苹果表情 保存mysql_IOS表情存入MYSQL数据库失败
  20. linux中tail命令的作用,Linux中tail命令实例

热门文章

  1. Content Type
  2. 关于Wechat 的充值
  3. tar -zxvf 什么意思
  4. 关于html5的标签整理合集(二)
  5. labview虚拟心电监测系统_基于LabVIEW的心电信号检测平台的设计
  6. 9点击按钮修改valu属性
  7. Mapped Statements collection does not contain valu
  8. 学会享受努力奋斗的过程
  9. Python学习 10 - 基本数据类型小练习
  10. Linux小知识---常见的IO复用技术