Visual C++ 实现数字化图像的分割

2016-02-19 21:35 60 1 收藏

关注图老师设计创意栏目可以让大家能更好的了解电脑,知道有关于电脑的更多有趣教程,今天给大家分享Visual C++ 实现数字化图像的分割教程,希望对大家能有一点小小的帮助。

【 tulaoshi.com - 编程语言 】

一、前言
  
  用计算机进行数字图像处理的目的有两个,一是产生更适合人类视觉观察和识别的图像,二是希望计算机能够自动进行识别和理解图像。无论是为了何种目的,图像处理的要害一步是对包含有大量各式各样景物信息的图像进行分解。分解的最终结果就是图像被分成一些具有各种特征的最小成分,这些成分就称为图像的基元。产生这些基元的过程就是图像分割的过程。图像分割作为图像处理领域中极为重要的内容之一,是实现图像分析与理解的基础。从概念上来说,所谓图像分割就是按照一定的原则将一幅图像或景物分为若干个部分或子集的过程。目前图像处理系统中我们只能得到二维图像信息,因此只能进行图像分割而不是景物分割(景物是三维信息);图像分割也可以理解为将图像中有意义的特征区域或者需要应用的特征区域提取出来,这些特征区域可以是像素的灰度值、物体轮廓曲线、纹理特性等,也可以是空间频谱或直方图特征等。在图像中用来表示某一物体的区域,其特征都是相近或相同的,但是不同物体的区域之间,特征就会急剧变化。目前已经提出的图像分割方法很多,从分割依据的角度来看,图像的分割方法可以分为相似性分割和非连续性分割。相似性分割就是将具有同一灰度级或相同组织结构的像素聚集在一起,形成图像的不同区域;非连续性分割就是首先检测局部不连续性,然后将它们连接在一起形成边界,这些边界将图像分成不同的区域。由于不同种类的图像,不同的应用场合,需要提取的图像特征是不同的,当然对应的图像特征提取方法也就不同,因此并不存在一种普遍适应的最优方法。
  
  图像分割方法又可分为结构分割方法和非结构分割方法两大类。结构分割方法是根据图像的局部区域象素的特征来实现图像分割,如阈值分割、区域生长、边缘检测、纹理分析等,这些方法假定事先知道这些区域的特性,或者在处理过程中能够求得这些特性,从而能够寻找各种形态或研究各像素群。非结构分割法包括统计模式识别、神经网络方法或其它利用景物的先验知识实现的方法等等。这些内容由于专业性很强,就不在本文讨论内容中了,有爱好的读者可以参考图像处理的专业书籍。总之,图像分割可以分为图像的边缘提取和图像的二值化二部分内容,下面我们首先来讨论一下各种常用的图像边缘提取的方法。  二、图像边缘检测
  
  数字图像的边缘检测是图像分割、目标区域的识别、区域外形提取等图像分析领域十分重要的基础,是图像识别中提取图像特征的一个重要属性,图像理解和分析的第一步往往就是边缘检测,目前它以成为机器视觉研究领域最活跃的课题之一,在工程应用中占有十分重要的地位。物体的边缘是以图像的局部特征不连续的形式出现的,也就是指图像局部亮度变化最显著的部分,例如灰度值的突变、颜色的突变、纹理结构的突变等,同时物体的边缘也是不同区域的分界处。图像边缘有方向和幅度两个特性,通常沿边缘的走向灰度变化平缓,垂直于边缘走向的像素灰度变换剧烈,根据灰度变化的特点,可分为阶跃型、房顶型和凸缘型,如图一所示,这些变化对应图像中不同的景物。需要读者注重的是,实际分析中图像要复杂的多,图像边缘的灰度变化情况并不仅限于上述标准情况。
  
  Visual C++ 实现数字化图像的分割(图一)
  (a)阶跃型
  Visual C++ 实现数字化图像的分割(图二)
  (b) 房顶型
  Visual C++ 实现数字化图像的分割(图三)
  (c) 凸缘型
  
     图一 边缘灰度变换的几种类型
  
  由于边缘是图像上灰度变化最剧烈的地方,传统的边缘检测就是利用了这个特点,对图像各个像素点进行微分或求二阶微分来确定边缘像素点。一阶微分图像的峰值处对应着图像的边缘点;二阶微分图像的过零点处对应着图像的边缘点。根据数字图像的特点,处理图像过程中常采用差分来代替导数运算,对于图像的简单一阶导数运算,由于具有固定的方向性,只能检测特定方向的边缘,所以不具有普遍性。为了克服一阶导数的缺点,我们定义了图像的梯度为梯度算子为Visual C++ 实现数字化图像的分割(图四) ,它是图像处理中最常用的一阶微分算法,式子中Visual C++ 实现数字化图像的分割(图五) 表示图像的灰度值,图像梯度的最重要性质是梯度的方向是在图像灰度最大变化率上,它恰好可以放映出图像边缘上的灰度变化。
  
  图像边缘提取的常用梯度算子有Robert算子、Sobel算子、Prewitt算子、Krisch算子等。下面以边缘检测Sobel算子为例来讲述数字图像处理中边缘检测的实现:
  
  对于数字图像,可以用一阶差分代替一阶微分;
  
    △xf(x,y)=f(x,y)-f(x-1,y);
    △yf(x,y)=f(x,y)-f(x,y-1);
  
  求梯度时对于平方和运算及开方运算,可以用两个分量的绝对值之和表示,即: 
  
   Visual C++ 实现数字化图像的分割(图六)
  
  Sobel梯度算子是先做成加权平均,再微分,然后求梯度,即:
  
    △xf(x,y)= f(x-1,y+1) + 2f(x,y+1) + f(x+1,y+1)- f(x-1,y-1) - 2f(x,y-1) - f(x+1,y-1);
    △yf(x,y)= f(x-1,y-1) + 2f(x-1,y) + f(x-1,y+1)- f(x+1,y-1) - 2f(x+1,y) - f(x+1,y+1);
    G[f(x,y)]=△xf(x,y)+△yf(x,y);
  
  上述各式中的像素之间的关系见图二
   F(x-1,y-1) F(x,y-1) F(x+1,y-1) F(x-1,y) F(x,y) F(x+1,y) F(x-1,y+1) F(x,y+1) F(x+1,y+1)   图二 Sober算子中各个像素点的关系图
  
  
  我在视图类中定义了响应菜单命令的边缘检测Sobel算子实现灰度图像边缘检测的函数(图像数据的获取可以参见我在天极网上发表的的相关文章):
  
  void CDibView::OnMENUSobel()
  {
   CClientDC pDC(this);
  HDC hDC=pDC.GetSafeHdc();//获取当前设备上下文的句柄;
  SetStretchBltMode(hDC,COLORONCOLOR);
  HANDLE data1handle;
   LPDIBHDRTMAPINFOHEADER lpDIBHdr;
   CDibDoc *pDoc=GetDocument();
   HDIB hdib;
   unsigned char *lpDIBBits;
   unsigned char *data;
   hdib=pDoc-m_hDIB;//得到图象数据;
   lpDIBHdr=(LPDIBHDRTMAPINFOHEADER)GlobalLock((HGLOBAL)hdib);
   lpDIBBits= lpDIBHdr +* (LPDWord)lpDIBHdr + 256*sizeof(RGBQUAD);
   //得到指向位图像素值的指针;
  data1handle=GlobalAlloc(GMEM_SHARE,WIDTHBYTES(lpDIBHdr-biWidth*8)*lpDIBHdr-biHeight); //申请存放处理后的像素值的缓冲区
  data=(unsigned char*)GlobalLock((HGLOBAL)data1handle);
   AfxGetApp()-BeginWaitCursor();
   int i,j,buf,buf1,buf2;
   for( j=0; jbiHeight; j++)//以下循环求(x,y)位置的灰度值
  for( i=0; ibiWidth; i++)
   {
   if(((i-1)=0)&&((i+1)biWidth)&&((j-1)=0)&&((j+1)biHeight))
   {//对于图像四面边界处的向素点不处理
    buf1=(int)*(lpDIBBits+(i+1)*WIDTHBYTES(lpDIBHdr-biWidth*8)+(j-1))
     +2*(int)*(lpDIBBits+(i+1)*WIDTHBYTES(lpDIBHdr-biWidth*8)+(j))
      +(int)(int)*(lpDIBBits+(i+1)*WIDTHBYTES(lpDIBHdr-biWidth*8)+(j+1));
    buf1=buf1-(int)(int)*(lpDIBBits+(i-1)*WIDTHBYTES(lpDIBHdr-biWidth*8)+(j-1))
     -2*(int)(int)*(lpDIBBits+(i-1)*WIDTHBYTES(lpDIBHdr-biWidth*8)+(j))
     -(int)(int)*(lpDIBBits+(i-1)*WIDTHBYTES(lpDIBHdr-biWidth*8)+(j+1));
  
    //以上是对图像进行水平(x)方向的加权微分
  
    buf2=(int)(int)*(lpDIBBits+(i-1)*WIDTHBYTES(lpDIBHdr-biWidth*8)+(j+1))
     +2*(int)(int)*(lpDIBBits+(i)*WIDTHBYTES(lpDIBHdr-biWidth*8)+(j+1))
     +(int)(int)*(lpDIBBits+(i+1)*WIDTHBYTES(lpDIBHdr-biWidth*8)+(j+1)); 
    buf2=buf2-(int)(int)*(lpDIBBits+(i-1)*WIDTHBYTES(lpDIBHdr-biWidth*8)+(j-1))
     -2*(int)(int)*(lpDIBBits+(i)*WIDTHBYTES(lpDIBHdr-biWidth*8)+(j-1))
     -(int)(int)*(lpDIBBits+(i+1)*WIDTHBYTES(lpDIBHdr-biWidth*8)+(j-1));
   
    //以上是对图像进行垂直(y)方向加权微分
  
    buf=abs(buf1)+abs(buf2);//求梯度
  
   if(buf255) buf=255;
     if(buf0)buf=0;
   *(data+i*WIDTHBYTES(lpDIBHdr-biWidth*8)+j)=(BYTE)buf;
   }
   else *(data+i*lpDIBHdr-biWidth+j)=(BYTE)0;
  }
   for( j=0; jbiHeight; j++)
   for( i=0; ibiWidth; i++)
   *(lpDIBBits+i*WIDTHBYTES(lpDIBHdr-biWidth*8)+j)=*(data+i*WIDTHBYTES(lpDIBHdr-biWidth*8)+j);     //处理后的数据写回原缓冲区
  StretchDIBits (hDC,0,0,lpDIBHdr-biWidth,lpDIBHdr-biHeight,0,0,
  lpDIBHdr-biWidth,lpDIBHdr-biHeight,
  lpDIBBits,(LPDIBHDRTMAPINFO)lpDIBHdr,
  DIB_RGB_COLORS,
  SRCCOPY); 
  }
  
  上述的数学分析读者可能看起来有些吃力,不过不要紧,对与边缘检测,大家只要知道有若干个检测模板(既边缘检测矩阵)可以直接实现检测功能就行了,现在将常用的检测实现公式列出如下(检测模版可以从相应的算法很轻易的得到):
  
  Roberts算子:G[i,i]=f[i,j]-f[i+1,j+1]+f[i+1,j]-f[i,j+1];
  
  Sobe算子:G[i,i]=f[i-1,j+1]+2f[i,j+1]+f[i+1,j+1]-f[i-1,j-1]-2f[i,j-1]-f[i+1,j-1]
  
          +f[i-1,j-1]+2f[i-1,j]+f[i-1,j+1]-f[i+1,j-1]-2f[i+1,j]-f[i+1,j+1];
  
  其中G[i,j]表示处理后(i,j)点的灰度值,f[i,j]表示处理前该点的灰度值。Photoshop教程 数据结构 五笔输入法专题 QQ病毒专题 共享上网专题 Google工具和服务专题   Kirsch算子实现起来相对来说稍微麻烦一些,它采用8个模板对图像上的每一个像素点进行卷积求导数,这8个模板代表8个方向,对图像上的8个特定边缘方向作出最大响应,运算中取最大值作为图像的边缘输出(上述算法中用到的8个模板在下面的实现代码中给出)。为了便于读者理解该算法的实现,这里我们给出实现该算法的函数代码,读者可以稍加改动应用到自己的项目中去。
  
  BOOL Kirsch(BYTE *pData,int Width,int Height)
  {//定义实现Kirsch算法的8个模板;
  int i,j,s,t,k,max,sum[8];
  static a[3][3]={{+5,+5,+5},{-3,0,-3},{-3,-3,-3}};
  static a1[3][3]={{-3,+5,+5},{-3,0,+5},{-3,-3,-3}};
  static a2[3][3]={{-3,-3,+5},{-3,0,+5},{-3,-3,+5}};
  static a3[3][3]={{-3,-3,-3},{-3,0,+5},{-3,+5,+5}};
  static a4[3][3]={{-3,-3,-3},{-3,0,-3},{+5,+5,+5}};
  static a5[3][3]={{-3,-3,-3},{+5,0,-3},{+5,+5,-3}};
  static a6[3][3]={{+5,-3,-3},{+5,0,-3},{+5,-3,-3}};
  static a7[3][3]={{+5,+5,-3},{+5,0,-3},{-3,-3,-3}};
  BYTE *pData1;
  if(pData==NULL)
  {
  AfxMessageBox("图像数据为空,请读取图像数据!");
  return FALSE;
  }
  pData1=(BYTE*)new char[Width*Height];
  if(pData1==NULL)
  {
  AfxMessageBox("图像缓冲数据区申请失败,请重新申请图像数据缓冲区!");
  return FALSE ;
  }
  memcpy(pData1,pData, Width*8*Height);
  //kirsch算子处理,对每一像素点求取八个方向的导数;;
  for(i=1;iHeight-1;i++)
  for(j=1;jWidth-1;j++)
  {
  sum[1]=sum[2]=sum[3]=sum[4]=sum[5]=sum[6]=sum[7]=sum[8]=0;
  for(t=-1;t2;t++)
  {
  for(s=-1;s2;s++)
  { sum[1]+=*(pData+WIDTHBYTES(Width*8)*(i+t)+j+s)*a[1+t][1+s];
  sum[2]+=*(pData+WIDTHBYTES(Width*8)*(i+t)+j+s)*a1[1+t][1+s]; sum[3]+=*(pData+WIDTHBYTES(Width*8)*(i+t)+j+s)*a2[1+t][1+s]; sum[4]+=*(pData+WIDTHBYTES(Width*8)*(i+t)+j+s)*a3[1+t][1+s]; sum[5]+=*(pData+WIDTHBYTES(Width*8)*(i+t)+j+s)*a4[1+t][1+s]; sum[6]+=*(pData+WIDTHBYTES(Width*8)*(i+t)+j+s)*a5[1+t][1+s]; sum[7]+=*(pData+WIDTHBYTES(Width*8)*(i+t)+j+s)*a6[1+t][1+s]; sum[8]+=*(pData+WIDTHBYTES(Width*8)*(i+t)+j+s)*a7[1+t][1+s];
  }
  }
  //取最大方向的导数;
  for(k=0;k8;k++)
  {
  max=0;
  if(maxsum[k])
  max=sum[k];
  }
  if(max0)
  max=0;
  if(max255)
  max=255;
  *(pData1+ Width*8*i+j)=max;
  }
  memcpy(pData,pData1, Width*8*Height);
  delete pData1;
  return TRUE;
  }
  
  另外还有一种称为拉普拉斯的算子是不依靠于边缘方向的二阶微分算子,其表示式Visual C++ 实现数字化图像的分割(图七) ,对于数字图像来说拉普拉斯算子可以简单表示为:G[I,j]=f[i+1,j]+f[i-1,j]+f(i,j+1)+f[i,j-1]-4f[i,j];它是一个标量而不是向量,具有旋转不变,既各向同性的性质,它经常用在图像处理的过程中。
  
  
  梯度算子和拉普拉斯算子对噪声敏感,它们都使噪声成份加强,因此在处理含有较大噪声的图像时,经常先对图像进行平滑操作,然后再进行二阶微分,这就产生了所谓的LOG(又称为Marr方法)边缘检测方法。它先用高斯函数对图像进行平滑,然后再用拉普拉斯算子进行运算。
  
  总的来说,传统的边缘检测算子的噪声平滑能力和边缘定位能力是矛盾的,为了克服这个不足,正确地得到图像的边缘信息,人们提出了很多方法,如多尺度空间滤波、Facet模型检测边缘、模板匹配、Hough变换、小波变换、人工神经网络、模糊推理等算法。但这些方法绝大多数没有经典的算法精简,要么难以获得合理的计算复杂度,要么需要人为的调节各种参数,有时甚至难以实时运行。因为传统边缘的定义为图像中灰度的突变,所以这样定义边缘既失去了边缘的部分信息,又把噪声的影响包含在了边缘中。其实,边缘往往具有以下特征:
  
  1)灰度突变;
  
  2)是不同区域的边界;
  
  3)具有方向性;
  
  根据边缘的这三个特征,可以判定所关心的区域其特征是否存在差异来判定是否存在边缘的可能性。假如特征没有差异,则认为是平滑区;假如特征有差异,则判定为边缘点。算法的具体实现步骤如下:
  
  1) 设置四个3x3模板如图三所示,显而易见,四个模板分别按0、45、90、135以(x,y)点为中心将3x3的区域分成两个部分,按照这四个模板分别对图像中的每一像素点进行卷积求和操作。
  
  2)对图像中每一像素点求的四个结果求绝对值,将每个结果分别与一个阈值比较,假如其中任意一结果大于或等于阈值T,则该模板的中心点所对应的图像像素点的灰度值为255,否则为0。
  
   Visual C++ 实现数字化图像的分割(图八)
  图三 边缘提取模板
  
  对于有噪声的图像,由于噪声是随机分布的,因此不论(x,y)是有效边界点还是处于平坦区域内部,沿边缘方向划分的两个区域R1和R2的噪声分布和噪声强度,在概率上相同。从四个模板的结构可以看出,噪声的影响基本上被相应的抵消,不会对边缘提取产生太大的影响,因此该算法具有较好的抗噪能力,克服了传统的边界提取仅考虑灰度突变的情况的局限。经实验证实该方法有较强的抗噪声性能。为了更好的对比各种算法进行边缘检测的效果,我们对一幅汽车图像进行了处理,下图中的a、b、c、d是分别通过LOG算子、Sober算子、Kirsch算子和使用上述算法检测得到的边缘。
  
  Visual C++ 实现数字化图像的分割(图九)
  (a) 原始图像
  Visual C++ 实现数字化图像的分割(图十)
  (b)LOG算子
  Visual C++ 实现数字化图像的分割(图十)
  (c) Sober算子
  Visual C++ 实现数字化图像的分割(图十二)
  (d) Kirsch算子
  Visual C++ 实现数字化图像的分割(图十三)
  (e) 模板检测法
     图四 图像的边缘检测
  
  从上面的处理后的效果图来说,只有最后一种算法处理后肉眼看不出有噪声点,所以从去噪和提取边缘的综合效果来看,我们上述介绍的模板检测算法还是比较另人满足的。Photoshop教程 数据结构 五笔输入法专题 QQ病毒专题 共享上网专题 Google工具和服务专题   三、图像的二值化
  
  所谓二值图像,就是指图像上的所有点的灰度值只用两种可能,不为"0"就为"255",也就是整个图像呈现出明显的黑白效果。为了得到理想的二值图像,一般采用阈值分割技术,它对物体与背景有较强对比的图像的分割非凡有效,它计算简单而且总能用封闭、连通的边界定义不交叠的区域。所有灰度大于或等于阈值的像素被判决为属于物体,灰度值用"255"表示,否则这些像素点被排除在物体区域以外,灰度值为"0",表示背景。这样一来物体的边界就成为这样一些内部的点的集合,这些点都至少有一个邻点不属于该物体。假如感爱好的物体在内部有均匀一致的灰度值,并且其处在一个具有另外一个灰度值的均匀背景下,使用阈值法可以得到比较好的效果。假如物体同背景的差别不在灰度值上(比如纹理不同),可以将这个性质转换为灰度的差别,然后利用阈值化技术来分割该图像。为了使分割更加鲁棒,适用性更强,系统应该可以自动选择阈值。基于物体、环境和应用域等知识的图像分割算法比基于固定阈值的算法更具有普遍性和适应性。这些知识包括:对应于物体的图像灰度特性、物体的尺寸、物体在图像中所占的比例、图像中不同类型物体的数量等。其中图像直方图就是一种灰度特性,通常被用来作为分割图像的工具。
  
  阈值分割法分为全局阈值法和局部阈值分割法。所谓局部阈值分割法是将原始图像划分成较小的图像,并对每个子图像选取相应的阈值。在阈值分割后,相邻子图像之间的边界处可能产生灰度级的不连续性,因此需用平滑技术进行排除。局部阈值法常用的方法有灰度差直方图法、微分直方图法。局部阈值分割法虽然能改善分割效果,但存在几个缺点:
  
  (1)每幅子图像的尺寸不能太小,否则统计出的结果无意义。
  
  (2)每幅图像的分割是任意的,假如有一幅子图像正好落在目标区域或背景区域,而根据统计结果对其进行分割,也许会产生更差的结果。
  
  (3)局部阈值法对每一幅子图像都要进行统计,速度慢,难以适应实时性的要求。
  
  全局阈值分割方法在图像处理中应用比较多,它在整幅图像内采用固定的阈值分割图像。经典的阈值选取以灰度直方图为处理对象。根据阈值选择方法的不同,可以分为模态方法、迭代式阈值选择等方法。这些方法都是以图像的直方图为研究对象来确定分割的阈值的。另外还有类间方差阈值分割法、二维最大熵分割法、模糊阈值分割法、共生矩阵分割法、区域生长法等等。
  
  
  对于比较简单的图像,可以假定物体和背景分别处于不同的灰度级,图像被零均值高斯噪声污染,所以图像的灰度分布曲线近似认为是由两个正态分布函数(Visual C++ 实现数字化图像的分割(图十四) )和(Visual C++ 实现数字化图像的分割(图十五) )叠加而成,图像的直方图将会出现两个分离的峰值,如图五所示。对于这样的图像,分割阈值可以选择直方图的两个波峰间的波谷所对应的灰度值作为分割的阈值。这种分割方法不可避免的会出现误分割,使一部分本属于背景的像素被判决为物体,属于物体的一部分像素同样会被误认为是背景。可以证实,当物体的尺寸和背景相等时,这样选择阈值可以使误分概率达到最小。在大多数情况下,由于图像的直方图在波谷四周的像素很稀疏,因此这种方法对图像的分割影响不大。这一方法可以推广到具有不同灰度均值的多物体图像。
  
   Visual C++ 实现数字化图像的分割(图十六)
  图五 双峰直方图
  
  迭代式阈值选择算法是对上一种方法的改进,它首先选择一个近似阈值T,将图像分割成两部分Visual C++ 实现数字化图像的分割(图十七)Visual C++ 实现数字化图像的分割(图十八),计算区域Visual C++ 实现数字化图像的分割(图十九)Visual C++ 实现数字化图像的分割(图二十) 的均值 Visual C++ 实现数字化图像的分割(图二十)Visual C++ 实现数字化图像的分割(图二十二) ,选择新的分割阈值T=(Visual C++ 实现数字化图像的分割(图二十三) )/2,重复上述步骤直到Visual C++ 实现数字化图像的分割(图二十四)Visual C++ 实现数字化图像的分割(图二十五)不再变化为止。Photoshop教程 数据结构 五笔输入法专题 QQ病毒专题 共享上网专题 Google工具和服务专题   后来"熵"的概念被引入了图像处理技术,人们提出了许多基于熵的阈值分割法。1980年,Pun提出了最大后验熵上限法,1985年,Kapur等人提出了一维最大熵阈值法,1989年Arutaleb将一维最大熵阈值法与Kirby等人的二维阈值方法相结合,提出了二维熵阈值法。对于一维最大熵分割方法,它的思想是统计图像中每一个灰度级出现的概率Visual C++ 实现数字化图像的分割(图二十六),计算该灰度级的熵Visual C++ 实现数字化图像的分割(图二十七) ,假设以灰度级T分割图像,图像中低于T灰度级的像素点构成目标物体(O),高于灰度级T的像素点构成背景(B),那么各个灰度级在本区的分布概率为:
  
  O区:Visual C++ 实现数字化图像的分割(图二十八) i=1,2……,t
  
  B区:Visual C++ 实现数字化图像的分割(图二十九) i=t+1,t+2……L-1
  
  上式中的Visual C++ 实现数字化图像的分割(图三十) ,这样对于数字图像中的目标和背景区域的熵分别为:
  
  Visual C++ 实现数字化图像的分割(图三十)
  
  
  对图像中的每一个灰度级分别求取w=Visual C++ 实现数字化图像的分割(图三十二) ,选取使w最大的灰度级作为分割图像的阈值,这就是一维最大熵阈值图像分割法。我们定义了一个函数GetMaxHtoThrod()来实现该算法,它的返回值就是用来分割图像的阈值。
  
  int GetMaxHtoThrod(BYTE *pData,int Width,int Height)
  {
  int i,j,t;
  float p[256],a1,a2,num[256],max,pt;
  if(pData==NULL)
  {
  AfxMessageBox("图像数据为空,请读取图像数据!");
  return -1;
  }
  //初始化数组p[];
  for(i=0;i256;i++)
  p[i]=0.0f;
  //统计各个灰度级出现的次数;
  for(i=0;iHeight;i++)
  for(j=0;jWidth;j++)
  {
  p[*(pData+ Width*8)*i+j]++;
  }
  //统计各个灰度级出现的概率;
  for(j=0;j256;j++)
  {
  p[j]=p[j]/(Width*Height);
  }
  //对每一个灰度级进行比较;
  for(i=0;i256;i++)
  {
  a1=a2=0.0f;
  pt=0.0f;
  for(j=0;j=i;j++)
  {
  pt+=p[j];
  }
  for(j=0;j=i;j++)
  {
  a1+=(float)p[j]/pt*logf(p[j]/pt);
  }
  for(j=i+1;j256;j++)
  {
  a2+=(float)p[j]/(1-pt)*logf(p[j]/(1-pt));
  }
  num[i]=a1+a2;
  }
  max=0.0f;
  //找到使类的熵最大的灰度级;
  for(i=0;i256;i++)
  {
  if(maxnum[i])
  {
  max=num[i];
  t=i;
  }
  }
  return t;
  }
  这种方法的缺点是仅仅考虑了像素点的灰度信息,没有考虑到像素点的空间信息,所以当图像的信噪比降低时分割效果不理想。毫无疑问,像素点的灰度是最基本的特征,但它对噪声比较敏感,为此,在分割图像时可以再考虑图像的区域信息,区域灰度特征包含了图像的部分空间信息,且对噪声的敏感程度要低于点灰度特征。综合利用图像的这两个特征就产生了二维最大熵阈值分割方法。二维最大熵阈值分割算法实现时首先以原始灰度图像中各个像素的每一个像素及其四邻域的四个像素构成一个区域,该像素点的灰度值i和四邻域的均值j构成一个二维向量(i,j),统计(i,j)的发生概率Visual C++ 实现数字化图像的分割(图三十三) ,假如图像的最大灰度级为Visual C++ 实现数字化图像的分割(图三十四)Visual C++ 实现数字化图像的分割(图三十五) ,那么 Visual C++ 实现数字化图像的分割(图三十六)(i,j=0,1…Visual C++ 实现数字化图像的分割(图三十七) )就构成了该图像关于点灰度-区域均值的二维直方图。对于给定的图像,由于大部份的像素点属于目标区域或背景,而目标和背景区域内部像素点的灰度级比较均匀,像素点的灰度和其邻域均值的灰度级相差不大,所以图像对应的二维直方图Visual C++ 实现数字化图像的分割(图三十八) 主要集中在i,j平面的对角线四周,并且在总体上呈现双峰和一谷的状态,两个峰分别对应于目标和背景。在远离IOJ平面对角线的坐标处,峰的高度迅速下降,这部分对应着图像中的噪声点、杂散点和边缘点。二维直方图的IOJ平面图如图六所示,沿对角线的方向分布的A区、B区分别代表目标和背景,远离对角线分布的C区、D区分别代表边界和噪声,所以应该在A区和B区上用点灰度-区域灰度平均值二维最大熵法确定阈值,使之分割的目标和背景的信息量最大。
  
  Visual C++ 实现数字化图像的分割(图三十九)
  图 六  二维直方图的IOJ平面图
  
  确定二维最大熵的算法和确定一维最大熵算法类似,设分割图像的阈值为(s,t),则A区、B区概率分别为:
  
  Visual C++ 实现数字化图像的分割(图四十)
  
  则A区、B区的二维熵分别为:
  
   Visual C++ 实现数字化图像的分割(图四十)
  
  对于确定图像的二维直方图,对不同的(Visual C++ 实现数字化图像的分割(图四十二))分别计算w=H(A)+H(B),选取使w达到最大的(Visual C++ 实现数字化图像的分割(图四十三))作为最佳分割图像的阈值。该算法实现的函数和上述一维最大熵算法大同小异,只是在二值化时对图像上的像素点不仅要考虑灰度值,同时还要考虑该点邻域的灰度均值。
  
  
  Visual C++ 实现数字化图像的分割(图四十四)
  (a)标准lena图像
  Visual C++ 实现数字化图像的分割(图四十五)
  (b)一维最大熵 
  Visual C++ 实现数字化图像的分割(图四十六)
  (c) 二维最大熵
       图七 二值图像
   
  上图中a、b、c分别显示了标准lena图像、采用一维最大熵法、二维最大熵法得到的分割效果。可以看出,二维最大熵法可以很好的分割包含目标和背景两类区域的图像,效果最好。
  
  由于篇幅有限,本文只能是蜻蜓点水一般对比较非凡的分割算法介绍了一下,其实无论二值化还是边缘提取的处理,对应的算法都很多,都各有特色,读者可以针对不同的图像,结合前人提出的各种算法,找到实际应用中最佳的处理效果。

