2015-12-30 16:48:37 minushuang 阅读数 9508
  • Java经典算法讲解

    在面试中,算法题目是必须的,通过算法能够看出一个程序员的编程思维,考察对复杂问题的设计与分析能力,对问题的严谨性都能够体现出来。一个算法的好坏,直接影响一个方法调用的性能,进而影响软件的整体性能。算法是学习所有编程语言的基础,在Java的学习过程中首先也会选择以算法起步,本次课程重点讲解Java开发中常用的基本算法。

    29968 人正在学习 去看看 张中强

原理:在特殊领域运算形式——结构元素(Sturcture Element),在每个像素位置上与二值图像对应的区域进行特定的逻辑运算。运算结构是输出图像的相应像素。运算效果取决于结构元素大小内容以及逻辑运算性质。

结构元素:膨胀和腐蚀操作的最基本组成部分,用于测试输出图像,通常要比待处理的图像小还很多。二维平面结构元素由一个数值为0或1的矩阵组成。结构元素的原点指定了图像中需要处理的像素范围,结构元素中数值为1的点决定结构元素的邻域像素在进行膨胀或腐蚀操作时是否需要参与计算。

先来定义一些基本符号和关系。

1.         元素

设有一幅图象X,若点aX的区域以内,则称aX的元素,记作aX,如图6.1所示。

2.         B包含于X

设有两幅图象BX。对于B中所有的元素ai,都有aiX,则称B包含于(included in)X,记作B  X,如图6.2所示。

3.         B击中X

设有两幅图象BX。若存在这样一个点,它即是B的元素,又是X的元素,则称B击中(hit)X,记作BX,如图6.3所示。

4.         B不击中X

设有两幅图象BX。若不存在任何一个点,它即是B的元素,又是X的元素,即BX的交集是空,则称B不击中(miss)X,记作BX=Ф;其中∩是集合运算相交的符号,Ф表示空集。如图6.4所示。

6.1     元素

6.2     包含

6.3     击中

6.4     不击中

5.         补集

设有一幅图象X,所有X区域以外的点构成的集合称为X的补集,记作Xc,如图6.5所示。显然,如果BX=Ф,则BX的补集内,即B  Xc

6.5     补集的示意图

6.         结构元素

设有两幅图象BX。若X是被处理的对象,而B是用来处理X的,则称B为结构元素(structure element),又被形象地称做刷子。结构元素通常都是一些比较小的图象。

7.         对称集

设有一幅图象B,将B中所有元素的坐标取反,即令(xy)变成(-x-y),所有这些点构成的新的集合称为B的对称集,记作Bv,如图6.6所示。

8.         平移

设有一幅图象B,有一个点a(x0,y0),将B平移a后的结果是,把B中所有元素的横坐标加x0,纵坐标加y0,即令(xy)变成(x+x0y+y0),所有这些点构成的新的集合称为B的平移,记作Ba,如图6.7所示。

6.6     对称集的示意图

6.7     平移的示意图

好了,介绍了这么多基本符号和关系,现在让我们应用这些符号和关系,看一下形态学的基本运算。

6.1 腐蚀

把结构元素B平移a后得到Ba,若Ba包含于X,我们记下这个a点,所有满足上述条件的a点组成的集合称做XB腐蚀(Erosion)的结果。用公式表示为:E(X)={a| Ba  X}=X  B,如图6.8所示。

6.8     腐蚀的示意图

6.8X是被处理的对象,B是结构元素。不难知道,对于任意一个在阴影部分的点aBa 包含于X,所以XB腐蚀的结果就是那个阴影部分。阴影部分在X的范围之内,且比X小,就象X被剥掉了一层似的,这就是为什么叫腐蚀的原因。

值得注意的是,上面的B是对称的,即B的对称集Bv=B,所以XB腐蚀的结果和X Bv腐蚀的结果是一样的。如果B不是对称的,让我们看看图6.9,就会发现XB腐蚀的结果和X Bv腐蚀的结果不同。

6.9     结构元素非对称时,腐蚀的结果不同

6.8和图6.9都是示意图,让我们来看看实际上是怎样进行腐蚀运算的。

在图6.10中,左边是被处理的图象X(二值图象,我们针对的是黑点),中间是结构元素B,那个标有origin的点是中心点,即当前处理元素的位置,我们在介绍模板操作时也有过类似的概念。腐蚀的方法是,拿B的中心点和X上的点一个一个地对比,如果B上的所有点都在X的范围内,则该点保留,否则将该点去掉;右边是腐蚀后的结果。可以看出,它仍在原来X的范围内,且比X包含的点要少,就象X被腐蚀掉了一层。

6.10   腐蚀运算

6.11为原图,图6.12为腐蚀后的结果图,能够很明显地看出腐蚀的效果。

6.11    原图

6.12   腐蚀后的结果图

下面的这段程序,实现了上述的腐蚀运算,针对的都是黑色点。参数中有一个BOOL变量,为真时,表示在水平方向进行腐蚀运算,即结构元素B  ;否则在垂直方向上进行腐蚀运算,即结构元素B  

腐蚀源码

BOOL Erosion(HWND hWnd,BOOL Hori)

{

       DWORD                             OffBits,BufSize;

LPBITMAPINFOHEADER    lpImgData;

       LPSTR                   lpPtr;

       HLOCAL                  hTempImgData;

       LPBITMAPINFOHEADER    lpTempImgData;

       LPSTR                            lpTempPtr;

       HDC                      hDc;

       HFILE                    hf;

       LONG                    x,y;

       unsigned char              num;

       int                        i;

//为了处理方便,仍采用256级灰度图,不过只用调色板中0和255两项

if( NumColors!=256){  

           MessageBox(hWnd,"Must be a mono bitmap with grayscale palette!",

"Error Message",MB_OK|MB_ICONEXCLAMATION);

return FALSE;

}

OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);

//BufSize为缓冲区大小

       BufSize=OffBits+bi.biHeight*LineBytes;

       //为新的缓冲区分配内存

       if((hTempImgData=LocalAlloc(LHND,BufSize))==NULL)

{

            MessageBox(hWnd,"Error alloc memory!","Error Message",

MB_OK|MB_ICONEXCLAMATION);

return FALSE;

    }

     lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData);    

       lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData);

       //拷贝头信息和位图数据     

       memcpy(lpTempImgData,lpImgData,BufSize);

       if(Hori)

       {   

//在水平方向进行腐蚀运算

              for(y=0;y<bi.biHeight;y++){

                     //lpPtr指向原图数据,lpTempPtr指向新图数据

                     lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes)+1;

                     lpTempPtr=(char*)lpTempImgData+

(BufSize-LineBytes-y*LineBytes)+1;

                     for(x=1;x<bi.biWidth-1;x++){ 

//注意为防止越界,x的范围从1到宽度-2

                            num=(unsigned char)*lpPtr;

                            if (num==0){  //因为腐蚀掉的是黑点,所以只对黑点处理

                                   *lpTempPtr=(unsigned char)0;  //先置成黑点

                                   for(i=0;i<3;i++){

                                          num=(unsigned char)*(lpPtr+i-1);

                                          if(num==255){ 

//自身及上下邻居中若有一个不是黑点,则将该点腐

//蚀成白点

                                                 *lpTempPtr=(unsigned char)255;

                                                 break;

                                          }

                                   }

                            }

//原图中就是白点的,新图中仍是白点

                            else *lpTempPtr=(unsigned char)255;  

                            //指向下一个象素

                            lpPtr++; 

                            lpTempPtr++;

                     }

              }

       }

else{ 

//在垂直方向进行腐蚀运算

              for(y=1;y<bi.biHeight-1;y++){ //注意为防止越界,y的范围从1到高度-2

                     //lpPtr指向原图数据,lpTempPtr指向新图数据

                     lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes);

                     lpTempPtr=(char *)lpTempImgData+(BufSize-LineBytes-y*LineBytes);

                     for(x=0;x<bi.biWidth;x++){

                            num=(unsigned char)*lpPtr;

                            if (num==0){ //因为腐蚀掉的是黑点,所以只对黑点处理

                                   *lpTempPtr=(unsigned char)0; //先置成黑点

                                   for(i=0;i<3;i++){

                                          num=(unsigned char)*(lpPtr+(i-1)*LineBytes);

                                          if(num==255){

//自身及上下邻居中若有一个不是黑点,则将该点腐

//蚀成白点

                                                 *lpTempPtr=(unsigned char)255;

                                                 break;

                                          }

                                   }

                            }

//原图中就是白点的,新图中仍是白点

                            else *lpTempPtr=(unsigned char)255;

                            //指向下一个象素

                            lpPtr++;

                            lpTempPtr++;

                     }

              }

       }

    if(hBitmap!=NULL)

           DeleteObject(hBitmap);

       hDc=GetDC(hWnd);     

       //产生新的位图

       hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData,

