一、前言
用计算机进行数字图像处理的目的有两个,一是产生更适合人类视觉观察和识别的图像,二是希望计算机能够自动进行识别和理解图像。无论是为了何种目的,图像处理的关键一步是对包含有大量各式各样景物信息的图像进行分解。分解的最终结果就是图像被分成一些具有各种特征的最小成分,这些成分就称为图像的基元。产生这些基元的过程就是图像分割的过程。图像分割作为图像处理领域中极为重要的内容之一,是实现图像分析与理解的基础。从概念上来说,所谓图像分割就是按照一定的原则将一幅图像或景物分为若干个部分或子集的过程。目前图像处理系统中我们只能得到二维图像信息,因此只能进行图像分割而不是景物分割(景物是三维信息);图像分割也可以理解为将图像中有意义的特征区域或者需要应用的特征区域提取出来,这些特征区域可以是像素的灰度值、物体轮廓曲线、纹理特性等,也可以是空间频谱或直方图特征等。在图像中用来表示某一物体的区域,其特征都是相近或相同的,但是不同物体的区域之间,特征就会急剧变化。目前已经提出的图像分割方法很多,从分割依据的角度来看,图像的分割方法可以分为相似性分割和非连续性分割。相似性分割就是将具有同一灰度级或相同组织结构的像素聚集在一起,形成图像的不同区域;非连续性分割就是首先检测局部不连续性,然后将它们连接在一起形成边界,这些边界将图像分成不同的区域。由于不同种类的图像,不同的应用场合,需要提取的图像特征是不同的,当然对应的图像特征提取方法也就不同,因此并不存在一种普遍适应的最优方法。
图像分割方法又可分为结构分割方法和非结构分割方法两大类。结构分割方法是根据图像的局部区域象素的特征来实现图像分割,如阈值分割、区域生长、边缘检测、纹理分析等,这些方法假定事先知道这些区域的特性,或者在处理过程中能够求得这些特性,从而能够寻找各种形态或研究各像素群。非结构分割法包括统计模式识别、神经网络方法或其它利用景物的先验知识实现的方法等等。这些内容由于专业性很强,就不在本文讨论内容中了,有兴趣的读者可以参考图像处理的专业书籍。总之,图像分割可以分为图像的边缘提取和图像的二值化二部分内容,下面我们首先来讨论一下各种常用的图像边缘提取的方法。
二、图像边缘检测
数字图像的边缘检测是图像分割、目标区域的识别、区域形状提取等图像分析领域十分重要的基础,是图像识别中提取图像特征的一个重要属性,图像理解和分析的第一步往往就是边缘检测,目前它以成为机器视觉研究领域最活跃的课题之一,在工程应用中占有十分重要的地位。物体的边缘是以图像的局部特征不连续的形式出现的,也就是指图像局部亮度变化最显著的部分,例如灰度值的突变、颜色的突变、纹理结构的突变等,同时物体的边缘也是不同区域的分界处。图像边缘有方向和幅度两个特性,通常沿边缘的走向灰度变化平缓,垂直于边缘走向的像素灰度变换剧烈,根据灰度变化的特点,可分为阶跃型、房顶型和凸缘型,如图一所示,这些变化对应图像中不同的景物。需要读者注意的是,实际分析中图像要复杂的多,图像边缘的灰度变化情况并不仅限于上述标准情况。
(a)阶跃型 |
(b) 房顶型 |
(c) 凸缘型 |
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) |
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(buf>255) buf=255; if(buf<0)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); } |
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;i sum[1]=sum[2]=sum[3]=sum[4]=sum[5]=sum[6]=sum[7]=sum[8]=0; for(t=-1;t<2;t++) { for(s=-1;s<2;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;k<8;k++) { max=0; if(max } if(max<0) max=0; if(max>255) max=255; *(pData1+ Width*8*i+j)=max; } memcpy(pData,pData1, Width*8*Height); delete pData1; return TRUE; } |
(a) 原始图像 |
(b)LOG算子 |
(c) Sober算子 |
(d) Kirsch算子 |
(e) 模板检测法 |
三、图像的二值化
所谓二值图像,就是指图像上的所有点的灰度值只用两种可能,不为"0"就为"255",也就是整个图像呈现出明显的黑白效果。为了得到理想的二值图像,一般采用阈值分割技术,它对物体与背景有较强对比的图像的分割特别有效,它计算简单而且总能用封闭、连通的边界定义不交叠的区域。所有灰度大于或等于阈值的像素被判决为属于物体,灰度值用"255"表示,否则这些像素点被排除在物体区域以外,灰度值为"0",表示背景。这样一来物体的边界就成为这样一些内部的点的集合,这些点都至少有一个邻点不属于该物体。如果感兴趣的物体在内部有均匀一致的灰度值,并且其处在一个具有另外一个灰度值的均匀背景下,使用阈值法可以得到比较好的效果。如果物体同背景的差别不在灰度值上(比如纹理不同),可以将这个性质转换为灰度的差别,然后利用阈值化技术来分割该图像。为了使分割更加鲁棒,适用性更强,系统应该可以自动选择阈值。基于物体、环境和应用域等知识的图像分割算法比基于固定阈值的算法更具有普遍性和适应性。这些知识包括:对应于物体的图像灰度特性、物体的尺寸、物体在图像中所占的比例、图像中不同类型物体的数量等。其中图像直方图就是一种灰度特性,通常被用来作为分割图像的工具。
阈值分割法分为全局阈值法和局部阈值分割法。所谓局部阈值分割法是将原始图像划分成较小的图像,并对每个子图像选取相应的阈值。在阈值分割后,相邻子图像之间的边界处可能产生灰度级的不连续性,因此需用平滑技术进行排除。局部阈值法常用的方法有灰度差直方图法、微分直方图法。局部阈值分割法虽然能改善分割效果,但存在几个缺点:
(1)每幅子图像的尺寸不能太小,否则统计出的结果无意义。
(2)每幅图像的分割是任意的,如果有一幅子图像正好落在目标区域或背景区域,而根据统计结果对其进行分割,也许会产生更差的结果。
(3)局部阈值法对每一幅子图像都要进行统计,速度慢,难以适应实时性的要求。
全局阈值分割方法在图像处理中应用比较多,它在整幅图像内采用固定的阈值分割图像。经典的阈值选取以灰度直方图为处理对象。根据阈值选择方法的不同,可以分为模态方法、迭代式阈值选择等方法。这些方法都是以图像的直方图为研究对象来确定分割的阈值的。另外还有类间方差阈值分割法、二维最大熵分割法、模糊阈值分割法、共生矩阵分割法、区域生长法等等。
对于比较简单的图像,可以假定物体和背景分别处于不同的灰度级,图像被零均值高斯噪声污染,所以图像的灰度分布曲线近似认为是由两个正态分布函数( )和( )叠加而成,图像的直方图将会出现两个分离的峰值,如图五所示。对于这样的图像,分割阈值可以选择直方图的两个波峰间的波谷所对应的灰度值作为分割的阈值。这种分割方法不可避免的会出现误分割,使一部分本属于背景的像素被判决为物体,属于物体的一部分像素同样会被误认为是背景。可以证明,当物体的尺寸和背景相等时,这样选择阈值可以使误分概率达到最小。在大多数情况下,由于图像的直方图在波谷附近的像素很稀疏,因此这种方法对图像的分割影响不大。这一方法可以推广到具有不同灰度均值的多物体图像。
图五 双峰直方图
迭代式阈值选择算法是对上一种方法的改进,它首先选择一个近似阈值T,将图像分割成两部分R1 和 R2,计算区域R1 和R2 的均值 u1和u2 ,选择新的分割阈值T=(u1+u2 )/2,重复上述步骤直到u1 和u2不再变化为止。