代码改变世界

图像特征提取:Sobel边缘检测

2013-10-25 11:26  ☆Ronny丶  阅读(25188)  评论(3编辑  收藏  举报

前言

点和线是做图像分析时两个最重要的特征,而线条往往反映了物体的轮廓,对图像中边缘线的检测是图像分割与特征提取的基础。文章主要讨论两个实际工程中常用的边缘检测算法:Sobel边缘检测和Canny边缘检测,Canny边缘检测由于算法复杂将在另一篇文章中单独介绍,文章不涉及太多原理,因为大部分的图像处理书籍都有相关内容介绍,文章主要通过Matlab代码,一步一步具体实现两种经典的边缘检测算法。

Sobel边缘检测

Soble边缘检测算法比较简,实际应用中效率比canny边缘检测效率要高,但是边缘不如Canny检测的准确,但是很多实际应用的场合,sobel边缘却是首选,尤其是对效率要求较高,而对细纹理不太关心的时候。

Soble边缘检测通常带有方向性,可以只检测竖直边缘或垂直边缘或都检测。

所以我们先定义两个梯度方向的系数:

kx=0;ky=1;% horizontal
kx=1;ky=0;% vertical
kx=1;ky=1;% both

然后我们来计算梯度图像,我们知道边缘点其实就是图像中灰度跳变剧烈的点,所以先计算梯度图像,然后将梯度图像中较亮的那一部分提取出来就是简单的边缘部分。

Sobel算子用了一个3*3的滤波器来对图像进行滤波从而得到梯度图像,这里面不再详细描述怎样进行滤波及它们的意义等。

竖起方向的滤波器:y_mask=op = [-1 -2 -1;0 0 0;1 2 1]/8;

水平方向的滤波器:op的转置:x_mask=op’;

定义好滤波器后,我们就开始分别求垂直和竖起方向上的梯度图像。用滤波器与图像进行卷积即可:

bx = abs(filter2(x_mask,a));
by = abs(filter2(y_mask,a));

上面bx为水平方向上的梯度图像,by为垂直方向上的梯度图像。为了更清楚的说明算法过程,下面给出一张示例图像的梯度图像。

原图:

image

竖直方向梯度图像:by

image

水平方向梯度图像:bx

image

而最终的梯度图像为:b = kx*bx.*bx + ky*by.*by;当然这里如果定义了检测方向,就会得到对应上面的图像。

这里值得注意的是为了计算效率并没有对b开平方。而涉及滤波等操作时对图像边界的处理是值得注意的一个地方。我们这里应该将梯度图像的四周1像素点都设置为0。

得到梯度图像后,我们需要的是计算阈值,这是Sobel算法很核心的一部分,也是对效果影响较大的地方,同理讲到canny边缘检测时,用到的双阈值法也是canny算法的核心。

同样这里,我并不太多的介绍算法原理,相关文献可以直接百度。阈值也可以通过自己期待的效果进行自定义阈值,如果没有,则我们计算默认阈值。

scale = 4;
cutoff = scale*mean2(b);
thresh = sqrt(cutoff);

其中mean2函数是求图像所有点灰度的平均值。scale是一个系数。

有了阈值后,我们就可以地得到的梯度图像进行二值化。二值化过程,不做详细说明,遍历图像中的像素点,灰度大于阈值的置为白点,灰度小于阈值的则置为黑点。

下面是示例图像梯度图像二值化后的效果:

image

其实很多介绍Soble算法的文章介绍到这里就结束了,印象中OpenCv的算法也是到此步为止。但是我们注意到上面的边缘图像,边缘较粗,如果我们在做边界跟踪或轮廓提取时,上面图像并不是我们期望的结果。

所以下面要介绍一个很重要的算法,用非极大值抑制算法对边缘进行1像素化。本人在开始的时候也一直以为这里用一个普通的细化算法就可以了,可是总得到到想要的结果,最后查找matlab早期版本的源码才找到方法,其实跟canny算法里原理差不多。

我们需要遍历刚才得到的梯度图像二值化结果b,对于b内的任意一点(i,j),如果满足下面条件,则保持白点,否则置为黑点。条件简单的说即是:如果是竖直边缘,则它的梯度值要比左边和右边的点都大;如果是水平连续,则该点的梯度值要比上边和下边的都大。

bx(i,j)>kx*by(i,j) && b(i,j-1)<b(i,j) && b(i,j+1)<b(i,j)

或者

by(i,j)>ky*bx(i,j) && b(i-1,j)<b(i,j) &&b (i+1,j)<b(i,j)

经过这样的非极大值抑制我们就可以得到比较理想的边缘图像。

image

下面给出利用OpenCV里的一些滤波函数,从新写的一个Sobel边缘检测的函数:

 1 bool Sobel(const Mat& image,Mat& result,int TYPE)
 2 {
 3     if(image.channels()!=1)
 4         return false;
 5     // 系数设置
 6     int kx(0);
 7     int ky(0);
 8     if( TYPE==SOBEL_HORZ ){
 9         kx=0;ky=1;
10     }
11     else if( TYPE==SOBEL_VERT ){
12         kx=1;ky=0;
13     }
14     else if( TYPE==SOBEL_BOTH ){
15         kx=1;ky=1;
16     }
17     else
18         return false;
19 
20     // 设置mask
21     float mask[3][3]={{1,2,1},{0,0,0},{-1,-2,-1}};
22     Mat y_mask=Mat(3,3,CV_32F,mask)/8;
23     Mat x_mask=y_mask.t(); // 转置
24 
25     // 计算x方向和y方向上的滤波
26     Mat sobelX,sobelY;
27     filter2D(image,sobelX,CV_32F,x_mask);
28     filter2D(image,sobelY,CV_32F,y_mask);
29     sobelX=abs(sobelX);
30     sobelY=abs(sobelY);
31     // 梯度图
32     Mat gradient=kx*sobelX.mul(sobelX)+ky*sobelY.mul(sobelY);
33 
34     // 计算阈值
35     int scale=4;
36     double cutoff=scale*mean(gradient)[0];
37 
38     result.create(image.size(),image.type());
39     result.setTo(0);
40     for(int i=1;i<image.rows-1;i++)
41     {
42         float* sbxPtr=sobelX.ptr<float>(i);
43         float* sbyPtr=sobelY.ptr<float>(i);
44         float* prePtr=gradient.ptr<float>(i-1);
45         float* curPtr=gradient.ptr<float>(i);
46         float* lstPtr=gradient.ptr<float>(i+1);
47         uchar* rstPtr=result.ptr<uchar>(i);
48         // 阈值化和极大值抑制
49         for(int j=1;j<image.cols-1;j++)
50         {
51             if( curPtr[j]>cutoff && (
52                 (sbxPtr[j]>kx*sbyPtr[j] && curPtr[j]>curPtr[j-1] && curPtr[j]>curPtr[j+1]) ||
53                 (sbyPtr[j]>ky*sbxPtr[j] && curPtr[j]>prePtr[j] && curPtr[j]>lstPtr[j]) ))
54                 rstPtr[j]=255;
55         }
56     }
57 
58     return true;
59 }