(LONG)CBM_INIT,

(LPSTR)lpTempImgData+

sizeof(BITMAPINFOHEADER)+

                                         NumColors*sizeof(RGBQUAD),

(LPBITMAPINFO)lpTempImgData, DIB_RGB_COLORS);

       //起不同的结果文件名

       if(Hori)

              hf=_lcreat("c:\\herosion.bmp",0);

       else

              hf=_lcreat("c:\\verosion.bmp",0);

       _lwrite(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER)); 

       _lwrite(hf,(LPSTR)lpTempImgData,BufSize);

       _lclose(hf);

       //释放内存及资源

ReleaseDC(hWnd,hDc);

       LocalUnlock(hTempImgData);

       LocalFree(hTempImgData);

       GlobalUnlock(hImgData);

       return TRUE;

}

膨胀

膨胀(dilation)可以看做是腐蚀的对偶运算,其定义是:把结构元素B平移a后得到Ba,若Ba击中X,我们记下这个a点。所有满足上述条件的a点组成的集合称做XB膨胀的结果。用公式表示为:D(X)={a | BaX}=X  B,如图6.13所示。图6.13X是被处理的对象,B是结构元素,不难知道,对于任意一个在阴影部分的点aBa击中X,所以XB膨胀的结果就是那个阴影部分。阴影部分包括X的所有范围,就象X膨胀了一圈似的,这就是为什么叫膨胀的原因。

同样,如果B不是对称的,XB膨胀的结果和X Bv膨胀的结果不同。

让我们来看看实际上是怎样进行膨胀运算的。在图6.14中,左边是被处理的图象X(二值图象,我们针对的是黑点),中间是结构元素B。膨胀的方法是,拿B的中心点和X上的点及X周围的点一个一个地对,如果B上有一个点落在X的范围内,则该点就为黑;右边是膨胀后的结果。可以看出,它包括X的所有范围,就象X膨胀了一圈似的。

6.13   膨胀的示意图

6.14   膨胀运算

6.15为图6.11膨胀后的结果图,能够很明显的看出膨胀的效果。

6.15   6.11膨胀后的结果图

下面的这段程序,实现了上述的膨胀运算,针对的都是黑色点。参数中有一个BOOL变量,为真时,表示在水平方向进行膨胀运算,即结构元素B  ;否则在垂直方向上进行膨胀运算,即结构元素B  

膨胀源码

BOOL Dilation(HWND hWnd,BOOL Hori)

{

       DWORD                             OffBits,BufSize;

LPBITMAPINFOHEADER    lpImgData;

       LPSTR                   lpPtr;

       HLOCAL                  hTempImgData;

       LPBITMAPINFOHEADER    lpTempImgData;

       LPSTR                     lpTempPtr;

       HDC                     hDc;

       HFILE                    hf;

       LONG                    x,y;

       unsigned char              num;

       int                        i;

//为了处理的方便,仍采用256级灰度图,不过只调色板中0和255两项

if( NumColors!=256){  

            MessageBox(hWnd,"Must be a mono bitmap with grayscale palette!",

"Error Message",MB_OK|MB_ICONEXCLAMATION);

return FALSE;

}

OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);

//BufSize为缓冲区大小

       BufSize=OffBits+bi.biHeight*LineBytes;

//为新的缓冲区分配内存

       if((hTempImgData=LocalAlloc(LHND,BufSize))==NULL)

    {

           MessageBox(hWnd,"Error alloc memory!","Error Message",

MB_OK|MB_ICONEXCLAMATION);

return FALSE;

    }

     lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData);    

       lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData);

       //拷贝头信息和位图数据     

       memcpy(lpTempImgData,lpImgData,BufSize);

       if(Hori)

       {   

//在水平方向进行膨胀运算

              for(y=0;y<bi.biHeight;y++){

                     //lpPtr指向原图数据,lpTempPtr指向新图数据

                     lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes)+1;

                     lpTempPtr=(char*)lpTempImgData+

(BufSize-LineBytes-y*LineBytes)+1;

                     for(x=1;x<bi.biWidth-1;x++){ 

//注意为防止越界,x的范围从1到宽度-2

                            num=(unsigned char)*lpPtr;

//原图中是黑点的,新图中肯定也是,所以要考虑的是那些原图

//中的白点,看是否有可能膨胀成黑点

                            if (num==255){

                                   *lpTempPtr=(unsigned char)255; //先置成白点

                                   for(i=0;i<3;i++){ 

                                          num=(unsigned char)*(lpPtr+i-1);

//只要左右邻居中有一个是黑点,就膨胀成黑点

                                          if(num==0){

*lpTempPtr=(unsigned char)0;

                                                 break;

                                          }

                                   }

                            }

//原图中就是黑点的,新图中仍是黑点

                            else *lpTempPtr=(unsigned char)0;

                            //指向下一个象素

                            lpPtr++;

                            lpTempPtr++;

                     }

              }

       }

       else{

//在垂直方向进行腐蚀运算

              for(y=1;y<bi.biHeight-1;y++){ //注意为防止越界,y的范围从1到高度-2

              lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes);

                     lpTempPtr=(char *)lpTempImgData+(BufSize-LineBytes-y*LineBytes);

                     for(x=0;x<bi.biWidth;x++){

                            num=(unsigned char)*lpPtr;

                            if (num==255){

                                   *lpTempPtr=(unsigned char)255;

                                   for(i=0;i<3;i++){

                                          num=(unsigned char)*(lpPtr+(i-1)*LineBytes);

//只要上下邻居中有一个是黑点,就膨胀成黑点

                                          if(num==0){

                                                 *lpTempPtr=(unsigned char)0;

                                                 break;

                                          }

                                   }

                            }

                            else *lpTempPtr=(unsigned char)0;

                            lpPtr++;

                            lpTempPtr++;

                     }

              }

       }

    if(hBitmap!=NULL)

           DeleteObject(hBitmap);

       hDc=GetDC(hWnd);     

       //产生新的位图

       hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData,

(LONG)CBM_INIT,

(LPSTR)lpTempImgData+

sizeof(BITMAPINFOHEADER)+

                                         NumColors*sizeof(RGBQUAD),

(LPBITMAPINFO)lpTempImgData,

DIB_RGB_COLORS);

       //起不同的结果文件名

       if(Hori)

              hf=_lcreat("c:\\hdilation.bmp",0);

       else

              hf=_lcreat("c:\\vdilation.bmp",0);

       _lwrite(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER)); 

       _lwrite(hf,(LPSTR)lpTempImgData,BufSize);

       _lclose(hf);

       //释放内存及资源

      ReleaseDC(hWnd,hDc);

       LocalUnlock(hTempImgData);

       LocalFree(hTempImgData);

       GlobalUnlock(hImgData);

       return TRUE;

}

腐蚀运算和膨胀运算互为对偶的,用公式表示为(X  B)c=(Xc  B),即B腐蚀后的补集等于X的补集被B膨胀。这句话可以形象的理解为:河岸的补集为河面,河岸的腐蚀等价于河面的膨胀。你可以自己举个例子来验证一下这个关系。在有些情况下,这个对偶关系是非常有用的。例如:某个图象处理系统用硬件实现了腐蚀运算,那么不必再另搞一套膨胀的硬件,直接利用该对偶就可以实现了。

先腐蚀后膨胀称为开(open),即OPEN(X)=D(E(X))

让我们来看一个开运算的例子(见图6.16)

6.16开运算

在图16上面的两幅图中,左边是被处理的图象X(二值图象,我们针对的是黑点),右边是结构元素B,下面的两幅图中左边是腐蚀后的结果;右边是在此基础上膨胀的结果。可以看到,原图经过开运算后,一些孤立的小点被去掉了。一般来说,开运算能够去除孤立的小点,毛刺和小桥(即连通两块区域的小点),而总的位置和形状不变。这就是开运算的作用。要注意的是,如果B是非对称的,进行开运算时要用B的对称集Bv膨胀,否则,开运算的结果和原图相比要发生平移。图6.17和图6.18能够说明这个问题。

6.17 B膨胀后,结果向左平移了

6.18   Bv膨胀后位置不变

6.17是用B膨胀的,可以看到,OPEN(X)向左平移了。图18是用Bv膨胀的,可以看到,总的位置和形状不变。

6.19为图6.11经过开运算后的结果。

6.19   6.11经过开运算后的结果

开运算的源程序可以很容易的根据上面的腐蚀,膨胀程序得到,这里就不给出了。

先膨胀后腐蚀称为闭(close),即CLOSE(X)=E(D(X))

让我们来看一个闭运算的例子(见图6.20)

6.20   闭运算

