现在的位置: 首页 > 综合 > 正文

“像元分组” 算法:将二值图像中处于相邻的元素进行分组标号, 使得属于同一个分组的像元集合,其编号都相同

2018年01月17日 ⁄ 综合 ⁄ 共 3339字 ⁄ 字号 评论关闭

1.需求规格说明

二值图像中每个元素的值只能为 或 0,其中 表示有效像元,表示图像的背景。如果一个元素在另外一个元素的上、下、左、右四个方向,称两个元素为相邻元素。“像元分组” 算法是将二值图像中处于相邻的元素进行分组标号, 使得属于同一个分组的像元集合,其编号都相同。

2.总体分析与设计

在开始设计之前我在网上看到了bwlabel()函数,这个函数的功能正是实现题目中这种功能。

相关说明:

L = bwlabel(BW,n)

返回一个和BW大小相同的L矩阵,包含了标记了BW中每个连通区域的类别标签,这些标签的值为1、2、num(连通区域的个数)。n的值为4或8,表示是按4连通寻找区域,还是8连通寻找,默认为8。

 

四连通或八连通是图像处理里的基本感念:而8连通,是说一个像素,如果和其他像素在上、下、左、右、左上角、左下角、右上角或右下角连接着,则认为他们是联通的;4连通是指,如果像素的位置在其他像素相邻的上、下、左或右,则认为他们是连接着的,连通的,在左上角、左下角、右上角或右下角连接,则不认为他们连通。

 

[L,num] = bwlabel(BW,n)

这里num返回的就是BW中连通区域的个数。

在此之外我还看了下计算机图形学是怎样的原理,不过比较麻烦而且看到最后没看懂就放弃了。

不过有种种子填充算法还是很精髓的,用递归来做,实在不忍舍弃

就摘录了下

2) 种子填充算法
  种子填充算法又称为边界填充算法。其基本思想是:从多边形区域的一个内点开始,由内向外用给定的颜色画点直到边界为止。如果边界是以一种颜色指定的,则种子填充算法可逐个像素地处理直到遇到边界颜色为止。
种子填充算法常用四连通域和八连通域技术进行填充操作。
  从区域内任意一点出发,通过上、下、左、右四个方向到达区域内的任意像素。用这种方法填充的区域就称为四连通域;这种填充方法称为四向连通算法。
  从区域内任意一点出发,通过上、下、左、右、左上、左下、右上和右下八个方向到达区域内的任意像素。用这种方法填充的区域就称为八连通域;这种填充方法称为八向连通算法。
  一般来说,八向连通算法可以填充四向连通区域,而四向连通算法有时不能填充八向连通区域。例如,八向连通填充算法能够正确填充如图2.4a所示的区域的内部,而四向连通填充算法只能完成如图2.4b的部分填充。

图2.4 四向连通填充算法

   

a) 连通域及其内点

b) 填充四连通域

四向连通填充算法:
a) 种子像素压入栈中;
b) 如果栈为空,则转e);否则转c);
c) 弹出一个像素,并将该像素置成填充色;并判断该像素相邻的四连通像素是否为边界色或已经置成多边形的填充色,若不是,则将该像素压入栈;
d) 转b);
e) 结束。
四向连通填充方法可以用递归函数实现如下:

算法2.3 四向连通递归填充算法:
void BoundaryFill4(int x, int y, long FilledColor, long BoundaryColor)
{
  long CurrentColor;
  CurrentColor = GetPixelColor(x,y);
  if (CurrentColor != BoundaryColor && CurrentColor != FilledColor)
  {
   SetColor(FilledColor);
   SetPixel (x,y);
   BoundaryFill4(x+1, y, FilledColor, BoundaryColor);
   BoundaryFill4(x-1, y, FilledColor, BoundaryColor);
   BoundaryFill4(x, y+1, FilledColor, BoundaryColor);
   BoundaryFill4(x, y-1, FilledColor, BoundaryColor);
  }
}

上述算法的优点是非常简单,缺点是需要大量栈空间来存储相邻的点。一个改进的方法就是:通过沿扫描线填充水平像素段,来处理四连通或八连通相邻 点,这样就仅仅只需要将每个水平像素段的起始位置压入栈,而不需要将当前位置周围尚未处理的相邻像素都压入栈,从而可以节省大量的栈空间。

当我做的差不多的时候,才发现要用队列来做。只好重新思索重写。

 

3.编码

<五号宋体>,具体内容:问题是如何解决的,编码过程中的困难与解决方法。)

在实现算法的时候发现第三题的测试数据是前面有几行注释一样的文字,然后才是我们想要的数据。

我当时用的是getline函数实现的不过感觉有点别扭就是。具体实现如下

char *first = new char [50];char *end= new char[50];char *strv = new char[50];char *strr = new char[50];char *strc = new char[50];

   /* double v; int r, c;*/

fin.getline(first, 50);

fin.getline(strv, 50);

fin.getline(strr, 50);

fin.getline(strc, 50); 

fin.getline(end, 50);

4.

结果如下

 

 

5.小结

<五号宋体>,具体内容:经验与体会,或需要改进的地方)

重新体验了getline函数的使用方法,在中间还体验了一下计算机图形学的算法,我还是想那种递归的用栈来做的方法,代码很简单,在上面已经给过。

6.附录

<五号宋体>,部分核心代码)

下面是是现代的具体操作及其注释

for (int i = 0; i != row; i++)

{

for (int j = 0; j != column; j++)

{

cout << a[i][j] << " ";

}

cout << endl;

}

 

 

for (int i = 0; i != row; i++)

{

for (int j = 0; j != column; j++)

{

while (a[i][j] == 1)                                    //编组的触发条件

{

q.m = i;

q.n = j;//记录点

a[i][j] = o;

z.EnQueue(q);                                     //初始判断点

 

 

 

while (z.IsEmpty() == false)                 //如果队列空,则一组内全部改完,退出循环

{

int d = 0;

int c, v;

z.DeQueue(q);                               //避免重复判断,保证能退出循环

c = q.m;

v = q.n;

while (d<4)                                   //四种情况,上下左右扫描

{

g = c + move[d].x;

h = v + move[d].y;

if (g<row&&g >= 0 && h<column&&h >= 0 && a[g][h] == 1)

{

a[g][h] = o;

q.m = g;

q.n = h;

z.EnQueue(q);

}

d++;

}

}

 

//按行开始读,读到一就进入队列并且把它::标记为标记位&&同时记录该点的位置&&进入队列::(原子性的),下一步:查找这个点的四周,用whiled<4)和g = c + move[d].x;

//h = v + move[d].y实现,如果有1执行::()::操作,从队列中退出上一个。查找队列首的元素四周

 

o++;                                                   //触发一次分组则组号加1

}

}

}

 

 

 

for (int i = 0; i != row; i++)

{

for (int j = 0; j != column; j++)

{

if (a[i][j] != 0){

a[i][j] = a[i][j] - 1;

}

}

}

【上篇】
【下篇】

抱歉!评论已关闭.