在对图像进行边缘检测处理时,得到的结果并不是理想的边缘,而是一幅灰度图像。有时在进行图像识别的时候需要获得图像的单点宽边缘,这就需要对边缘检测的结果进行细化增强。
Sobel边缘细化的原理
图像的边缘检测处理可以简单理解为提取图像中区域的轮廓。图像中区域的划分以像素灰度为依据,每个区域中的像素灰度大致相同,而区域之间的边界就称为边缘,寻找这些边缘就是图像边缘检测的目的。
图像边缘检测的结果直观地看类似图像的骨架,对图像边缘检测结果的细化是图像边缘细化很好的示例,如图11-27所示,其中a为原始图像,b为a图像的Sobel边缘检测结果。
图11-27 图像的边缘检测示例
从图11-27中可以看出,Sobel边缘检测的结果能用线条较好地描述图像,源图像中对比度较高的区域在结果图中体现为高灰度的像素,简单地说就是对源图像进行了“描边”操作。在进行图像细化时,最直接的思路就是对线条宽度进行缩减,然而图像中的线条往往是不规则的,这使得直接对线条进行处理的方法很难实现。
换一种思路,如果把Sobel边缘检测结果中的轮廓线条看作一个高灰度区域,那么如果能够对这个区域的边缘进行某种切割,使这个区域收缩,就可以实现图像线条的细化。考虑对Sobel边缘检测的结果S1再次进行Sobel边缘检测,则可以得到一幅双线条的边缘图像S2,而S2中双线条所覆盖的区域正好是S1中高灰度区域的边缘,如果用从S1中去除S2中的高灰度部分,得到的结果就恰好是S1的一个细化结果。这样的细化过程可以进行多次,进行次数越多,得到的细化图像线条就越细,但同时图像的信号强度也就越弱。图11-28中a、b显示了对图11-27中a分别进行一次和两次边缘检测的结果,c显示了对a、b进行减法运算的结果。
图11-28 图像的边缘检测和减法细化
从图中不难看出,虽然通过两次边缘检测和减法运算可以使边缘图像被很大程度地细化,然而代价却是图像信息的大量丢失。为了弥补这一缺陷,可以对图像进行多次边缘检测,然后得到多组减法结果,并将这些结果相加。通过这样的方法可以在一定程度上对细化结果进行增强,然而也很容易为细化结果引入杂点。
图像的细化同样可以利用邻域分析的方法来完成,考虑在图像的3×3邻域中,如果邻域内有5个以上的可见像素,那么此邻域中一定包含宽度超过1像素的线条,而如果此邻域中仅有3个或3个以下的可见像素,那么此邻域中一定只包含单点宽度的线条。图11-29中列举了几种不同邻域像素的情况,其中1表示可见像素,0表示背景,分析可知a中邻域可见像素只有3个,所以一定不包含宽度超过1的线条,而b中邻域的可见像素数目大于5个,所以b中一定包含宽度超过1的线条,c、d中邻域的可见像素数目都在3~5之间,c中邻域包含宽度超过1的线条,而d中邻域却不包含。
图11-29 图像邻域像素情况
在图像细化中,如果能够根据邻域内像素灰度情况,判定邻域的中心像素存在的必要性,那么基于邻域分析的图像细化处理就不难实现。在灰度图像的处理中,像素不能简单地用存在或不存在来描述,这为图像细化的邻域带来了一定的麻烦。在灰度图像中,若定义背景色为0灰度,那么灰度越高的像素越容易被人眼感知,对图像也就越重要,在图像细化中应优先保留。
不妨借助统计排序滤波器的思想,在决定中心像素是否保留的时候,先对邻域内像素按灰度进行排序,设定阈值K,若邻域的中心像素在序列中的次序小于K,则说明中心像素在邻域内较为重要需要被保留,否则就对中心像素进行删除。这样处理就限制了处理结果中每个邻域内仅包含的可见像素数目,对于3×3邻域,若设定K为3,则可限制图像细化的结果中尽可能只包含宽度为1的线条。在实践中可以通过调整K的值来控制细化的程度。如图11-30所示,其中a为图像的Sobel边缘检测结果,b、c为利用邻域分析的方法分别选取K=3、K=5对a进行细化的结果。
图11-30 利用邻域分析对图像进行细化
经过边缘细化的图像常常存在低灰度的杂点干扰,同时,为了提高图像边缘细化结果的对比度,可以对图像进行二值化处理,最终得到清晰的边缘细化结果。
11.6.2 Sobel边缘细化的编程实现
有关图像的边缘检测算法实现将在本书第13章中给出,这里只对细化部分的算法进行实现。
1.利用减法实现图像的细化
由于图像细化中的各方法可以自由组合,这里只给出各方法的实现,读者在使用时只用自行安排方法的调用顺序即可。
/********************************************************
* 把线性存储的像素转化为二维数组形式
* 参数: image为线性存储的像素,width、height为图像的长宽
* 方法实现见11.1.3小节
********************************************************/
BYTE** CreatImage(BYTE* image, unsigned int width, unsigned int height, int bt=4);
/**************************************************
* 功能: 获得图像灰度,采用直接对RGB求平均值的方法
* 参数: imageBuf为目标图像,x、y为要取得像素的坐标
* 方法实现见11.1.1小节
**************************************************/
BYTE GetAsh(BYTE** imageBuf0, int x, int y);
/**************************************************
* 功能: 设定指定位置的像素灰度
* 参数: imageBuf为目标图像,x、y为要设定像素的坐标
* 方法实现见11.1.3小节
**************************************************/
void SetPixelXY(BYTE** imageBuf1, int x, int y, int a);
/******************************************************************
* 功能: Sobel边缘检测
* 参数: image0为原图形,image1为要减去的图像
* w、h为图像的宽和高
* 当type为true时,差分结果取水平和垂直方向差分中较大者,
* 否则取平均值(方法实现见本书第13章)
******************************************************************/
void SideSobel(BYTE* image0, BYTE* image1,
unsigned int w, unsigned int h, bool type);
/******************************************************************
* 功能: 对图像进行二值化处理
* 参数: image0为原图形,image1为处理的结果图像
* w、h为图像的宽和高
* K为阈值
******************************************************************/
void ToTwoValue(BYTE* image0, BYTE* image1, unsigned int w, unsigned int h,
int K)
{
//将图像转化为矩阵形式
BYTE** imageBuf0 = CreatImage(image0, w, h);
BYTE** imageBuf1 = CreatImage(image1, w, h);
int x,y;
//依次对源图像的每个像素进行处理
for(y=0; y<h; y++)
for(x=0; x<w; x++)
{
//如果当前点已经为单点,则在结果图中用黑色标记
if( GetAsh(imageBuf0,x,y) >=K )
{
SetPixelXY (imageBuf1,x,y,255 );
}
//如果当前点不是单点,则在结果图中用白色标记
else
{
SetPixelXY(imageBuf1,x,y,0);
}
}
//清理内存
free(imageBuf0);
free(imageBuf1);
}
/**************************************************************************
* 功能: 对两幅图像进行减法运算
* 参数: image0为原图形,image1为要减去的图像
* w、h为图像的宽和高
**************************************************************************/
void Subtract(BYTE* image0, BYTE* image1, unsigned int w, unsigned int h)
{
//将图像转化为矩阵形式
BYTE** imageBuf0 = CreatImage(image0, w, h);
BYTE** imageBuf1 = CreatImage(image1, w, h);
int x,y;
int a;
//依次对源图像的每个像素进行处理
for(y=0; y<h; y++)
for(x=0; x<w; x++)
{
//取得源图像灰度
a=GetAsh(imageBuf0,x,y);
//进行减法运算
a=a-GetAsh(imageBuf1,x,y);
//过限处理
a = a>255?255:a;
a = a<0?0:a;
SetPixelXY(imageBuf1,x,y,a);
}
//清理内存
free(imageBuf0);
free(imageBuf1);
}
/**************************************************************************
* 功能: 对两幅图像进行加法运算
* 参数: image0为原图形,image1为要减去的图像
* w、h为图像的宽和高
**************************************************************************/
void AshAdd(BYTE* image0, BYTE* image1, unsigned int w, unsigned int h)
{
//将图像转化为矩阵形式
BYTE** imageBuf0 = CreatImage(image0, w, h);
BYTE** imageBuf1 = CreatImage(image1, w, h);
int x,y,c;
int a;
//依次对源图像的每个像素进行处理
for(y=0; y<h; y++)
for(x=0; x<w; x++)
{
//取得源图像灰度
a=GetAsh(imageBuf0,x,y);
//进行减法运算
a=a+GetAsh(imageBuf1,x,y);
//过限处理
a = a>255?255:a;
a = a<0?0:a;
SetPixelXY(imageBuf1,x,y,a);
}
//清理内存
free(imageBuf0);
free(imageBuf1);
}
2.利用邻域分析实现图像的细化
/***************************************************************************
* 把线性存储的像素转化为二维数组形式
* 参数: image为线性存储的像素,width、height为图像的长宽
* 方法实现见11.1.3小节
***************************************************************************/
BYTE** CreatImage(BYTE* image, unsigned int width, unsigned int height, int bt=4);
/**************************************************
* 功能: 获得图像灰度,采用直接对RGB求平均值的方法
* 参数: imageBuf为目标图像,x、y为要取得像素的坐标
* 方法实现见11.1.1小节
**************************************************/
BYTE GetAsh(BYTE** imageBuf0, int x, int y);
/**************************************************
* 功能: 设定指定位置的像素灰度
* 参数: imageBuf为目标图像,x、y为要设定像素的坐标
* 方法实现见11.1.3小节
**************************************************/
void SetPixelXY(BYTE** imageBuf1, int x, int y, int a);
/******************************************************************
* 功能: 对图像进行二值化处理
* 参数: image0为原图形,image1为处理的结果图像
* w、h为图像的宽和高
* K为阈值
* 方法实现见“利用减法实现图像的细化”一节
******************************************************************/
void ToTwoValue(BYTE* image0, BYTE* image1, unsigned int w, unsigned int h,
int K)
/**************************************************
* 功能: 判断当前邻域的中心像素是否该保留
* 参数: imageBuf为目标图像,w、h为图像大小
* x、y为当前采样窗口中心像素的坐标
* K为阈值
**************************************************/
bool IsOnePoint(BYTE** imageBuf0, int w, int h, int x, int y, int K)
{
int i,j,k;
int px,py;
int a; //用来保存中心像素灰度
a=GetAsh(imageBuf0,x,y);
k=0; //计数器
//从采样窗口中依次取得每个像素的灰度
for(i=0; i<3; i++)
for(j=0; j<3; j++)
{
py=y-1+i;
px=x-1+j;
//如果该像素灰度大于中心像素则计数器加1
if(GetAsh(imageBuf0,px,py)>a)
{
k++;
}
}
//如果中心像素灰度值处于邻域内前三位,则认为此中心像素已经为单点
if(k<K)
return true;
else
return false;
}
/**************************************************************************
* 功能: 对图像进行单点化处理
* 参数: image0为原图形,image1为处理的结果图像
* w、h为图像的宽和高
* K为阈值
**************************************************************************/
void ToOnePointWide(BYTE* image0, BYTE* image1,
unsigned int w, unsigned int h, int K)
{
//将图像转化为矩阵形式
BYTE** imageBuf0 = CreatImage(image0, w, h);
BYTE** imageBuf1 = CreatImage(image1, w, h);
int x,y,c;
int a;
//依次对源图像的每个像素进行处理
for(y=1; y<h-1; y++)
for(x=1; x<w-1; x++)
{
//如果当前点已经为单点 则在结果图中用黑色标记
if( IsOnePoint(imageBuf0,w,h,x,y,K) )
{
SetPixelXY(imageBuf1,x,y,GetAsh(imageBuf0,x,y) );
}
//如果当前点不是单点 则在结果图中用白色标记
else
{
SetPixelXY(imageBuf1,x,y,0);
}
}
//清理内存
free(imageBuf0);
free(imageBuf1);
}
图11-31为经过二值化处理的图像边缘细化结果,其中a为源图像,b为处理结果。
图11-31 二值化处理后的图像边缘细化结果
本节通过Sobel边缘细化介绍了两种全新的图像增强处理思路,本节内容的重点并不是边缘增强算法本身,而是帮助读者拓宽思路,在数字图像处理中,没有标准化的算法或研究思路,算法创新才是学习的关键。