在图6.20上面的两幅图中,左边是被处理的图象X(二值图象,我们针对的是黑点),右边是结构元素B,下面的两幅图中左边是膨胀后的结果,右边是在此基础上腐蚀的结果可以看到,原图经过闭运算后,断裂的地方被弥合了。一般来说,闭运算能够填平小湖(即小孔),弥合小裂缝,而总的位置和形状不变。这就是闭运算的作用。同样要注意的是,如果B是非对称的,进行闭运算时要用B的对称集Bv膨胀,否则,闭运算的结果和原图相比要发生平移。

6.21为图6.11经过闭运算后的结果。

6.21   .611经过闭运算后的结果

闭运算的源程序可以很容易的根据上面的膨胀,腐蚀程序得到,这里就不给出了。

你大概已经猜到了,开和闭也是对偶运算,的确如此。用公式表示为(OPEN(X))c=CLOSE((Xc)),或者(CLOSE(X))c=OPEN((Xc))。即开运算的补集等于X的补集的闭运算,或者闭运算的补集等于X的补集的开运算。这句话可以这样来理解:在两个小岛之间有一座小桥,我们把岛和桥看做是处理对象X,则X的补集为大海。如果涨潮时将小桥和岛的外围淹没(相当于用尺寸比桥宽大的结构元素对X进行开运算),那么两个岛的分隔,相当于小桥两边海域的连通(Xc做闭运算)

细化

细化(thinning)算法有很多,我们在这里介绍的是一种简单而且效果很好的算法,用它就能够实现从文本抽取骨架的功能。我们的对象是白纸黑字的文本,但在程序中为了处理的方便,还是采用256级灰度图,不过只用到了调色板中0255两项。

所谓细化,就是从原来的图中去掉一些点,但仍要保持原来的形状。实际上,是保持原图的骨架。所谓骨架,可以理解为图象的中轴,例如一个长方形的骨架是它的长方向上的中轴线;正方形的骨架是它的中心点;圆的骨架是它的圆心,直线的骨架是它自身,孤立点的骨架也是自身。文本的骨架嘛,前言中的例子显示的很明白。那么怎样判断一个点是否能去掉呢?显然,要根据它的八个相邻点的情况来判断,我们给几个例子(如图6.22所示)

6.22   根据某点的八个相邻点的情况来判断该点是否能删除

6.22中,(1)不能删,因为它是个内部点,我们要求的是骨架,如果连内部点也删了,骨架也会被掏空的;(2)不能删,和(1)是同样的道理;(3)可以删,这样的点不是骨架;(4)不能删,因为删掉后,原来相连的部分断开了;(5)可以删,这样的点不是骨架;(6)不能删,因为它是直线的端点,如果这样的点删了,那么最后整个直线也被删了,剩不下什么;(7)不能删,因为孤立点的骨架就是它自身。

总结一下,有如下的判据:(1)内部点不能删除;(2)孤立点不能删除;(3)直线端点不能删除;(4)如果P是边界点,去掉P后,如果连通分量不增加,则P可以删除。

我们可以根据上述的判据,事先做出一张表,从0255共有256个元素,每个元素要么是0,要么是1。我们根据某点(当然是要处理的黑色点了)的八个相邻点的情况查表,若表中的元素是1,则表示该点可删,否则保留。

查表的方法是,设白点为1,黑点为0;左上方点对应一个8位数的第一位(最低位),正上方点对应第二位,右上方点对应的第三位,左邻点对应第四位,右邻点对应第五位,左下方点对应第六位,正下方点对应第七位,右下方点对应的第八位,按这样组成的8位数去查表即可。例如上面的例子中(1)对应表中的第0项,该项应该为0(2)对应37,该项应该为0(3)对应173,该项应该为1(4)对应231,该项应该为0(5)对应237,该项应该为1(6)对应254,该项应该为0(7)对应255,该项应该为0

这张表我已经替大家做好了,可花了我不少时间呢!

static int erasetable[256]={

                                         0,0,1,1,0,0,1,1,          1,1,0,1,1,1,0,1,

                                   1,1,0,0,1,1,1,1,             0,0,0,0,0,0,0,1,

                                          0,0,1,1,0,0,1,1,             1,1,0,1,1,1,0,1,

                                          1,1,0,0,1,1,1,1,             0,0,0,0,0,0,0,1,

                                          1,1,0,0,1,1,0,0,             0,0,0,0,0,0,0,0,

                                          0,0,0,0,0,0,0,0,             0,0,0,0,0,0,0,0,

                                          1,1,0,0,1,1,0,0,             1,1,0,1,1,1,0,1,

                                   0,0,0,0,0,0,0,0,             0,0,0,0,0,0,0,0,

                           0,0,1,1,0,0,1,1,             1,1,0,1,1,1,0,1,

                                          1,1,0,0,1,1,1,1,             0,0,0,0,0,0,0,1,

                                          0,0,1,1,0,0,1,1,             1,1,0,1,1,1,0,1,

                                          1,1,0,0,1,1,1,1,             0,0,0,0,0,0,0,0,

                                          1,1,0,0,1,1,0,0,             0,0,0,0,0,0,0,0,

                                1,1,0,0,1,1,1,1,             0,0,0,0,0,0,0,0,

                                          1,1,0,0,1,1,0,0,             1,1,0,1,1,1,0,0,

                                   1,1,0,0,1,1,1,0,             1,1,0,0,1,0,0,0

                                     };

有了这张表,算法就很简单了,每次对一行一行的将整个图象扫描一遍,对于每个点(不包括边界点),计算它在表中对应的索引,若为0,则保留,否则删除该点。如果这次扫描没有一个点被删除,则循环结束,剩下的点就是骨架点,如果有点被删除,则进行新的一轮扫描,如此反复,直到没有点被删除为止。

实际上,该算法有一些缺陷。举个简单的例子,有一个黑色矩形,如图6.23所示。

6.23经过细化后,我们预期的结果是一条水平直线,且位于该黑色矩形的中心。实际的结果确实是一条水平直线,但不是位于黑色矩形的中心,而是最下面的一条边。

为什么会这样,我们来分析一下:在从上到下,从左到右的扫描过程中,我们遇到的第一个黑点就是黑色矩形的左上角点,经查表,该点可以删。下一个点是它右边的点,经查表,该点也可以删,如此下去,整个一行被删了。每一行都是同样的情况,所以都被删除了。到了最后一行时,黑色矩形已经变成了一条直线,最左边的黑点不能删,因为它是直线的端点,它右边的点也不能删,因为如果删除,直线就断了,如此下去,直到最右边的点,也不能删,因为它是直线的右端点。所以最下面的一条边保住了,但这并不是我们希望的结果。

解决的办法是,在每一行水平扫描的过程中,先判断每一点的左右邻居,如果都是黑点,则该点不做处理。另外,如果某个黑点被删除了,那么跳过它的右邻居,处理下一个点。这样就避免了上述的问题。

6.23  黑色矩形

6.24  6.23细化后的结果

解决了上面的问题,我们来看看处理后的结果,如图6.24所示。这次变成一小段竖线了,还是不对,是不是很沮丧?别着急,让我们再来分析一下:在上面的算法中,我们遇到的第一个能删除的点就是黑色矩形的左上角点;第二个是第一行的最右边的点,即黑色矩形的右上角点;第三个是第二行的最左边的点;第四个是第二行的最右边的点;……;整个图象处理这样一次后,宽度减少2。每次都是如此,直到剩最中间一列,就不能再删了。为什么会这样呢?原因是这样的处理过程只实现了水平细化,如果在每一次水平细化后,再进行一次垂直方向的细化(只要把上述过程的行列换一下),就可以了。

这样一来,每处理一次,删除点的顺序变成:(先是水平方向扫描)第一行最左边的点;第一行最右边的点;第二行最左边的点;第二行最右边的点;……最后一行最左边的点;最后一行最右边的点;(然后是垂直方向扫描)第二列最上边的点(因为第一列最上边的点已被删除);第二列最下边的点;第三列最上边的点;第三列最下边的点;……倒数第二列最上边的点(因为倒数第一列最上边的点已被删除);倒数第二列最下边的点。我们发现,刚好剥掉了一圈,这也正是细化要做的事。实际的结果也验证了我们的想法。

以下是源程序,黑体字部分是值得注意的地方。

细化源码

BOOL Thinning(HWND hWnd)

{

       DWORD                             OffBits,BufSize;

     LPBITMAPINFOHEADER    lpImgData;

       LPSTR                            lpPtr;

       HLOCAL                  hTempImgData;

       LPBITMAPINFOHEADER    lpTempImgData;

       LPSTR                   lpTempPtr;

       HDC                      hDc;

       HFILE                    hf;

       LONG                    x,y;

       int                                        num;

       BOOL                     Finished;

       int                        nw,n,ne,w,e,sw,s,se;

//为了处理的方便,仍采用256级灰度图,不过只用调色板中0和255两项

       if( NumColors!=256){

MessageBox(hWnd,"Must be a mono bitmap with grayscale palette!",

"Error Message",MB_OK|MB_ICONEXCLAMATION);

return FALSE;

}

OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);

