边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点。图像属性中的显著变化通常反映了属性的重要事件和变化。这些包括:(i)深度上的不连续;(ii)表面方向不连续;(iii)物质属性变化;(iv)场景照明变化。
边缘检测算子包括:
其中,Canny算子(或者这个算子的变体)是最常用的边缘检测方法。
Canny边缘检测方法是由Canny于1986年提出的一种被公认为效果较好的边缘检测方法。
在介绍Canny算法的具体流程之前,先说一下边缘检测方法的3项指标:
Canny的贡献不仅是提出了一种边缘检测算子,而且还给出了衡量边缘检测算子好坏的准则:
下面我们就重点介绍一下Canny算子的实现过程:平滑处理,梯度检测,非极大值抑制和滞后阈值处理。
所有的边缘都极易受到噪声的干扰,为了防止因噪声所引起的错误的检测结果,有必要应用平滑滤波的方法滤除噪声.高斯滤波方法是最常用的滤波方法,二维图像应用二维高斯函数,它的定义为:
G ( u , v ) = 1 2 π σ 2 e − u 2 + v 2 2 σ 2 G(u, v) = \frac{1}{2\pi\sigma^2}e^{-\frac{u^2+v^2}{2\sigma^2}} G(u,v)=2πσ21e−2σ2u2+v2
式中, σ \sigma σ表示高斯函数的标准差.只要把输入图像与二维高斯函数进行卷积,即可得到平滑处理后的图像.考虑到数字图像为离散化的形式,我们往往把高斯函数转换为离散化的高斯内核模板的形式,如标准差为1.4的模板尺寸为5*5的归一化高斯内核模板为:
K = 1 159 ∗ [ 2 4 5 4 2 4 9 12 9 4 5 12 15 12 5 4 9 12 9 4 2 4 5 4 2 ] K = \frac{1}{159}* \begin{bmatrix} 2&4&5&4&2\\ 4&9&12&9&4\\ 5&12&15&12&5\\ 4&9&12&9&4\\ 2&4&5&4&2 \end{bmatrix} K=1591∗⎣⎢⎢⎢⎢⎡245424912945121512549129424542⎦⎥⎥⎥⎥⎤
梯度是图像灰度值变化剧烈的地方,它可以通过Roberts算子,Prewitt算子,Sobel算子等最简单的模板检测方法得到.常用的是Sobel算子,它是由两个模板组成:
S G X = [ − 1 0 1 − 2 0 2 − 1 0 1 ] S G Y = [ 1 2 1 0 0 0 − 1 − 2 − 1 ] S_{GX} = \begin{bmatrix} -1&0&1\\ -2&0&2\\ -1&0&1 \end{bmatrix} S_{GY} = \begin{bmatrix} 1&2&1\\ 0&0&0\\ -1&-2&-1 \end{bmatrix} SGX=⎣⎡−1−2−1000121⎦⎤SGY=⎣⎡10−120−210−1⎦⎤
把这两个模板分别与图像进行卷积运算,则分别得到水平方向的梯度 G x G_x Gx和垂直方向的梯度 G y G_y Gy.最终的梯度幅值 G G G往往是由欧几里得距离( L 2 L2 L2范数)求得:
G = G x 2 + G y 2 G = \sqrt{G_x^2 + G_y^2} G=Gx2+Gy2
但有时为了简化,梯度值也可由曼哈顿距离( L 1 L1 L1范数)得到:
G = ∣ G x ∣ + ∣ G y ∣ G = |G_x| + |G_y| G=∣Gx∣+∣Gy∣
而梯度幅角 θ \theta θ为:
θ = a r c t a n ( ∣ G y ∣ ∣ G x ∣ ) \theta = arctan(\frac{|G_y|}{|G_x|}) θ=arctan(∣Gx∣∣Gy∣)
由上式得到的角度可以是任意值,但这里我们需要把梯度幅角四舍五入到代表水平方向,垂直方向和2个对角线方向的4个方向上,即 0 ° 0\degree 0°, 45 ° 45\degree 45°, 90 ° 90\degree 90°, 135 ° 135\degree 135°.例如,梯度幅角为 − 22.5 ° -22.5\degree −22.5°~ 22.5 ° 22.5\degree 22.5°时,将被统一设置为 0 ° 0\degree 0°.
这一步骤的目的是使边缘细化.由上一步得到的边缘图像十分模糊,不符合好的边缘检测中的第三个指标,而非极大值抑制可以抑制那些局部不是梯度幅值最大值的边缘,而保留下来的具有局部最大值的像素点正是灰度值变化最剧烈的地方.这里的局部最大值是由在3*3的邻域内的梯度方向上比较梯度值得到的.例如:
还需说明的是,梯度方向的符号与非极大值抑制的结果无关,即无论是东-西方向还是西-东方向,两者是一样的.
由上一步得到的边缘仍有一小部分由于噪声或者颜色变化的影响而不是真正的边缘,这种现象的表现形式是尽管这些边缘的梯度幅值是局部最大值,但与其他边缘比,他们的梯度幅值很小,也就是绝对梯度幅值很小.处理它们也很简单,采用阈值法即可.但Canny采用的双阈值的方法,即设置高,低两个阈值,当梯度幅值大于高阈值时,该边缘为强边缘;当梯度幅值小于低阈值时,该边缘需要被剔除;当梯度值介于高,低阈值之间时,该边缘为弱边缘.
强边缘毫无疑问是需要被保留下来的,而弱边缘则需要采用边缘跟踪的方法来判断其是否为真正的边缘.在弱边缘的3*3的邻域内,如果有强边缘,则说明该弱边缘是属于这个强边缘的,所以需要被保留,否则被剔除掉.
Canny函数的原型为:
void Canny(InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize=3, bool L2gradient=false)
image表示输入图像;
edges表示输出边缘图像;
threshold1和threshold2表示滞后阈值法中所需要的高,低两个阈值;
apertureSize表示孔径尺寸,即Sobel算子的尺寸大小,OpenCV是采用Sobel算子来计算图像的梯度的,该值默认为3;
L2gradient表示在计算梯度幅值时是用L2范数还是用L1范数,该值默认为false,即采用L1范数.
源码解析内容太多,暂时不做详细整理,但是可以列一下源码中的几个特点:
在进行边缘检测之前,我们先应用GaussianBlur函数对图像进行高斯平滑滤波,设它的标准差为1.6,而高斯内核尺寸是由该标准差确定的.
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/imgcodecs/imgcodecs.hpp"
#include
using namespace cv;
using namespace std;
int main(int argc, char** argv){
Mat src, dst, edge;
src = imread("happynewyear.jpeg");
if(!src.data)
return -1;
GaussianBlur(src, dst, Size(0, 0), 1.6); //高斯滤波
//Canny方法,高阈值为60, 低阈值为25
Canny(dst, edge, 25, 60);
namedWindow("Canny", WINDOW_AUTOSIZE);
imshow("Canny", edge);
imwrite("Canny.jpg", edge);
waitKey(0);
return 0;
}
我们看下效果(糟心的2020过去了,祝大家新年快乐!新的一年,继续奋斗!):
有几点需要注意一下:
bool imwrite(const string& filename, InputArray img, const vector& params=vector() )
其中,filename: 需要写入的文件名,会自己创建(eg. imwrite(“1.jpeg”, src)),注意要带后缀;
g++ happynewyear.cpp -o happynewyear `pkg-config opencv --cflags --libs`
注意,pkg-config opencv --cflags --libs外面的引号是键盘左上角1旁边的那个引号.总体来讲,Canny边缘检测相对比较成熟,调用起来也比较方便.如果不是要针对边缘检测做研究和进一步优化的话,直接调用就可以了.如果需要对源码做优化,虽然原理相对比较简单,但是还是要对源码进行解读才可以.