精华内容
下载资源
问答
  • 区域填充算法

    2018-11-09 04:51:00
    区域填充算法

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

    也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

                   

            区域填充即给出一个区域的边界,要求对边界范围内的所有象素单元赋予指定的颜色代码。区域填充中最常用的是多边形填色,本节中我们就以此为例讨论区域填充算法。

    多边形填色即给出一个多边形的边界,要求对多边形边界范围的所有象素单元赋予指定的色代码。要完成这个任务,一个首要的问题,是判断一个象素是在多边形内还是外。数学上提供的方法是“扫描交点的奇偶数判断”法:

    1、将多边形画在纸上。

    2、用一根水平扫描线自左而右通过多边形而与多边形之边界相交。扫描线与边界相交奇次数后进入该多边形,相交偶次数后走出该多边形。图2.3.1示出这类情况:扫描线与多边形相交四点。相交a点之后入多边形;交b点(第2交点)之后出多边形;交c点(第3交点)之后又入多边形;交d点(第4交点)之后又出多边形。

    上述方法似乎能完满地解决问题,但事实并非如此,因为直线在光栅化后变成了占有单位空间的离散点。图2.3.1中的A点处和B、C处,在光栅化后变成图2.3.2所示的情况。此时,使用上述判断法则,在A、B、C处发现错判现象。在A处,扫描线通过一点后以为入多边形,其实此时已出多边形。结果是在A点之后的扫描线段上全都错误地填上色。在B和C处,因为光栅化后,使得扫描线通过交点的个数发生变化而同样导致填色错误。因此,原始的奇偶判断方法需要加以周密地改善,才能成为计算机中实用的填色算法。

    2_3_1.gif (3081 bytes)                         2_3_2.gif (2351 bytes)         

                                图2.3.1                                                                            图2.3.2

    填色算法分为两大类:

    1、扫描线填色(Scan-Line Filling)算法。这类算法建立在多边形边边界的矢量形式数据之上,可用于程序填色,也可用交互填色。

    2、种子填色(Seed Filling)算法。这类算法建立在多边形边边界的图象形式数据之上,并还需提供多边形界内一点的坐标。所以,它一般只能用于人机交互填色,而难以用于程序填色。

            区域填充即给出一个区域的边界,要求对边界范围内的所有象素单元赋予指定的颜色代码。区域填充可以分两步走,第一步先确定需要填充那些象素,第二步确定用什么颜色来填充。区域填充中最常用的是多边形填色。
    填色算法分为两大类: 1、扫描线填色(Scan-Line Filling)算法。这类算法建立在多边形边边界的矢量形式数据之上,可用于程序填色,也可用交互填色。 2、种子填色(Seed Filling)算法。这类算法建立在多边形边边界的图象形式数据之上,并还需提供多边形界内一点的坐标。所以,它一般只能用于人机交互填色,而难以用于程序填色。

    1>.扫描线填色算法

    扫描线填色算法的基本思想是:

    用水平扫描线从上到下扫描由点线段构成的多段构成的多边形。每根扫描线与多边形各边产生一系列交点。将这些交点按照x坐标进行分类,将分类后的交点成对取出,作为两个端点,以所填的色彩画水平直线。多边形被扫描完毕后,填色也就完成。
     

    一、算法的基本思想

    多边形以n, x_array, y_array形式给出,其中x_array,y_array中存放着多边形的n个顶点的x, y坐标。扫描线填色算法的基本思想是:

    用水平扫描线从上到下扫描由点线段构成的多段构成的多边形。每根扫描线与多边形各边产生一系列交点。将这些交点按照x坐标进行分类,将分类后的交点成对取出,作为两个端点,以所填的色彩画水平直线。多边形被扫描完毕后,填色也就完成。

    上述基本思想中,有几个问题需要解决或改善。它们是:

    1. 左、右顶点处理 当以1, 2, 3的次序画多边形外框时,多边形的左顶点和右顶点如图2.3.3 (a)、(b)所示的顶点2。它们具有性质:

    左顶点2:y1<y2<y3

    右顶点2:y1>y2>y3

    其中y1, y2, y3是三个相邻的顶点的y坐标。

    (a)左顶点 (b)右顶点

    2_3_3.gif (2620 bytes)

    图2.3.3

    当扫描线与多边形的每个顶点相交时,会同时产生2个交点,这是因为一个顶点同属于多边形之两条边的端点。这时,如果所交的顶点是左顶点或右顶点,填色就会因奇偶计数出错而出现错误。因此,对多边形的所有左、右顶点作如下处理;

    左、右顶点的入边(以该顶点为终点的那条边),即1, 2边之终点删去。即:

    对于左顶点:入边(x1, y1)(x2, y2)修改为(x1, y1)(,y2-1);

    对于右顶点:入边(x1, y1)(x2, y2)修改为(x1, y1)(,y2+1);

    其中m=,即入边之斜率。

    对于多边形的上顶点(y2>y1 & y2>y3)或下顶点(y2<y1 & y2<y3),奇偶记数保持正确,因此不必修改,保持相邻边原状不变。

    2. 水平边处理 水平边(y1=y2)与水平扫描线重合法求交点。因此,将水平边画出后删去,不参加求交及求交以后的操作。

    3. 扫描线与边的求交点方法采用递归算法 边(x1, y1)(x2, y2)与扫描线i+1的交点为:

    (当交点不为x1, y1时)

    否则,交点为x1, y1。

    由上式可知,求交点只须做两个简单的减法。

    4. 减少求交计算,采用活性边表 对于一根扫描线而言,与之相交的边只占多边形全部边的一部分。因此,在基本算法思想中,每根扫描线与多边形所有边求交的操作是一种浪费,需要加以改善。活性边表(Active List of Side)的采用将多边形的边分成两个子集:与当前扫描线相交的边的集合,以及与当前的扫描线不相交的边的集合。后者不必予以求交,这样就提高了算法的效率。

    (a) (b)在scan1的情况

      2_3_4.gif (4139 bytes)

    图2.3.4 活性边表及其指针的表示

    活性边表的构成方法是:

    1)将经过左、右顶点处理及剔除水平边后的多边形之各边按照max y值排序,存入一个线性表中。表中每一个元素代表一根边。第一个元素是max y值最大的边,最后一个元素是max y值最小的边。图2.3.4 (a)中的多边形所形成的线性表如(b)所示。其中F点和B点的y值相等,且为全部多边形的max y的最大值。因此FG, FE, AB, BC等四边排在表之首。而C点的y值>E点的y值,所以CH排在DE前面,余类推。在max y值相等的边之间,按任意次序排列。

    2)在上述线性表上加入两个指针first和last,即形成活性边表。这两个指针之间是与当前扫描线相交的边的集合和已经处理完(即扫描完)的边的集合。这两者的区分方法是在处理完的边上加上记号:? y=0。在last指针以后的是尚未与当前扫描线相交的,在first指针以前的是已经处理完了的边。对于图2.3.4 (a)中扫描线scan1的情况下,图2.3.4 (b)中列出first, last的位置。如果扫描线由上而下移到了scan2的位置,则活性边表的first应指向AB,last应指向CH。每根扫描线只须与位于first, last之间的,而且? y? 0的边求交即可。这就缩小了求交的范围。

    3)活性边表中每个元素的内容包括:

    ·边的max y值,记为y_top;

    ·与当前扫描线相交点的x坐标值,记为x_int;

    ·边的y方向当前总长。初始值为y2-y1。记为? y;

    ·边的斜率倒数:,记为x_change_per_scan。

    4)活性边在每根扫描线扫描之后刷新。刷新的内容有2项:

    ·调整first和last指针字间的参加求交的边元素之值:? y=? y-1; x_int = x_int - x_change_per_scan;

    ·调整first和last指针,以便让新边进入激活范围,处理完的边退出激活范围:

    当first所指边的? y=0时,first=first+1;

    当last所指的下一条边的y_top? 下一扫描线的y值时,last=last+1。

    二、扫描线填色程序

    程序2.3.1示出扫描线填色算法的程序。主程序名为fill_area(count, x, y),其中参数x, y是两个一维数组,存放多边形顶点(共count个)的x和y坐标。它调用8个子程序,彼此的调用关系如图2.3.5所示。各子程序的功能为:

      2_3_5.gif (4681 bytes)图2.3.5 fill_area的程序结构

     

    typedef struct {

    int y_top;

    float x_int;

    int delta_y;

    floaat x_change_per_scan;

    } EACH_ENTRY;

     

    EACH_ENTRY SIDES[MAX_POINT];

    int x[MAX_POINT], y[MAX_POINT];

    int side_count, first_s, last_s, scan, bottomscan, x_int_count, r;

    fill_area(count, x, y)

    int count, x[ ], y[ ];

    {

    sort_on_bigger_y(count);

    first_s=1;

    last_s=1;

    for (scan=sides[1].y_top; scan>bottomscan ?; scan - -)

       {

            up date_first_and_last(count, scan);

            process_x_intersections(scan, first_s, last_s);

            draw_lines (scan, x_int_count, first_s);

            update-_sides_list ( );

        }

    }

    void put_in_sides_list(entry, x1, y1, x2, y2, next_y);

    int entry, x1, y1, x2, y2, next_y;

    {

    int maxy;

    float x2_temp, x_change_temp;

    x_change_temp = (float) (x2-x1) / (float) (y2-y1);

    x2_temp =x2; /*以下为退缩一点操作. */

    if ((y2>y1) && (y2<next_y)) {

              y2 - - ;

              x2_temp - = x_change_temp;

                  }

    else {

              if ((y2<y1) && (y2 >next_y)) {

                        y2++;

                        x2_temp+=x_change_temp;

                                  }

             }

    /* 以下为插入活性表操作. */

    maxy = (y1 > y2)? y1: y2;

    while (( entry >1) && (maxy > sides [entry -1]. y_top))

                       {

                            sides[entry]=sides [entry ?];

                            entry - -;

                       }

    sides[entry]. y_top=maxy;

    sides[entry]. delta_y =abs(y2-y1)+1;

    if (y1>y2)

                    sides[entry]. x_int =x1;

    else{

                    sides[entry].x_int=x2_temp;

                    sides[entry]. x_change_per_scan=x_change_temp;

       }

    void sort_on_bigger_y(n)

    int n;

    {

    int k, x1, y1;

    side_count=0;

    y1=y[n];

    x1=x[n];

    bottomscan=y[n];

     

    for (k=1; k<n+1; k++)

         {

               if (y1 ! =y[k]) {

                                   side_count ++;

                                put_in_sides_list(side_count, x1, y1, x[k], y[k]);

                                    }

              else {

                                 move ((short)x1, (short)y1);

                                 line((short)x[k], (short)y1, status);

                      }

               if (y[k] <bottomscan) bottomscan=y[k];

               y1=y[k]; x1=x[k];

          }

    }

    void update_first_and_last(count, scan)

    int count, scan;

    {

    while((sides[last_s+1]. y_top>=scan) && (last_s <count)) last_s ++;

    while(sides[first_s]. delta_y = = 0) first_s ++;

    }

     

    void swap(x, y)

    EACH_ENTRY x, y;

    {

    int i_temp;

    float f_temp;

    i_temp=x.y_top; x.y_top=y.y_top; y.y_top=i_temp;

    f_temp=x.x_int; x.x_int=y.x_int; y.x_int=f_temp;

    i_temp=x.delta_y; x.delta=y.delta_y; y.delta_y=i_temp;

    f_temp=x.x_change_per_scan; x. x_change_per_scan=y. x_change_per_scan; y.x.

    change_per_scan=f_temp;

    }

     

    void sort_on_x(entry, first_s)

    int entry, first_s;

    {

    while((entry > first_s) && (sides[entry]. x_int < sides[entry-1]. x_int))

             {

                   swap (sides[entry], sides[entry-1]);

                   entry - -;

    }

    }

    void process_x_intersections(scan, first_s, last_s)

    int scan, first_s, last_s;

    {

    int k;

    x_int_cout=0;

    for(k=first_s; k<last_s+1; k++)

    {

    if(sides[k]. delta_y >0) {

                          x_int_count ++;

                          sort_on_x(k, first_s);

    }

    }

    }

     

    void draw_lines(scan, x_int_count, index)

    int scan, x_int_count, index;

    {

    int k, x, x1, x2;

    for (k=1; k< (int) (x_int_count/2+1.5); k++)

    {

                while(sides[index]. delta_y = = 0) index ++;

                x1=(int)(sides[index]. x_int +0.5);

                index ++;

                while(sides[index].delta_y = = 0) index ++;

                x2 = (int) (sides [index]. x_int +0.5);

                move((short)x1, (short)scan);

                line((short)x2, (short)scan, status);

                index ++;

    }

    }

    void update_sides_list( )

    {

    int k;

    for (k=first_s; k<last_s +1; k++)

       {

             if(sides[k].delta_y >0)

                       {

                             sides[k].delta_y - -;

                             sides[k]. x_int - = sides[k]. x_change_per_scan;

                       }

      }

    }

     

                           程序2.3.1 扫描线填色程序

    1、sort_on_bigger_y子程序的主要功能是按照输入的多边形,建立起活性边表。操作步骤是:对每条边加以判断:如非水平边则调用put_in_side_list子程序放入活性边来;如是水平边则直接画出。

    2、put_in_sides_list子程序的主要功能是将一条边存入活性边表之内。操作步骤是:对该边判别是否左顶点或右顶点,如果将入边之终点删去,按照y_top的大小在活性边表中找到该点的合适位置,在该边的位置中填入数据。

    3、update_first_and_last子程序的主要功能是刷新活性边表的first和last两根指针的所指位置,以保证指针指出激活边的范围。

    4、process_x_intersections子程序的主要功能是对活性边表中的激活边(即位于first和last之间的,并且? y? 0的边)按照x_int的大小排序。操作步骤是:从first到last,对每一根? y? 0的边,调用sort_on_x子程序排入活性边表中合适位置。

    5、sort_on_x子程序主要功能是将一条边side[entry],在活性边表的first到entry之间按x_int的大小插入合适位置。操作步骤是:检查位于entry的边的x_int是否小于位置entry-1的边的x_int,如是,调用swap子程序交换两条边的彼此位置。

    6、swap子程序的主要功能是交换活性边表中两条相邻位置边的彼此位置。

    7、draw_lines子程序的主要功能是在一条扫描线位于多边形内的部分,填上指定的色彩。操作步骤是:在活性边表的激活边范围内,依次取出Δy¹ 0两边的x_int,作为两个端点(x1, scan),(x2, scan),画一条水平线。

    8、update_sides_list子程序的主要功能是刷新活性边表内激活边的值:Δy=Dy-1

        x_int=x_int_x_chang_per_scan;2>种子填色算法
    种子填色又称边界填色(Boundary Filling)。它的功能是:给出多边形光栅化后的边界位置及边界色代码boundary,以及多边形之内的一点x, y位置,要求将

    种子填色又称边界填色(Boundary Filling)。它的功能是:给出多边形光栅化后的边界位置及边界色代码boundary,以及多边形之内的一点x, y位置,要求将颜色color填满多边形。

    通常采用的填法有两种:四邻法(4-connected)和八邻法。四邻法是已知x, y(图2.3.6(a)的黑色象素)是多边形内的一点,据此向上下左右四个方向测试(图2.3.6(a)中打勾的象素)、填色、扩散。四邻法的缺点是有时不能通过狭窄区域,因而不能填满多边形。如图2.3.6(b)所示,左下角方形中的种子(打点的象素)不能扩散到右上角的方形中,因为采用四邻法通不过中间的狭窄区域。八邻法是已知x, y(图2.3.6 (c)中黑色的象素)为多边形内的一点,即种子,据此可向周围的八个方向(图2.3.6(c)中打勾的象素)测试、填色、扩散。八邻法的缺点是有时要填出多边形的边界。如图2.3.6(d)所示的边界,按八邻法就会将色彩涂出多边形。由于填不满往往比涂出更易于补救,因此四邻法比八邻法用的更普通。

    四邻法种子填色基本程序如程序2.3.2所示。这种程序书写简洁,但运行效率不高,因为包含有多余的判断。在它的基础上可以写出各种改进的算法[8]。

    void seed_filling (x, y, fill_color, boundary_color)

    int x, y, fill_color, boundary_color;

    {

    int c;

    c=inquire_color(x, y);

    if((c< > boundary_color) && (c< > fill_color))

    {

    set_pixel(x, y, fill_color);

    seed_filling(x+1, y, fill_color, boundary_color);

    seed_filling(x-1, y, fill_color, boundary_color);

    seed_filling(x, y+1, fill_color, boundary_color);

    seed_filling(x, y-1, fill_color, boundary_color);

    }

    }

     

                        程序2.3.2 四邻法种子填色程序

                             2_3_6.gif (4035 bytes)                                               图2.3.6 四邻法和八邻法种子

     

    1.    

      四邻法

         
    2.    
    3.    

      四邻法不能填满此多边形

         
    4.    
    5.    

      八邻法

         
    6.    
    7.    

      八邻法会涂出此多边形

         

    颜色color填满多边形。

     

               

    给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow

    这里写图片描述
    展开全文
  • 本文主要介绍几种区域填充算法,重点解释多边形的扫描线填充算法,最后实现了多边形填充算法,包括在附录文件中。在参考【5】中,作者详细介绍了一系列区域填充算法,可以查看相应网页。代码的下载地址为:...

    本文主要介绍几种区域填充算法,重点解释多边形的扫描线填充算法,最后实现了多边形填充算法,包括在附录文件中。在参考【5】中,作者详细介绍了一系列区域填充算法,可以查看相应网页。代码的下载地址为:https://github.com/twinklingstar20/twinklingstar_cn_region_polygon_fill_scanline/

    1. 1.区域的定义和填充

    1.1像素定义的区域(Pixel-Defined Region)

    1.1.1        边界定义区域(boundary-defined)

    定义某些像素是边界,边界包围着一块区域。填充所有在边界内的相连通的像素,主要分下面几个步骤:

    从区域内部一个像素点开始

    判断这个像素是否是一个边界像素点或者已经被填充了

    如果都不是,就把它填充,然后开始设置邻居像素点。

    用图片演示这个过程,如下面的幻灯片所示,代码片段如下所示:

    void boundaryFill4 (int x, int y, int fill, int boundary)

    {

    int current;

    current = getPixel (x,y);

    if (current != boundary && current !=fill)

    {

    setColor(fill);

    setPixel(x,y);

    boundaryFill4 (x+1, y, fill, boundary);

    boundaryFill4 (x−1, y, fill, boundary);

    boundaryFill4(x, y+1, fill, boundary);

    bonddaryFill4(x, y−1, fill, boundary);

    }

    }

    1.1.2          内定义区域(interior-defined)

    内定义区域的定义是:给定一个像素S,颜色是C,区域R指与S连通的且颜色都是C的像素集合(Region R is the set of all pixels having color C that are “connected” to a given pixel S)。

    如果两个像素连通,则它们之间有一条“相邻(adjacent)”像素组成的连续路径,所以连通的概念就依赖“相邻”的定义。在图形学中,相邻通常有两种定义:

    (1)       四相邻(4-Adjacent):两个像素是四相邻的,则它们在彼此水平或者垂直相邻的位置上,如图1所示:

    图1. 四相邻

    (2)       八相邻(8-Adjacent):两个像素是八相邻的,则它们在彼此水平、垂直或者是斜方向上相邻的位置,如图2所示:

    图2. 八相邻

    如果两个像素是四连通(4-connected),指它们之间有一条四相邻像素组成的连续路径;如果两个像素是八连通(8-connected),指它们之间有一条四相邻像素组成的连续路径。举个例子,如下图3所示,是由黑色、灰色和白色组成的像素图,给定一个像素点S,则由它定义的四连通区域共有20像素,由它定义的八连通区域共有28个像素。

    图3 像素区域

    这里介绍两种简单的填充算法:

    (1)       一种称为洪水填充法。规定采用4连通的区域,下面用幻灯片演示了这个过程,下面的代码片段实现了该算法。由于没有使用到区域间的相关性,很多像素点可能会重复被填充。

    从内部一个像素点开始,并用新的颜色替换它

    填充四连通或者八连通区域,直到所有的内部点被替换了

    void floodFill4 (int x, int y, int fill, int oldColor)

    {

    if (getPixel(x,y) == oldColor)

    {

    setColor(fill);

    setPixel(x,y);

    floodFill4 (x+1, y, fill, oldColor);

    floodFill4 (x−1, y, fill, oldColor);

    floodFill4(x, y+1, fill, oldColor);

    floodFill4(x, y−1, fill, oldColor);

    }

    }

    缺点是:1)大量的嵌套调用;2)很多像素点可能会被测试多次;3)难以清楚的掌控由于嵌套调用所占的内存大小;4)如果算法多次测试一个像素,会导致占用的内存扩大。

    (2)利用像素间的相关性,可以提高算法的性能,并避免堆栈的溢出。每次填充在同一条扫描线上相邻的一排像素,同时把与它相邻的未填充的种子像素放在堆栈中,下面的幻灯片演示了这个过程。伪码如下所示:

    Push address of seed pixel on the stack;

    while( stack not empty)

    {

    Pop the stack to provide the next seed;

    Fill the run defined by the seed;

    In the row above find interior runs reachable from this run;

    Push the addresses of the rightmost pixels of each such run;

    Do the same for the row below the current run;

    }

    1.2符号定义的区域(Symbolically Defined Region)

    这里简单介绍下符号定义的区域的分类,详细参见参考【4】,主要包括两类:

    (1)用一系列的矩形方块表示的区域;

    (2)通过一条表示一个区域边界的路径来界定一个区域:

    1)用一个数学公式来定义边界,例如采用(x-122)^2+(y-36)^2=25来定义一个圆的区域

    2)通过一系列的多边形顶点,像(x1,y1),(x2,y2),(x3,y3)…(xn,yn)来定义一个多边形区域

    3)通过一系列相邻的像素来定义。

    4)链码(chain code),这是一个很经典的方法,在参考【3】和【4】中都有简单的介绍,这里不详细介绍这块知识

    5)其它

    2.多边形填充算法

    2.1算法思想

    参考【5】,扫描线填充算法的基本思想是:每条水平扫描线与多边形的边产生一系列交点,交点之间形成一条一条的线段,该线段上的像素就是需要被填充的像素。将这些交点按照x坐标排序,将排序后的交点两两成对,作为线段的两个端点。水平扫描线从上到下(或从下到上)扫描由多条首尾相连的线段,使用要求的颜色填充该水平线段上的像素。多边形扫描完成后,颜色填充也就完成了。扫描线填充算法可以归纳为以下4个步骤:

    (1)       求交,计算扫描线与多边形的交点;

    (2)       交点排序,对第(1)步得到的交点按照x值从小到大进行排序;

    (3)       颜色填充,对排序后的交点两两组成一个水平线段,以画线段的方式进行颜色填充;

    (4)       是否完成多边形扫描?如果是就结束算法,如果不是就改变扫描线,然后转第1步继续处理;

    整个算法的关键是第1步,需要用尽量少的计算量求出交点,还要考虑交点是线段端点的特殊情况,最后,交点的计算最好是整数,便于光栅设备输出显示。对于每一条扫描线,如果每次都按照正常的线段与直线相交算法进行计算,则计算量大,而且效率低下,如图(4)所示:

    图4. 扫描线算法

    2.2存在的问题

    利用上述算法还存在几个问题,接下来分别讨论:

    (1)       如果多个多边形相邻的话,它们可能会共享一条边,那么共享边可能会被绘制两次,图5和图6演示了该错误:

    图5. 相邻的两个三角形共享边

    图6. 左图是背景颜色与前景颜色混合的情况,右图是不绘制共享边的情况

    对这个问题有一种很好的解决方案是:

    原则1:每个多边形只拥有它左边的像素,即采用左闭右开的原则,如果边是水平的话,则只拥有底边。

    图7. 共享边的解决方案

    (2)       如图7所示,若采用Bresenham直线绘制算法,(参见文章,《布雷森汉姆直线算法》可能会出现一些像素超出边所在的范围:

    原则2:在计算完扫描线与边的交点后,会形成一条条的首尾相连的线段,用xLeft表示左端点,xRight表示右端点,xLeft和xRight是实数,取大于等于xLeft的最小整数xNewLeft,取小于等于xRight的最大整数xNewRight,则绘制像素范围是[xNewLeft,xNewRight),如果xNewRight

    (3)       水平扫描线与端点发生相交的情况。如图8所示,穿过顶点H的扫描线,发生了两次相交(一次是与边GH,一次是与边HI),所以2.1描述的算法思想的第1步结束后,H点会存储两次,排完序后H两边的奇偶性会相同,因此会错误的将H右边的像素进行填充。(本图是从参考【4】中获取的,个人觉得该图解释这个问题,有点牵强。如果整个图左右翻转的话,解释这个问题就特别清楚了,算法思想的第1步结束后,该扫描线上共有三个交点:(H,H,右交点),这样问题就明显了)。这里采用两条原则,可以很简单的解决这个问题:

    图8. 填充多边形

    原则3:忽略任何一条与水平边的相交计算;

    原则4:如果交点是边的上端点,则把该端点忽略。

    举个遵守该该原则的例子,如图9所示,得到每条边端点相交的数量:

    图9. 一个多边形的端点相交的数量

    2.3数据结构设计

    2.3.1活动边链表(Active-Edge List,AEL)

    在计算相邻两条扫描线与一条边的相交时,可以利用它们之间的相关性。假设,一条边与的斜率是k,与扫描线y相交于x,则该边与扫描线y+1相交于x+1/k点的位置,利用这个特性可以减少运算量。为了方便进行该运算,有人提出了一种数据结构,称为活动边链表,链表的每个节点存储三个数据:(1)与当前水平扫描线的交点xint;(2)斜率m的倒数,1/m;(3)边的上端点的yhigh。举个例子来说该结构的存储方式,如图10所示,虚线代表了水平扫描线,在该水平扫描线上与多边形共有4个交点,则在AEL中会存储4个节点,4个结点按照xint从小到大排序。

    图10. 活动边链表

    2.3.2边表(Edge Table,ET)

    边表存储的是边的信息,边表中每个节点的数据结构与AEL中每个节点的相同,同样存储了三个信息:(1)边下端点X坐标xbottom;(2)边斜率的倒数;(3)边上端点Y坐标yhigh。当AEL表进行更新时,边表这种数据结构提供了快速索引的功能。如图10中多边形的边信息,用边表表示,如图11所示,这里就记录了其中四条边的信息。首先用一个边节点的数组,一条边下端点的Y坐标值,表示该数组的索引。图10中,Y=20扫描线上,共有两条边(原则3,忽略水平边),所以把两条边的信息记录节点,该节点存储在数组索引号20的表项后面,注意:在我的实现中,要求这个链表中的节点也是按照xbottom从小到大的顺序排列的;例如扫描线39所示,不存在边的下端点在该扫描线上,则索引号39的表项后面为空。

    图11. 边表

    2.4算法实现

    如下面的代码片段所示,主要有如下几个步骤:

    (1)       分配AEL的表头g_ptrAELHead和边表g_ptrEdgeTable[EDGE_TABLE_SIZE]的表头内存空间,由于现在显示器在垂直方向的分辨率一般不超过1024,所以这里不进行优化。

    (2)       初始化边表

    (3)       在当前扫描线上,在ET中是否存在表项,如果存在,则插入到AEL表中。

    (4)       填充该扫描线

    (5)       更新AEL。AEL中是否有边的y坐标值大于或者等于下一条扫描线(原则1:上边不进行绘制),由于AEL结点中保存有yhigh,这一步很容易判断,将相应的节点删除。

    (6)       判断算法是否结束,否则的话重复(3)-(5)几个步骤。

    typedef struct _Edge

    {

    doubledbX;

    doubledbDelta;

    intinMaxY;

    _Edge*ptrNext;

    }Edge;

    Edge*g_ptrEdgeTable[EDGE_TABLE_SIZE];

    Edge*g_ptrAELHead;

    void scanLineFill(Vector* ptrPolygon, int inNumPoly,DWORD inColor)

    {

    allocEdges();

    initEdgeTable(ptrPolygon,inNumPoly);

    for(int y=g_inMinY ; y

    {

    insertAEL(g_ptrAELHead,g_ptrEdgeTable[y]);

    fillAELScanLine(g_ptrAELHead,y,inColor);

    updateAEL(g_ptrAELHead,y+1);

    }

    deallocEdges();

    }

    2.5算法结果演示

    如图12所示,实现该算法,并用glut库实现了个简单的Demo,按’U’,’L’可以放大或者缩小图像,即放大缩小每个“像素”占据的像素大小。

    图12. 算法演示

    3.参考

    【3】冈萨雷斯《数字图像处理》

    【4】F.S Hill, JR. 《Computer Graphics Using OpenGL, Second Edition》

    展开全文
  • 本文实例为大家分享了OpenGL实现不规则区域填充算法,供大家参考,具体内容如下一、简单递归利用Dfs实现简单递归填充。核心代码:// 简单深度搜索填充 (四连通)void DfsFill(int x, int y){if (x < 0 || y < 0...

    本文实例为大家分享了OpenGL实现不规则区域填充算法,供大家参考,具体内容如下

    一、简单递归

    利用Dfs实现简单递归填充。

    核心代码:

    // 简单深度搜索填充 (四连通)

    void DfsFill(int x, int y)

    {

    if (x < 0 || y < 0 || x>23 || y>23)

    {

    return;

    }

    if (a[x][y] == 0)

    {

    a[x][y] = 2;

    DfsFill(x - 1, y);

    DfsFill(x + 1, y);

    DfsFill(x, y - 1);

    DfsFill(x, y + 1);

    }

    }

    二、扫描线种子填充算法(四连通)

    1. 种子点(x,y)入栈。

    2. 栈顶元素(x,y)出栈作为种子点。

    3. 从种子点(x,y)开始沿着扫描线向左右两个方向逐个像素进行填充,直到到达边界像素为止。

    4. 将上述填充区段的左右端点的横坐标分别记为xleft和xright.

    5. 在与当前扫描线相邻的上下两条扫描线的[xleft,xright]范围内进行检查,看看是否全部为边界像素或已填充像素,若存在着非边界且未填充的像素,那么将该区段的最右端像素作为种子点入栈。

    7fa1872bb463c1d8fdd5332c5ca9a24e.png

    void ScanFill(int x, int y)

    {

    if (a[x][y]!=0)

    {

    return;

    }

    Pos first(x, y);

    s.push(first);

    while (!s.empty())

    {

    int rightX = 0;

    int leftX = 0;

    Pos cur = s.top();

    s.pop();

    a[cur.x][cur.y] = 2;

    // 遍历当前行

    for (int i = 1; i < 24; i++)

    {

    if (cur.x + i < 24)

    {

    if (a[cur.x + i][cur.y] == 0)

    a[cur.x + i][cur.y] = 2;

    else

    {

    rightX = cur.x + i - 1;

    break;

    }

    }

    if (i==23)

    {

    rightX = 23;

    }

    }

    for (int i = 1; i < 24; i++)

    {

    if (cur.x - i > -1)

    {

    if (a[cur.x - i][cur.y] == 0)

    a[cur.x - i][cur.y] = 2;

    else

    {

    leftX = cur.x - i + 1;

    break;

    }

    }

    if (i == 0)

    {

    leftX = 0;

    }

    }

    cout << leftX <

    // 判断上行

    int upRightX = -1;

    for (int i = leftX;i<=rightX;i++)

    {

    if (a[i][cur.y+1]==0 && cur.y+1<24)

    {

    upRightX = i;

    }

    }

    if (upRightX!=-1)

    {

    Pos temPos(upRightX,cur.y+1);

    s.push(temPos);

    }

    // 判断下行

    int downRightX = -1;

    for (int i = leftX; i <= rightX; i++)

    {

    if (a[i][cur.y - 1] == 0 && cur.y - 1 >=0)

    {

    downRightX = i;

    }

    }

    if (downRightX != -1)

    {

    Pos temPos(downRightX, cur.y - 1);

    s.push(temPos);

    }

    }

    }

    完整代码:

    #include

    #include

    #include "gl/glut.h"

    #include "iostream"

    using namespace std;

    #define PI 3.14

    struct Pos

    {

    int x;

    int y;

    Pos(int mx, int my) :x(mx), y(my) {};

    Pos() :x(0), y(0) {};

    };

    stack s;

    int a[24][24] = { 0 };

    void init(void)

    {

    glClearColor(1.0, 1.0, 1.0, 1.0);

    glMatrixMode(GL_PROJECTION);//设置投影矩阵

    gluOrtho2D(0.0, 600.0, 0.0, 600.0);//二维视景区域

    glPointSize(12.0f);

    }

    // 画棋子

    void Drawtri(int x,int y,int color)

    {

    double n = 200;//分段数

    float R = 10;//半径

    int i;

    if (color == 1)

    {

    glColor3f(1.0, 0.0, 0.0);

    }

    else if (color == 2)

    {

    glColor3f(0.0, 1.0, 0.0);

    }

    glBegin(GL_POLYGON);

    glVertex2f(x, y);

    for (i = 0; i <= n; i++)

    glVertex2f(R*cos(2 * PI / n * i)+x, R*sin(2 * PI / n * i)+y);

    glEnd();

    glPopMatrix();

    }

    // 绘制格线

    void playMap()

    {

    glColor3f(0.0, 0.0, 0.0);

    glBegin(GL_LINES);

    for (int i = 0; i < 600; i += 25)

    {

    glVertex2f(i, 0);

    glVertex2f(i, 600);

    }

    for (int j = 0; j < 600; j += 25)

    {

    glVertex2f(0, j);

    glVertex2f(600, j);

    }

    glEnd();

    for (int k = 0; k < 24; k++)

    {

    for (int l = 0; l < 24; l++)

    {

    if (a[k][l] == 1)

    {

    Drawtri(k * 25 + 12, l * 25 + 12,1);

    }

    else if (a[k][l] == 2)

    {

    Drawtri(k * 25 + 12, l * 25 + 12, 2);

    }

    }

    }

    }

    // 简单深度搜索填充 (四连通)

    void DfsFill(int x, int y)

    {

    if (x < 0 || y < 0 || x>23 || y>23)

    {

    return;

    }

    if (a[x][y] == 0)

    {

    a[x][y] = 2;

    DfsFill(x - 1, y);

    DfsFill(x + 1, y);

    DfsFill(x, y - 1);

    DfsFill(x, y + 1);

    }

    }

    // 扫描线种子填充算法(四连通)

    void ScanFill(int x, int y)

    {

    if (a[x][y]!=0)

    {

    return;

    }

    Pos first(x, y);

    s.push(first);

    while (!s.empty())

    {

    int rightX = 0;

    int leftX = 0;

    Pos cur = s.top();

    s.pop();

    a[cur.x][cur.y] = 2;

    // 遍历当前行

    for (int i = 1; i < 24; i++)

    {

    if (cur.x + i < 24)

    {

    if (a[cur.x + i][cur.y] == 0)

    a[cur.x + i][cur.y] = 2;

    else

    {

    rightX = cur.x + i - 1;

    break;

    }

    }

    if (i==23)

    {

    rightX = 23;

    }

    }

    for (int i = 1; i < 24; i++)

    {

    if (cur.x - i > -1)

    {

    if (a[cur.x - i][cur.y] == 0)

    a[cur.x - i][cur.y] = 2;

    else

    {

    leftX = cur.x - i + 1;

    break;

    }

    }

    if (i == 0)

    {

    leftX = 0;

    }

    }

    cout << leftX <

    // 判断上行

    int upRightX = -1;

    for (int i = leftX;i<=rightX;i++)

    {

    if (a[i][cur.y+1]==0 && cur.y+1<24)

    {

    upRightX = i;

    }

    }

    if (upRightX!=-1)

    {

    Pos temPos(upRightX,cur.y+1);

    s.push(temPos);

    }

    // 判断下行

    int downRightX = -1;

    for (int i = leftX; i <= rightX; i++)

    {

    if (a[i][cur.y - 1] == 0 && cur.y - 1 >=0)

    {

    downRightX = i;

    }

    }

    if (downRightX != -1)

    {

    Pos temPos(downRightX, cur.y - 1);

    s.push(temPos);

    }

    }

    }

    void displayFcn(void)

    {

    glClear(GL_COLOR_BUFFER_BIT);

    playMap();

    glFlush();

    }

    void mouse(GLint button, GLint action, GLint x, GLint y)

    {

    int curX, curY;

    if (button == GLUT_LEFT_BUTTON && action == GLUT_DOWN)

    {

    curX = x / 25;

    curY = (600 - y) / 25;

    a[curX][curY] = 1;

    glutPostRedisplay();//重绘窗口

    }

    if (button == GLUT_RIGHT_BUTTON && action == GLUT_DOWN)

    {

    curX = x / 25;

    curY = (600 - y) / 25;

    ScanFill(curX, curY);

    glutPostRedisplay();//重绘窗口

    }

    }

    void main(int argc, char** argv)

    {

    glutInit(&argc, argv);

    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);

    glutInitWindowPosition(300, 100);

    glutInitWindowSize(600, 600);

    glutCreateWindow("mouse");

    init();

    glutDisplayFunc(displayFcn);

    glutMouseFunc(mouse);

    glutMainLoop();

    }

    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

    展开全文
  • 平面区域填充算法是计算机图形学领域的一个很重要的算法,区域填充即给出一个区域的边界(也可以是没有边界,只是给出指定颜色),要求将边界范围内的所有象素单元都修改成指定的颜色(也可能是图案填充)。...

        平面区域填充算法是计算机图形学领域的一个很重要的算法,区域填充即给出一个区域的边界(也可以是没有边界,只是给出指定颜色),要求将边界范围内的所有象素单元都修改成指定的颜色(也可能是图案填充)。区域填充中最常用的是多边形填色,本文中我们就讨论几种多边形区域填充算法。

    一、种子填充算法(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) “4-联通”和“8-联通”填充效果

        并不能仅仅因为图1的填充效果就认为“8-联通算法”一定比“4-联通算法”好,应该根据应用环境和实际的需求选择联通搜索方式,在很多情况下,只有“4-联通算法”才能得到正确的结果。

    1.1 注入填充算法(Flood Fill Algorithm)
      注入填充算法不特别强调区域的边界,它只是从指定位置开始,将所有联通区域内某种指定颜色的点都替换成另一种颜色,从而实现填充效果。注入填充算法能够实现颜色替换之类的功能,这在图像处理软件中都得到了广泛的应用。注入填充算法的实现非常简单,核心就是递归和搜索,以下就是注入填充算法的一个实现:
     

    164 void FloodSeedFill(int x, int y, int old_color, int new_color)
    
    165 {
    
    166     if(GetPixelColor(x, y) == old_color)
    
    167     {
    
    168         SetPixelColor(x, y, new_color);
    
    169         for(int i = 0; i < COUNT_OF(direction_8); i++)
    
    170         {
    
    171             FloodSeedFill(x + direction_8[i].x_offset,
    
    172                           y + direction_8[i].y_offset, old_color, new_color);
    
    173         }
    
    174     }
    
    175 }
    

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

    15 typedef struct tagDIRECTION
    
    16 {
    
    17     int x_offset;
    
    18     int y_offset;
    
    19 }DIRECTION;
    DIRECTION direction_8[] = { {-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}, {-1, -1} };

    这个是搜索类算法中常用的技巧,无需做太多说明,其实只要将其替换成如下direction_4的定义,就可以将算法改成4个联通方向填充算法:

     DIRECTION direction_4[] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} };

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

    图(2) 注入填充算法实现

    1.2 边界填充算法(Boundary Fill Algorithm)

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

    177 void BoundarySeedFill(int x, int y, int new_color, int boundary_color)
    
    178 {
    
    179     int curColor = GetPixelColor(x, y);
    
    180     if( (curColor != boundary_color)
    
    181         && (curColor != new_color) )
    
    182     {
    
    183         SetPixelColor(x, y, new_color);
    
    184         for(int i = 0; i < COUNT_OF(direction_8); i++)
    
    185         {
    
    186             BoundarySeedFill(x + direction_8[i].x_offset,
    
    187                              y + direction_8[i].y_offset, new_color, boundary_color);
    
    188         }
    
    189     }
    
    190 }
    

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

    图(3) 边界填充算法实现

     

     

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • 多边形区域填充算法

    2014-10-28 09:22:06
    多边形区域填充算法,
  • 西安工程大学实验报告 课程 实验名称区第 1 页 共 6 页 系 别 组别_ 实 验 报 告 日 期 年 月 日 姓 名 学号 报 告 退 发 ( 订正 重做 ) E_mail_ 教 师 审 批 评 分_ 区域填充算法 一实验目的和任务 1?学习多边形...
  • 多边形区域填充算法--扫描线种子填充算法
  • 区域填充算法要求区域是连通的,因为只有在连通区域中,才可能将种子点的颜色扩展到区域内的其它点。1、区域有两种表示形式1)内点表示:枚举出区域内部的所有象素,内部所有象素着同一个颜色,边界像素着与内部象素...
  • 本文实例为大家分享了opengl实现直线扫描算法和区域填充算法,供大家参考,具体内容如下 总体介绍 1、采用直线扫描算法绘制一条线段,直线由离散点组成 2、利用区域填充算法绘制多边形区域,区域由离散点组成 开发...
  • PAGE / NUMPAGES 贵州大学计算机图形学实验报告 学院计算机科学与信息学院 专业软件工程 班级 姓名 学号 实验组 实验时间 指导教师 成绩 实验项目名称 实验三 多边形的扫描转换算法区域填充算法 实验目的 通过本实验...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,017
精华内容 406
关键字:

区域填充算法