//BufSize为缓冲区大小

       BufSize=OffBits+bi.biHeight*LineBytes;

//为新的缓冲区分配内存

       if((hTempImgData=LocalAlloc(LHND,BufSize))==NULL)

{

            MessageBox(hWnd,"Error alloc memory!","Error Message",

MB_OK|MB_ICONEXCLAMATION);

return FALSE;

}

     lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData);    

       lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData);

       //拷贝头信息和位图数据     

       memcpy(lpTempImgData,lpImgData,BufSize);

       //结束标志置成假

       Finished=FALSE;

while(!Finished){ //还没有结束

              //结束标志置成假

            Finished=TRUE;

       //先进行水平方向的细化

              for (y=1;y<bi.biHeight-1;y++){ //注意为防止越界,y的范围从1到高度-2

                     //lpPtr指向原图数据,lpTempPtr指向新图数据

                     lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes);

                     lpTempPtr=(char *)lpTempImgData+(BufSize-LineBytes-y*LineBytes);

                     x=1; //注意为防止越界,x的范围从1到宽度-2

                     while(x<bi.biWidth-1){

                            if(*(lpPtr+x)==0){ //是黑点才做处理

                                   w=(unsigned char)*(lpPtr+x-1);  //左邻点

                                   e=(unsigned char)*(lpPtr+x+1);  //右邻点

                                   if( (w==255)|| (e==255)){ 

//如果左右两个邻居中至少有一个是白点才处理

                                          nw=(unsigned char)*(lpPtr+x+LineBytes-1); //左上邻点

                                          n=(unsigned char)*(lpPtr+x+LineBytes); //上邻点

                                          ne=(unsigned char)*(lpPtr+x+LineBytes+1); //右上邻点

                                          sw=(unsigned char)*(lpPtr+x-LineBytes-1); //左下邻点

                                          s=(unsigned char)*(lpPtr+x-LineBytes); //下邻点

                                          se=(unsigned char)*(lpPtr+x-LineBytes+1); //右下邻点

                                          //计算索引

                            num=nw/255+n/255*2+ne/255*4+w/255*8+e/255*16+

sw/255*32+s/255*64+se/255*128;

                                          if(erasetable[num]==1){ //经查表,可以删除

//在原图缓冲区中将该黑点删除

                                                 *(lpPtr+x)=(BYTE)255; 

//结果图中该黑点也删除

                                                 *(lpTempPtr+x)=(BYTE)255; 

                                                 Finished=FALSE; //有改动,结束标志置成假

                                                 x++; //水平方向跳过一个象素

                                          }

                                   }

                            }

                            x++; //扫描下一个象素

                     }

              }

       //再进行垂直方向的细化

              for (x=1;x<bi.biWidth-1;x++){ //注意为防止越界,x的范围从1到宽度-2

                     y=1; //注意为防止越界,y的范围从1到高度-2

                     while(y<bi.biHeight-1){

                            lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes);

                            lpTempPtr=(char*)lpTempImgData+

(BufSize-LineBytes-y*LineBytes);

                            if(*(lpPtr+x)==0){ //是黑点才做处理

                                   n=(unsigned char)*(lpPtr+x+LineBytes);

                                   s=(unsigned char)*(lpPtr+x-LineBytes);

                                   if( (n==255)|| (s==255)){

//如果上下两个邻居中至少有一个是白点才处理

                                          nw=(unsigned char)*(lpPtr+x+LineBytes-1);

                                          ne=(unsigned char)*(lpPtr+x+LineBytes+1);

                                          w=(unsigned char)*(lpPtr+x-1);

                                          e=(unsigned char)*(lpPtr+x+1);

                                          sw=(unsigned char)*(lpPtr+x-LineBytes-1);

                                          se=(unsigned char)*(lpPtr+x-LineBytes+1);

                                          //计算索引

num=nw/255+n/255*2+ne/255*4+w/255*8+e/255*16+

sw/255*32+s/255*64+se/255*128;

                                          if(erasetable[num]==1){ //经查表,可以删除

//在原图缓冲区中将该黑点删除

                                                 *(lpPtr+x)=(BYTE)255; 

//结果图中该黑点也删除

                                                 *(lpTempPtr+x)=(BYTE)255; 

                                                 Finished=FALSE; //有改动,结束标志置成假

                                                 y++;//垂直方向跳过一个象素

                                          }

                                   }

                            }

                            y++; //扫描下一个象素

                     }

              } 

}

     if(hBitmap!=NULL)

           DeleteObject(hBitmap);

       hDc=GetDC(hWnd);     

       //产生新的位图

       hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData,

(LONG)CBM_INIT,

(LPSTR)lpTempImgData+

sizeof(BITMAPINFOHEADER)+

NumColors*sizeof(RGBQUAD),

(LPBITMAPINFO)lpTempImgData,

DIB_RGB_COLORS);

hf=_lcreat("c:\\thinning.bmp",0);

       _lwrite(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER)); 

       _lwrite(hf,(LPSTR)lpTempImgData,BufSize);

       _lclose(hf);

       //释放内存及资源

      ReleaseDC(hWnd,hDc);

       LocalUnlock(hTempImgData);

       LocalFree(hTempImgData);

       GlobalUnlock(hImgData);

       return TRUE;

}

另外补充说明一下,助于理解

腐蚀:删除对象边界的某些像素

膨胀:给图像中的对象边界添加像素

算法:

膨胀算法:用3X3的结构元素,扫描二值图像的每一个像素,用结构元素与其覆盖的二值图像做“与”运算,如果都为0,结构图像的该像素为0,否则为1.结果:使二值图像扩大一圈。

腐蚀算法:用3X3的结构元素,扫描二值图像的每一个像素,用结构元素与其覆盖的二值图像做“与”运算,如果都为1,结构图像的该像素为1,否则为0.结果:使二值图像减小一圈。

 




2014-07-19 00:40:57 helloUSB2010 阅读数 25622
  • Java经典算法讲解

    在面试中,算法题目是必须的,通过算法能够看出一个程序员的编程思维,考察对复杂问题的设计与分析能力,对问题的严谨性都能够体现出来。一个算法的好坏,直接影响一个方法调用的性能,进而影响软件的整体性能。算法是学习所有编程语言的基础,在Java的学习过程中首先也会选择以算法起步,本次课程重点讲解Java开发中常用的基本算法。

    29968 人正在学习 去看看 张中强

一、RGB

RGB色彩模式使用RGB模型为图像中每一个像素的RGB分量分配一个0~255范围内的强度值。RGB图像只使用三种颜色,R(red)、G(green)、B(blue),就可以使它们按照不同的比例混合,在屏幕上呈现16777216(256 * 256 * 256)种颜色。

在电脑中,RGB的所谓“多少”就是指亮度,并使用整数来表示。通常情况下,RGB各有256级亮度,用数字表示为从0、1、2...直到255。


二、ARGB

一种色彩模式,也就是RGB色彩模式附加上Alpha(透明度)通道,常见于32位位图存储结构
ARGB---Alpha,Red,Green,Blue.


三、灰度化

 在RGB模型中,如果R=G=B时,则彩色表示一种灰度颜色,其中R=G=B的值叫灰度值,因此,灰度图像每个像素只需一个字节存放灰度值(又称强度值、亮度值),灰度范围为0-255。一般有以下四种方法对彩色图像进行灰度化,具体方法参考:http://blog.csdn.net/evsqiezi/article/details/7905436   


四、二值化

一幅图像包括目标物体、背景还有噪声,要想从多值的数字图像中直接提取出目标物体,最常用的方法就是设定一个全局的阈值T,用T将图像的数据分成两部分:大于T的像素群和小于T的像素群。将大于T的像素群的像素值设定为白色(或者黑色),小于T的像素群的像素值设定为黑色(或者白色)。
比如:计算每一个像素的(R+G+B)/3,如果>127,则设置该像素为白色,即R=G=B=255;否则设置为黑色,即R=G=B=0。
C#实现代码如下:
public Bitmap binarization()
        {
            Bitmap bitImage = new Bitmap(pictureBox1.Image);//二值化pictureBox1中的图片
            Color c;
            int height = pictureBox1.Image.Height;
            int width = pictureBox1.Image.Width;
            for (int i = 0; i < height; i++)
            {
                for (int j = 0; j < width; j++)
                {
                    c = bitImage.GetPixel(j,i);
                    int r = c.R;
                    int g = c.G;
                    int b = c.B;
                    if ((r + g + b) / 3 >= 127)
                    {                       
                        bitImage.SetPixel(j, i, Color.FromArgb(255, 255, 255));                  
                    }
                    else
                    {     
                        bitImage.SetPixel(j, i, Color.FromArgb(0,0,0));
                    }
                }
            }
            return bitImage;
        }
