对于canny算法,这个应用非常广泛算法,我着实想不到什么很好的开场词来介绍它。那么就套用《Learning openCV》中文版中的一段好了:
“在图像边缘检测中,抑制噪声和边缘精确定位是无法同时满足的,一些边缘检测算法通过平滑滤波去除噪声的同事,也增加了边缘定位的不确定性;而提高边缘检测算子对边缘的敏感性的同事,也提高了对噪声的敏感性。canny算子力求在抗噪声干扰和精确定位之间寻求最佳折中方案。”
回顾一下各种边缘检测的算法,正如上述所论。比如说,一阶算子检测边缘,如Robert,虽然对边缘定位精度较高,但是容易丢失边缘,对噪声无抑制能力,又如Sobel,对噪声仅有一点抑制能力,但无法完全排除虚假边缘;又如二阶算子-拉普拉斯算子,对噪声的处理更让人烦恼无比(对噪声响应很高);又或是改进之后的高斯-拉普拉斯算法,虽然经过了高斯模糊,但是这些模糊不能完全去除噪声,没处理的好,反而会影响边缘检测的效果(也就是降低真实边缘的响应)···这种种都说明我们需要一个更牛逼的算法来完成边缘检测的艰巨任务,不用多说,那就是-------------canny算法!
正如上文所说,canny旨在抗噪和精确定位之间寻求最佳折中方案,这就想数学建模中的多约束求最优解问题,那么首先要明确的是我们的约束是什么:
(1) 信噪比准则: 信噪比越高越佳,错误率也就越小,噪声干扰越小;
(2) 定位精度准则:检测出的边缘要尽可能接近真实边缘;
(3) 单边缘响应准备:想拉普拉斯算子对于阶跃边缘的处理,往往会出现两条响应边缘,这是我们不希望出现的;
此处我略掉了一些数学公式(至少我现在看上去没那么有用),我认为明白这些概念就可以了,接下来我将想大家介绍canny处理的步骤,然后再回过头来对照准则,加深理解,也算是弥补理论上的不足吧!
Canny算法的步骤如下:
(1)用高斯滤波器平滑图像;
(2)用一阶偏导的有限差分计算梯度的幅值和方向;
Hx = [ 1, -1; 1, -1 ] Hy = [ -1, -1; 1, 1 ]
设图像数据用 f 表示,则卷积后得到结果:
phiX = f * Hx phiY = f * Hy
以此来计算梯度的幅值和方向:
梯度幅值: sqrt(phiX^2 + phiY^2)
梯度方向: arctan(phiY / phiX)
(3)对梯度幅值进行非极大值抑制。什么是非极大值抑制?从字面上理解,就是对非极大值的数据进行抑制,也可以理解成对非极大值数据排除其是边缘的可能性。那么,“极大值”这个概念如何理解呢?首先,我们要打破常规的思维----极大值往往是运用在数学中的连续函数中,这是一个相对的概念,实际上是指一定区域上的最大值或者最小值。而图像是由一个一个像素点构成,宏观上看就是一个离散的矩阵,那么我们要在矩阵中使用极大值的概念,那么我们首先要划分一个区域,有区域才有极值而言------8领域是最好的选择。
如上图所示,以C点为中心的8领域(即C点为我们要讨论的对象)。首先明确,我们讨论的“极大值”是针对梯度幅值的,并且是在算出来的梯度方向上的梯度幅值极大值!所以,这个问题实际上被描述成:中心点C点的梯度幅值在其梯度方向上且在八领域范围内是否是最大的?否则抑制!
而很容易知道我们算出来的梯度方向应该是0°~360°的,并且是连续的角度。但是我们的八领域提供的是八个固定的离散角度(0°(360°),45°,90°,135°,180°,225°,270°分别对应上图的4,3,2,1,8,7,6,5区域)。所以,要算出梯度方向上的、且在中心点C八领域内的梯度幅值,那么就需要使用插值运算。举个例子来说,对于上图C点,若其梯度幅值算出为30°,要得到该梯度方向上、C点八领域内的梯度幅值,实际上是在区域3与区域4之间进行插值(相对的还有区域7和区域8之间进行插值,此图省略),具体如何插值呢?下文程序处将详细说明!
(4)双阈值算法检测和连接边缘。
首先也要明确,阈值是针对梯度幅值而言的!双阈值言下之意就是设置两个阈值TH,TL,经验上来说TH:TL = 2:1最佳。在高阈值TH的截取下,将得到一高阈值图像,配合步骤(3)中非极大值抑制的结果,于是我们可以得到较为一些边缘曲线。由于双重判断、并且阈值高,判断出来的边缘一般不会存在假边缘,但是正是因为阈值高,会出现边缘间断的现象。此时就要进行基于低阈值的边缘修补。总的来说,双阈值的作用是:
高阈值:判断真边缘的位置在哪里!
低阈值:修补判断出来的真边缘!
具体步骤:
高阈值处理得高阈值图像 -> 搜寻高阈值图像中真边缘端点 -> 基于低阈值修补真边缘端点附近边缘
接下来,懒得啰嗦了,直接上关键程序。程序中有很详细地注释。必要地方我会再做解释:
目标函数:IplImage* UseCanny(IplImage* img, int iSize, double sigma, double Th, double Tl)
输入参数: img ------ 待处理图像
iSize ------ 高斯滤波模板尺寸
sigma ------ 高斯滤波标准差
Th ------ 双阈值中的高阈值
Tl ------ 双阈值中的低阈值
输出参数:处理完的图像
按照步骤,该函数各部分如下:
/*step 1 : 高斯滤波,得到处理好的图像guass
*/ IplImage* MyImg = cvCreateImage(cvGetSize(img), 8, 1);
cvConvertImage(img, MyImg, CV_BGR2GRAY);
IplImage* guass = cvCreateImage(cvGetSize(MyImg), 8, 1);
cvSmooth(MyImg, guass, CV_GAUSSIAN, iSize, iSize);