来源:http://www.tulaoshi.com/n/20160219/1626591.html

延伸阅读
  适用于: Microsoft Visual C++ .NET 2003 Microsoft Visual C++ Toolkit 2003 Microsoft Visual Studio .NET 摘要: 演示了 Visual C++ 2003 编译器提供的众多代码优化功能中的几项功能。(8 ...
标签: PS PS基础
最终效果 新建文档,用淡咖啡色填充,并将人像移到本文档,使用自由变换命令调节大小及位置,如下图: 下面我们要对人像不同的位置添加效果,解析图如下图所示 新建图层,使用椭圆工具(文字工具下面那个自定义形状七工具组里的)绘制圆形,并使用下图的颜色填充,然后拷贝多个,使用移动工具调整大小及位置。 按照同样的方法,绘...
“N次失败,1次成功,还学的不怎么样……你也太笨了吧!”假如您有这样的评价,那么祝贺您,您已经了解了我这人的99.9%。N这个数字具体是多少连我自己也记不清楚,保守地讲(N≥6==TRUE)这个表达式是能够成立的。 !-- frame contents -- !-- /frame contents -- 回想我的VisualC++入门过程,一路过来,绝对不是像黄飞鸿的功夫那...
程序作者:管宁 个人网站:www.cndev-lab.com VC作为一个主流的开发平台一直深受编程爱好者的喜爱,但是很多人却对它的入门感到难于上青天,究其原因主要是大家对他错误的熟悉造成的,严格的来说VC++不是门语言,虽然它和C++之间有密切的关系,假如形象点比喻的话,可以C++看作为一种”工业标准”,而VC++则是某种操作系统平台下的”厂商标准”,...
当我们使用Visual C++进行ADO编程时,一项颇为头疼的工作就是对VARIANT字段类型的处理。通常做法是,先把VARIANT类型转换为形式上较为类似的C++类型,然后再把转换后的数据存放在一个类(class)或结构(structure)中。即便如此,对VARIANT数据类型的处理在一定程度上也影响到了程序的性能。 ADO为我们提供了一个接口,该接口使我们可...

经验教程

694

收藏

98
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部