运行结果如图:
左边为处理前,右边为二值化后效果。


五、膨胀算法

膨胀是将与物体接触的所有背景点合并到该物体中,使边界向外部扩张的过程。

可以用来填补物体中的空洞。

 

3x3的结构元素,扫描图像的每一个像素

用结构元素与其覆盖的二值图像做操作

如果都为0,结果图像的该像素为0。否则为1

结果:使二值图像扩大一圈


膨胀(dilation)可以看做是腐蚀的对偶运算,其定义是:把结构元素B平移a后得到Ba,若Ba击中X,我们记下这个a点。所有满足上述条件的a点组成的集合称做XB膨胀的结果。用公式表示为:D(X)={a | BaX}=X腐蚀,膨胀,细化算法B,如下图所示。图X是被处理的对象,B是结构元素,不难知道,对于任意一个在阴影部分的点aBa击中X,所以XB膨胀的结果就是那个阴影部分。阴影部分包括X的所有范围,就象X膨胀了一圈似的,这就是为什么叫膨胀的原因。



在下图中,左边是被处理的图象X(二值图象,我们针对的是黑点),中间是结构元素B。膨胀的方法是,拿B的中心点和X上的点及X周围的点一个一个地对,如果B上有一个点落在X的范围内,则该点就为黑;右边是膨胀后的结果。可以看出,它包括X的所有范围,就象X膨胀了一圈似的。




我设计了一个简单的膨胀算法,依次遍历整个图片的像素,分析每一个像素的周围八个像素,只要该像素周围存在黑色的像素,就设置该像素颜色为黑色。下面是使用膨胀算法处理经过二值化后的图像的C#实现代码:

public bool[] getRoundPixel(Bitmap bitmap, int x, int y)//返回(x,y)周围像素的情况,为黑色,则设置为true
        {
            bool[] pixels=new bool[8];
            Color c;
            int num = 0;
            for (int i = -1; i < 2; i++)
            {
                for (int j = -1; j < 2; j++)
                {
                    c = bitmap.GetPixel(x+i,y+j);
                    if (i != 0 || j != 0)
                    {
                        if (255 == c.G)//因为经过了二值化,所以只要检查RGB中一个属性的值
                        {
                            pixels[num] = false;//为白色,设置为false
                            num++;
                        }
                        else if(0==c.G)
                        {
                            pixels[num] = true;//为黑色,设置为true
                            num++;
                        }
                    }
                }
            }
            return pixels;
        }


public Bitmap expend()
        {
            Bitmap bitImage = new Bitmap(pictureBox2.Image);//处理pictureBox2中的图片
            Bitmap bitImage1 = new Bitmap(pictureBox2.Image);
            int height = pictureBox1.Image.Height;
            int width = pictureBox1.Image.Width;
            bool[] pixels;
            for (int i = 1; i < width-1; i++)
            {
                for (int j = 1; j < height-1; j++)
                {
                    
                    if (bitImage.GetPixel(i, j).R != 0)
                    {
                        pixels = getRoundPixel(bitImage, i, j);
                        for (int k = 0; k < pixels.Length; k++)
                        {
                            if (pixels[k] == true)
                            {
                                //set this piexl's color to black
                                bitImage1.SetPixel(i, j, Color.FromArgb(0,0,0));
                                break;
                            }
                        }
                    }
                }
            }
                return bitImage1;

        }


运行结果如图:


六、腐蚀算法


腐蚀是一种消除边界点,使边界向内部收缩的过程。可以用来消除小且无意义的物体。



3x3的结构元素,扫描图像的每一个像素


用结构元素与其覆盖的二值图像做操作


如果都为1,结果图像的该像素为1。否则为0


结果:使二值图像减小一圈

把结构元素B平移a后得到Ba,若Ba包含于X,我们记下这个a点,所有满足上述条件的a点组成的集合称做XB腐蚀(Erosion)的结果。用公式表示为:E(X)={a| Ba 腐蚀,膨胀,细化算法X}=X 腐蚀,膨胀,细化算法B。



下图中X是被处理的对象,B是结构元素。不难知道,对于任意一个在阴影部分的点aBa包含于X,所以XB腐蚀的结果就是那个阴影部分。阴影部分在X的范围之内,且比X小,就象X被剥掉了一层似的,这就是为什么叫腐蚀的原因。


我设计了一个简单的腐蚀算法,一次遍历图像中每一个像素,检查它四周的八个像素,如果有白色的像素,则设置改点为白色。用二值化处理后的图片进行腐蚀算法C#代码如下:


public Bitmap corrode()
        {
            Bitmap bitImage = new Bitmap(pictureBox2.Image);
            Bitmap bitImage1 = new Bitmap(pictureBox2.Image);
            Color c;
            int height = pictureBox1.Image.Height;
            int width = pictureBox1.Image.Width;
            bool[] pixels;
            for (int i = 1; i < width - 1; i++)
            {
                for (int j = 1; j < height - 1; j++)
                {
                    c = bitImage.GetPixel(i, j);
                    if (bitImage.GetPixel(i, j).R == 0)
                    {
                        pixels = getRoundPixel(bitImage, i, j);
                        for (int k = 0; k < pixels.Length; k++)
                        {
                            if (pixels[k] == false)
                            {
                                //set this piexl's color to black
                                bitImage1.SetPixel(i, j, Color.FromArgb(255, 255, 255));
                                break;
                            }
                        }
                    }
                }
            }
            return bitImage1;
        }

处理后图片变成:



七、开运算

先腐蚀后膨胀的过程称为开运算。用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积。


八、闭运算

先膨胀后腐蚀的过程称为闭运算。用来填充物体内细小空洞、连接邻近物体、平滑其边界的同时并不明显改变其面积。



2017-03-05 20:26:34 Mac_lzq 阅读数 16356
  • Java经典算法讲解

    在面试中,算法题目是必须的,通过算法能够看出一个程序员的编程思维,考察对复杂问题的设计与分析能力,对问题的严谨性都能够体现出来。一个算法的好坏,直接影响一个方法调用的性能,进而影响软件的整体性能。算法是学习所有编程语言的基础,在Java的学习过程中首先也会选择以算法起步,本次课程重点讲解Java开发中常用的基本算法。

    29968 人正在学习 去看看 张中强
图像处理基本算法操作从处理对象的多少可以有如下划分:
一)点运算:处理点单元信息的运算
二)群运算:处理群单元 (若干个相邻点的集合)的运算
                                              表1 图像处理操作按处理对象数量分类表格
操作类型
具体常用操作
点运算
二值化操作、直方图处理、亮度映射、加法运算、
翻转运 算 、尺度运算 、对数运算 、指数运算等
群运算
模板卷积运算、滤波运算(均值滤波、最大值滤波 、最小值滤波)、
各项异性扩散、形态学操作(膨胀和腐蚀)、力场变换等
      下图是一副普通的吉普车图像和我们生活中见到的并没有什么两样,但是在计算机看来则是另外一副“模样”了。图像中黄色部分则是几部车图像倒车镜的局部图像在计算机中的形态。                                                
图1 计算机图像的真实表现形态
      以上图为例说明几种重要的点运算和群运算。
      1.二值化操作
       图像二值化是图像处理中十分常见且重要的操作,它是将灰度图像转换为二值图像或灰度图像的过程。二值化操作有很多种,例如一般二值化、翻转二值化、截断二值化、置零二值化、置零翻转二值化。
其中src(x,y)表示的是原始图像中第x行第y列像素值。 
       如果去图像中左上角3X3的邻域,thresh取200,maxval取255,阈值方法选择一般二值化(THRESH_BINARY),那么操作过后的结果如下:
                                                                                                                                                    
                                                                                                                                     图2 一般二值化图示
       在图2中,一般二值化下底表示为0,上顶表示为maxval,其中蓝色横线则表示阈值(thresh)。超过该阈值则为maxval,否则为0。
                                                                                                                     
                                                                                                            (a) 操作前                                                      (b) 操作后
     2.直方图处理 
       直方图是图像处理中另一重要处理过程,它反映图像中不同像素值的统计信息。从这句话我们可以了解到直方图信息仅反映灰度统计信息,与像素具体位置没有关系。这一重要特性在许多识别类算法中直方图处理起到关键作用。假设现有3X3的大小的图像。像素值分别为6,3,3,8,6,8,3,3,3,那么它的统计直方图则为
       假设图1中反光镜的直方图为下图所示。
               
                    图3 图像直方图
     假设我们对直方图中中某一灰度信息进行置零操作,那么反映在直方图图上则是该灰度的柱状高度为0。
     值得一说的是二值化处理和某些直方图处理属于不可逆运算,而亮度映射、加法运算、翻转运算 、尺度运算 、对数运算 、指数运算等皆属于可逆运算。
    
     3.模板卷积运算
       模板运算是图像处理中使用频率相当高的一种运算,很多操作可以归结为模板运算,例如平滑处理,滤波处理以及边缘特征提取处理等。这里需要说明的是模板运算所使用的模板通常说来就是NXN的矩阵(N一般为奇数如3,5,7,...),如果这个矩阵是对称矩阵那么这个模板也称为卷积模板,如果不对称则是一般的运算模板。我们通常使用的模板一般都是卷积模板。如边缘提取中的Sobel算子模板。
           
图 4-a Mx算子模板                         图4-bMy算子模板
       模板运算一般操作过程分为以下三个步骤:
       1)定"锚点 ":就是处理之后的结果值的保存位置,该位置称为"锚点 ",有时候也不在中心。            
       2)对位相乘:模板和原图像的待处理区域,进行对位相乘运算           
       3)求和:将步骤2中模板区域内运算结果进行求和,将求和的结果置于"锚点 "

     4.形态学处理
     形态学处理是二值图像处理中的经典处理手段,主要有膨胀处理和腐蚀处理。也包含一些其他操作如 二值开闭运算、骨架抽取、极限腐蚀、击中击不中变换、形态学梯度、Top-hat变换、颗粒分析、流域变换等。
    膨胀和腐蚀操作主要的功能有:1)消除杂波,噪声信息  2)填充图像内部的"孔洞"  3)平滑边缘毛刺
    膨胀和腐蚀具有类似的数学模型,这里就一起介绍了,简单来说膨胀就是取最大值,腐蚀操作是取最小值。
    膨胀操作的数学表达式为:
                                                                      
    腐蚀操作的数学表达式为:

    膨胀操作和腐蚀操作一般的处理过程如下:
    1)按照选定的处理核(NxN,一般N为奇数)与源图像的区域进行逐个“锚点”配对
    2)将配对的处理邻域信息进行相应操作,膨胀操作就取最大值,腐蚀操作就取最小值
    3)求的结果作为源图像中配对区域的数值。
                         
                                                          图a 原图                                                                图b 膨胀处理图                                                           图c 腐蚀处理
 

2017-08-08 11:09:41 CSDN_he_01 阅读数 1900
  • Java经典算法讲解

    在面试中,算法题目是必须的,通过算法能够看出一个程序员的编程思维,考察对复杂问题的设计与分析能力,对问题的严谨性都能够体现出来。一个算法的好坏,直接影响一个方法调用的性能,进而影响软件的整体性能。算法是学习所有编程语言的基础,在Java的学习过程中首先也会选择以算法起步,本次课程重点讲解Java开发中常用的基本算法。

    29968 人正在学习 去看看 张中强

2017年全国大学生电子设计竞赛(预测题)-腐蚀算法

腐蚀
腐蚀是图像处理中形态学图像处理部分的内容,具体可参考数字图像处理(第三版)[冈萨雷斯] 第九章 形态学图像处理处理

腐蚀算法的定义--(摘自“数字图像处理(第三版)[冈萨雷斯] ”)

实例--(摘自“数字图像处理(第三版)[冈萨雷斯] ”)



源码--(源自网络)
static int SearchCenter(unsigned int* x, unsigned int* y, const TARGET_CONDITION* condition, SEARCH_AREA* area )
{
	unsigned int i, j, k;
	unsigned int FailCount=0;
	unsigned int SpaceX, SpaceY;
	COLOR_RGB rgb;
	COLOR_HLS hls;
	
	SpaceX = condition->WIDTH_MIN / 3;  //X间隔
	SpaceY = condition->HEIGHT_MIN / 3; //Y间隔,将区域矩形化,便于处理
	
	for(i=area->Y_Start; i<area->Y_End; i+=SpaceY)//Y轴扫描
	{
		for(j=area->X_Start; j<area->X_End; j+=SpaceX)//X轴扫描
		{
			FailCount = 0;//累加器,用于对累加匹配的颜色成功个数
			for(k=0; k<SpaceX+SpaceY; k++)
			{//这段代码先进行横向的X轴,Y轴的中点进行横向扫描,然后又以X轴的中点,Y轴移动进行竖向扫描
				if(k<SpaceX)//每分一块进行横向扫描
					ReadColor( j+k, i+SpaceY/2, &rgb );
				else//每分一块进行竖向扫描
					ReadColor( j+SpaceX/2, i+k-SpaceX, &rgb );
				RGB2HSL( &rgb, &hls );
				if(!ColorMatch( &hls, condition ))//颜色范围匹配
					FailCount++;
				
				if(FailCount>( (SpaceX+SpaceY) >> ALLOW_FAIL_PER ))//提高容错率
					break;
				
			}
			
			if(k == SpaceX+SpaceY)
			{//腐蚀中心的表达式
				*x = j + SpaceX / 2;
				*y = i + SpaceY / 2;
				return 1;
			}
			
		}
			
	}
	
	return 0;
		
}

static int Corrode(unsigned int oldX, unsigned int oldY, const TARGET_CONDITION* condition, RESULT* result )
{
	unsigned int Xmin, Xmax, Ymin, Ymax;
	unsigned int i;
	unsigned int FailCount=0;
	COLOR_RGB rgb;
	COLOR_HLS hls;
	
	for(i=oldX; i>IMG_X; i--)
	{
		ReadColor(i, oldY, &rgb);
		RGB2HSL(&rgb, &hls);
		if(!ColorMatch(&hls, condition))
			FailCount++;
		if(FailCount>(((condition->WIDTH_MIN+condition->WIDTH_MAX)>>2)>>ALLOW_FAIL_PER))
			break;
	}
	Xmin=i;
	
	FailCount=0;
	for(i=oldX; i<IMG_X+IMG_W; i++)
	{
		ReadColor(i, oldY, &rgb);
		RGB2HSL(&rgb, &hls);
		if(!ColorMatch(&hls, condition))
			FailCount++;
		if(FailCount>(((condition->WIDTH_MIN+condition->WIDTH_MAX)>>2)>>ALLOW_FAIL_PER))
			break;
	}
	Xmax=i;
	
	FailCount=0;
	for(i=oldY; i>IMG_Y; i--)
	{
		ReadColor(oldX, i, &rgb);
		RGB2HSL(&rgb, &hls);
		if(!ColorMatch(&hls, condition))
			FailCount++;
		if(FailCount>(((condition->HEIGHT_MIN+condition->HEIGHT_MAX)>>2)>>ALLOW_FAIL_PER))
			break;
	}
	Ymin=i;
	
	FailCount=0;
	for(i=oldY; i<IMG_Y+IMG_H; i++)
	{
		ReadColor(oldX, i, &rgb);
		RGB2HSL(&rgb, &hls);
		if(!ColorMatch(&hls, condition))
			FailCount++;
		if(FailCount>(((condition->HEIGHT_MIN+condition->HEIGHT_MAX)>>2)>>ALLOW_FAIL_PER))
			break;
	}
	Ymax=i;
	
	FailCount=0;
	
	result->x = (Xmin + Xmax) / 2;
	result->y = (Ymin + Ymax) / 2;
	result->w = (Xmax - Xmin);
	result->h = (Ymax - Ymin);
	
	if( (result->w > condition->WIDTH_MIN) && (result->w < condition->WIDTH_MAX) &&
			(result->h > condition->HEIGHT_MIN) && (result->h < condition->HEIGHT_MAX)  )
		return 1;
	else
		return 0;
}


int Trace(const TARGET_CONDITION* condition, RESULT* result_final)
{
	unsigned int i;
	static unsigned int x0, y0, Flag = 0;
	static SEARCH_AREA area = {IMG_X, IMG_X+IMG_W, IMG_Y, IMG_Y+IMG_H};
	RESULT result;
	
	if(Flag == 0)
	{
		if(SearchCenter(&x0, &y0, condition, &area))
		{
			Flag = 1;
		}
		else
		{
			area.X_Start = IMG_X;
			area.X_End   = IMG_X+IMG_W;
			area.Y_Start = IMG_Y;
			area.Y_End   = IMG_Y+IMG_H;
			
			if(SearchCenter(&x0, &y0, condition, &area))
			{
				Flag = 0;
				return 0;
			}
		}
	}
	result.x = x0;
	result.y = y0;
	
	for(i=0; i<ITERATER_NUM; i++)
	{
		Corrode(result.x, result.y, condition, &result);
		
	}
	
	if( Corrode(result.x, result.y, condition, &result) )
	{
		x0 = result.x;
		y0 = result.y;
		result_final->x = result.x;
		result_final->y = result.y;
		result_final->w = result.w;
		result_final->h = result.h;
		Flag = 1;
		
		area.X_Start = result.x - ((result.w)>>1);
		area.X_End   = result.x + ((result.w)>>1);
		area.Y_Start = result.y - ((result.h)>>1);
		area.Y_End   = result.y + ((result.h)>>1);
		
		return 1;
	}
	else
	{
		Flag = 0;
		return 0;
	}
	
}




2015-10-24 13:28:34 Dopamy_BusyMonkey 阅读数 2109
  • Java经典算法讲解

    在面试中,算法题目是必须的,通过算法能够看出一个程序员的编程思维,考察对复杂问题的设计与分析能力,对问题的严谨性都能够体现出来。一个算法的好坏,直接影响一个方法调用的性能,进而影响软件的整体性能。算法是学习所有编程语言的基础,在Java的学习过程中首先也会选择以算法起步,本次课程重点讲解Java开发中常用的基本算法。

    29968 人正在学习 去看看 张中强

转自:http://www.cnblogs.com/slysky/archive/2011/10/16/2214015.html

原理:在特殊领域运算形式——结构元素(Sturcture Element),在每个像素位置上与二值图像对应的区域进行特定的逻辑运算。运算结构是输出图像的相应像素。运算效果取决于结构元素大小内容以及逻辑运算性质。

结构元素:膨胀和腐蚀操作的最基本组成部分,用于测试输出图像,通常要比待处理的图像小还很多。二维平面结构元素由一个数值为0或1的矩阵组成。结构元素的原点指定了图像中需要处理的像素范围,结构元素中数值为1的点决定结构元素的邻域像素在进行膨胀或腐蚀操作时是否需要参与计算。

先来定义一些基本符号和关系。

1.         元素

设有一幅图象X,若点aX的区域以内,则称aX的元素,记作aX,如图6.1所示。

2.         B包含于X

设有两幅图象BX。对于B中所有的元素ai,都有aiX,则称B包含于(included in)X,记作B  X,如图6.2所示。

3.         B击中X

设有两幅图象BX。若存在这样一个点,它即是B的元素,又是X的元素,则称B击中(hit)X,记作BX,如图6.3所示。

4.         B不击中X

设有两幅图象BX。若不存在任何一个点,它即是B的元素,又是X的元素,即BX的交集是空,则称B不击中(miss)X,记作BX=Ф;其中∩是集合运算相交的符号,Ф表示空集。如图6.4所示。

6.1     元素

6.2     包含

6.3     击中

6.4     不击中

5.         补集

设有一幅图象X,所有X区域以外的点构成的集合称为X的补集,记作Xc,如图6.5所示。显然,如果BX=Ф,则BX的补集内,即B  Xc

6.5     补集的示意图

6.         结构元素

设有两幅图象BX。若X是被处理的对象,而B是用来处理X的,则称B为结构元素(structure element),又被形象地称做刷子。结构元素通常都是一些比较小的图象。

7.         对称集

设有一幅图象B,将B中所有元素的坐标取反,即令(xy)变成(-x-y),所有这些点构成的新的集合称为B的对称集,记作Bv,如图6.6所示。

8.         平移

设有一幅图象B,有一个点a(x0,y0),将B平移a后的结果是,把B中所有元素的横坐标加x0,纵坐标加y0,即令(xy)变成(x+x0y+y0),所有这些点构成的新的集合称为B的平移,记作Ba,如图6.7所示。

6.6     对称集的示意图

6.7     平移的示意图

好了,介绍了这么多基本符号和关系,现在让我们应用这些符号和关系,看一下形态学的基本运算。


把结构元素B平移a后得到Ba,若Ba包含于X,我们记下这个a点,所有满足上述条件的a点组成的集合称做XB腐蚀(Erosion)的结果。用公式表示为:E(X)={a| Ba  X}=X B,如图6.8所示。

6.8     腐蚀的示意图

6.8X是被处理的对象,B是结构元素。不难知道,对于任意一个在阴影部分的点aBa 包含于X,所以XB腐蚀的结果就是那个阴影部分。阴影部分在X的范围之内,且比X小,就象X被剥掉了一层似的,这就是为什么叫腐蚀的原因。

值得注意的是,上面的B是对称的,即B的对称集Bv=B,所以XB腐蚀的结果和X Bv腐蚀的结果是一样的。如果B不是对称的,让我们看看图6.9,就会发现XB腐蚀的结果和X Bv腐蚀的结果不同。

6.9     结构元素非对称时,腐蚀的结果不同

6.8和图6.9都是示意图,让我们来看看实际上是怎样进行腐蚀运算的。

在图6.10中,左边是被处理的图象X(二值图象,我们针对的是黑点),中间是结构元素B,那个标有origin的点是中心点,即当前处理元素的位置,我们在介绍模板操作时也有过类似的概念。腐蚀的方法是,拿B的中心点和X上的点一个一个地对比,如果B上的所有点都在X的范围内,则该点保留,否则将该点去掉;右边是腐蚀后的结果。可以看出,它仍在原来X的范围内,且比X包含的点要少,就象X被腐蚀掉了一层。

6.10   腐蚀运算

6.11为原图,图6.12为腐蚀后的结果图,能够很明显地看出腐蚀的效果。

6.11    原图

6.12   腐蚀后的结果图

下面的这段程序,实现了上述的腐蚀运算,针对的都是黑色点。参数中有一个BOOL变量,为真时,表示在水平方向进行腐蚀运算,即结构元素B  ;否则在垂直方向上进行腐蚀运算,即结构元素B  


膨胀

膨胀(dilation)可以看做是腐蚀的对偶运算,其定义是:把结构元素B平移a后得到Ba,若Ba击中X,我们记下这个a点。所有满足上述条件的a点组成的集合称做XB膨胀的结果。用公式表示为:D(X)={a | BaX}=X  B,如图6.13所示。图6.13X是被处理的对象,B是结构元素,不难知道,对于任意一个在阴影部分的点aBa击中X,所以XB膨胀的结果就是那个阴影部分。阴影部分包括X的所有范围,就象X膨胀了一圈似的,这就是为什么叫膨胀的原因。

同样,如果B不是对称的,XB膨胀的结果和X Bv膨胀的结果不同。

让我们来看看实际上是怎样进行膨胀运算的。在图6.14中,左边是被处理的图象X(二值图象,我们针对的是黑点),中间是结构元素B。膨胀的方法是,拿B的中心点和X上的点及X周围的点一个一个地对,如果B上有一个点落在X的范围内,则该点就为黑;右边是膨胀后的结果。可以看出,它包括X的所有范围,就象X膨胀了一圈似的。

6.13   膨胀的示意图

6.14   膨胀运算

6.15为图6.11膨胀后的结果图,能够很明显的看出膨胀的效果。

6.15   6.11膨胀后的结果图

下面的这段程序,实现了上述的膨胀运算,针对的都是黑色点。参数中有一个BOOL变量,为真时,表示在水平方向进行膨胀运算,即结构元素B  ;否则在垂直方向上进行膨胀运算,即结构元素B  


腐蚀运算和膨胀运算互为对偶的,用公式表示为(X  B)c=(Xc  B),即B腐蚀后的补集等于X的补集被B膨胀。这句话可以形象的理解为:河岸的补集为河面,河岸的腐蚀等价于河面的膨胀。你可以自己举个例子来验证一下这个关系。在有些情况下,这个对偶关系是非常有用的。例如:某个图象处理系统用硬件实现了腐蚀运算,那么不必再另搞一套膨胀的硬件,直接利用该对偶就可以实现了。


先腐蚀后膨胀称为开(open),即OPEN(X)=D(E(X))

让我们来看一个开运算的例子(见图6.16)

6.16开运算

在图16上面的两幅图中,左边是被处理的图象X(二值图象,我们针对的是黑点),右边是结构元素B,下面的两幅图中左边是腐蚀后的结果;右边是在此基础上膨胀的结果。可以看到,原图经过开运算后,一些孤立的小点被去掉了。一般来说,开运算能够去除孤立的小点,毛刺和小桥(即连通两块区域的小点),而总的位置和形状不变。这就是开运算的作用。要注意的是,如果B是非对称的,进行开运算时要用B的对称集Bv膨胀,否则,开运算的结果和原图相比要发生平移。图6.17和图6.18能够说明这个问题。

6.17 B膨胀后,结果向左平移了

6.18   Bv膨胀后位置不变

6.17是用B膨胀的,可以看到,OPEN(X)向左平移了。图18是用Bv膨胀的,可以看到,总的位置和形状不变。

6.19为图6.11经过开运算后的结果。

6.19   6.11经过开运算后的结果

开运算的源程序可以很容易的根据上面的腐蚀,膨胀程序得到,这里就不给出了。

先膨胀后腐蚀称为闭(close),即CLOSE(X)=E(D(X))

让我们来看一个闭运算的例子(见图6.20)

6.20   闭运算

在图6.20上面的两幅图中,左边是被处理的图象X(二值图象,我们针对的是黑点),右边是结构元素B,下面的两幅图中左边是膨胀后的结果,右边是在此基础上腐蚀的结果可以看到,原图经过闭运算后,断裂的地方被弥合了。一般来说,闭运算能够填平小湖(即小孔),弥合小裂缝,而总的位置和形状不变。这就是闭运算的作用。同样要注意的是,如果B是非对称的,进行闭运算时要用B的对称集Bv膨胀,否则,闭运算的结果和原图相比要发生平移。

6.21为图6.11经过闭运算后的结果。

6.21   .611经过闭运算后的结果

闭运算的源程序可以很容易的根据上面的膨胀,腐蚀程序得到,这里就不给出了。

你大概已经猜到了,开和闭也是对偶运算,的确如此。用公式表示为(OPEN(X))c=CLOSE((Xc)),或者(CLOSE(X))c =OPEN((Xc))。即开运算的补集等于X的补集的闭运算,或者闭运算的补集等于X的补集的开运算。这句话可以这样来理解:在两个小岛之间有一座小桥,我们把岛和桥看做是处理对象X,则X的补集为大海。如果涨潮时将小桥和岛的外围淹没(相当于用尺寸比桥宽大的结构元素对X进行开运算),那么两个岛的分隔,相当于小桥两边海域的连通(Xc做闭运算)


细化(thinning)算法有很多,我们在这里介绍的是一种简单而且效果很好的算法,用它就能够实现从文本抽取骨架的功能。我们的对象是白纸黑字的文本,但在程序中为了处理的方便,还是采用256级灰度图,不过只用到了调色板中0255两项。

所谓细化,就是从原来的图中去掉一些点,但仍要保持原来的形状。实际上,是保持原图的骨架。所谓骨架,可以理解为图象的中轴,例如一个长方形的骨架是它的长方向上的中轴线;正方形的骨架是它的中心点;圆的骨架是它的圆心,直线的骨架是它自身,孤立点的骨架也是自身。文本的骨架嘛,前言中的例子显示的很明白。那么怎样判断一个点是否能去掉呢?显然,要根据它的八个相邻点的情况来判断,我们给几个例子(如图6.22所示)

6.22   根据某点的八个相邻点的情况来判断该点是否能删除

6.22中,(1)不能删,因为它是个内部点,我们要求的是骨架,如果连内部点也删了,骨架也会被掏空的;(2)不能删,和(1)是同样的道理;(3)可以删,这样的点不是骨架;(4)不能删,因为删掉后,原来相连的部分断开了;(5)可以删,这样的点不是骨架;(6)不能删,因为它是直线的端点,如果这样的点删了,那么最后整个直线也被删了,剩不下什么;(7)不能删,因为孤立点的骨架就是它自身。

总结一下,有如下的判据:(1)内部点不能删除;(2)孤立点不能删除;(3)直线端点不能删除;(4)如果P是边界点,去掉P后,如果连通分量不增加,则P可以删除。

我们可以根据上述的判据,事先做出一张表,从0255共有256个元素,每个元素要么是0,要么是1。我们根据某点(当然是要处理的黑色点了)的八个相邻点的情况查表,若表中的元素是1,则表示该点可删,否则保留。

查表的方法是,设白点为1,黑点为0;左上方点对应一个8位数的第一位(最低位),正上方点对应第二位,右上方点对应的第三位,左邻点对应第四位,右邻点对应第五位,左下方点对应第六位,正下方点对应第七位,右下方点对应的第八位,按这样组成的8位数去查表即可。例如上面的例子中(1)对应表中的第0项,该项应该为0(2)对应37,该项应该为0(3)对应173,该项应该为1(4)对应231,该项应该为0(5)对应237,该项应该为1(6)对应254,该项应该为0(7)对应255,该项应该为0

这张表我已经替大家做好了,可花了我不少时间呢!

static int erasetable[256]={

                                         0,0,1,1,0,0,1,1,          1,1,0,1,1,1,0,1,

                                   1,1,0,0,1,1,1,1,             0,0,0,0,0,0,0,1,

                                          0,0,1,1,0,0,1,1,             1,1,0,1,1,1,0,1,

                                          1,1,0,0,1,1,1,1,             0,0,0,0,0,0,0,1,

                                          1,1,0,0,1,1,0,0,             0,0,0,0,0,0,0,0,

                                          0,0,0,0,0,0,0,0,             0,0,0,0,0,0,0,0,

                                          1,1,0,0,1,1,0,0,             1,1,0,1,1,1,0,1,

                                   0,0,0,0,0,0,0,0,             0,0,0,0,0,0,0,0,

                           0,0,1,1,0,0,1,1,             1,1,0,1,1,1,0,1,

                                          1,1,0,0,1,1,1,1,             0,0,0,0,0,0,0,1,

                                          0,0,1,1,0,0,1,1,             1,1,0,1,1,1,0,1,

                                          1,1,0,0,1,1,1,1,             0,0,0,0,0,0,0,0,

                                          1,1,0,0,1,1,0,0,             0,0,0,0,0,0,0,0,

                                1,1,0,0,1,1,1,1,             0,0,0,0,0,0,0,0,

                                          1,1,0,0,1,1,0,0,             1,1,0,1,1,1,0,0,

                                   1,1,0,0,1,1,1,0,             1,1,0,0,1,0,0,0

                                     };

有了这张表,算法就很简单了,每次对一行一行的将整个图象扫描一遍,对于每个点(不包括边界点),计算它在表中对应的索引,若为0,则保留,否则删除该点。如果这次扫描没有一个点被删除,则循环结束,剩下的点就是骨架点,如果有点被删除,则进行新的一轮扫描,如此反复,直到没有点被删除为止。

实际上,该算法有一些缺陷。举个简单的例子,有一个黑色矩形,如图6.23所示。

6.23经过细化后,我们预期的结果是一条水平直线,且位于该黑色矩形的中心。实际的结果确实是一条水平直线,但不是位于黑色矩形的中心,而是最下面的一条边。

为什么会这样,我们来分析一下:在从上到下,从左到右的扫描过程中,我们遇到的第一个黑点就是黑色矩形的左上角点,经查表,该点可以删。下一个点是它右边的点,经查表,该点也可以删,如此下去,整个一行被删了。每一行都是同样的情况,所以都被删除了。到了最后一行时,黑色矩形已经变成了一条直线,最左边的黑点不能删,因为它是直线的端点,它右边的点也不能删,因为如果删除,直线就断了,如此下去,直到最右边的点,也不能删,因为它是直线的右端点。所以最下面的一条边保住了,但这并不是我们希望的结果。

解决的办法是,在每一行水平扫描的过程中,先判断每一点的左右邻居,如果都是黑点,则该点不做处理。另外,如果某个黑点被删除了,那么跳过它的右邻居,处理下一个点。这样就避免了上述的问题。

6.23  黑色矩形

6.24  6.23细化后的结果

解决了上面的问题,我们来看看处理后的结果,如图6.24所示。这次变成一小段竖线了,还是不对,是不是很沮丧?别着急,让我们再来分析一下:在上面的算法中,我们遇到的第一个能删除的点就是黑色矩形的左上角点;第二个是第一行的最右边的点,即黑色矩形的右上角点;第三个是第二行的最左边的点;第四个是第二行的最右边的点;……;整个图象处理这样一次后,宽度减少2。每次都是如此,直到剩最中间一列,就不能再删了。为什么会这样呢?原因是这样的处理过程只实现了水平细化,如果在每一次水平细化后,再进行一次垂直方向的细化(只要把上述过程的行列换一下),就可以了。

这样一来,每处理一次,删除点的顺序变成:(先是水平方向扫描)第一行最左边的点;第一行最右边的点;第二行最左边的点;第二行最右边的点;……最后一行最左边的点;最后一行最右边的点;(然后是垂直方向扫描)第二列最上边的点(因为第一列最上边的点已被删除);第二列最下边的点;第三列最上边的点;第三列最下边的点;……倒数第二列最上边的点(因为倒数第一列最上边的点已被删除);倒数第二列最下边的点。我们发现,刚好剥掉了一圈,这也正是细化要做的事。实际的结果也验证了我们的想法。


题外话:

腐蚀:删除对象边界的某些像素

膨胀:给图像中的对象边界添加像素

算法:

膨胀算法:用3X3的结构元素,扫描二值图像的每一个像素,用结构元素与其覆盖的二值图像做“与”运算,如果都为0,结构图像的该像素为0,否则为1.结果:使二值图像扩大一圈。

腐蚀算法:用3X3的结构元素,扫描二值图像的每一个像素,用结构元素与其覆盖的二值图像做“与”运算,如果都为1,结构图像的该像素为1,否则为0.结果:使二值图像减小一圈。


没有更多推荐了,返回首页