原标题:【OpenCV+Python】Canny边缘检测&图像金字塔

Canny边缘检测

上一个教程 《三种算子:Sobel、Scharr、拉普拉斯》 中我们谈到的拉普拉斯算子本质上属于图像的边缘检测,但是我们同时也看到,拉普拉斯算子有一定的局限性,对于复杂图像的边缘检测有些力不从心,本次我们将介绍一个在OpenCV中有着决定性地位的边缘检测——Canny算法。

我们在前面已经了解过,边缘检测算法通常有四个步骤:

(1)滤波:边缘检测算法主要是基于图像强度的一阶和二阶导数,但导数的计算对噪声很敏感,因此必须使用滤波器来改善与噪声有关的边缘检测器的性能.需要指出,大多数滤波器在降低噪声的同时也导致了边缘强度的损失,因此,增强边缘和降低噪声之间需要折衷。

(2)增强:增强边缘的基础是确定图像各点邻域强度的变化值。增强算法可以将邻域(或局部)强度值有显著变化的点突显出来。边缘增强一般是通过计算梯度幅值来完成的。

(3)检测:在图像中有许多点的梯度幅值比较大,而这些点在特定的应用领域中并不都是边缘,所以应该用某种方法来确定哪些点是边缘点。最简单的边缘检测判据是梯度幅值阈值判据。

(4)定位:如果某一应用场合要求确定边缘位置,则边缘的位置可在子像素分辨率上来估计,边缘的方位也可以被估计出来。

在边缘检测算法中,前三个步骤用得十分普遍。这是因为大多数场合下,仅仅需要边缘检测器指出边缘出现在图像某一像素点的附近,而没有必要指出边缘的精确位置或方向。边缘检测误差通常是指边缘误分类误差,即把假边缘判别成边缘而保留,而把真边缘判别成假边缘而去掉。边缘估计误差是用概率统计模型来描述边缘的位置和方向误差的.我们将边缘检测误差和边缘估计误差区分开,是因为它们的计算方法完全不同,其误差模型也完全不同。

▼ Canny边缘检测算法

JohnCanny于1986年提出Canny算子,它与Marr(LoG)边缘检测方法类似,也属于是先平滑后求导数的方法。本节对根据上述的边缘检测过程对Canny检测算法的原理进行介绍。

01

灰度化

Canny算法通常处理的图像为灰度图,因此如果摄像机获取的是彩色图像,那首先就得进行灰度化。对一幅彩色图进行灰度化,就是根据图像各个通道的采样值进行加权平均。

以RGB格式的彩图为例,通常灰度化采用的方法主要有:

方法1 Gray=(R+G+B)/3

方法2 Gray=0.299R+0.587G+0.114B(这种参数考虑到了人眼的生理特点)

*注意:

至于其他格式的彩色图像,可以根据相应的转换关系转为RGB然后再进行灰度化。

在编程时要注意图像格式中RGB的顺序通常为BGR。

02

高斯滤波

令f(x,y)表示数据(输入源数据),G(x,y)表示二维高斯函数(卷积操作数),fs(x,y)为卷积平滑后的图像。

python opencv 二值图像边缘检测 python边缘检测算法_图像金字塔

python opencv 二值图像边缘检测 python边缘检测算法_高斯金字塔_02

Guess过程:

用坐标点(x,y)表示一个3x3的邻域,设中心点的坐标为(0,0),相邻的点以此类推。

python opencv 二值图像边缘检测 python边缘检测算法_图像金字塔_03

计算权重矩阵。设定方差σ2=0.64的值,将对应各个坐标点(x,y)带入二维高斯公式G(x,y)中,得到一个权重矩阵,归一化权重矩阵(矩阵中各个点除以权重之和),得到标准的权重矩阵,即高斯模板。

python opencv 二值图像边缘检测 python边缘检测算法_边缘检测_04

计算高斯模糊。设在一幅图像中的3×3区域内,用各像素点的灰度值乘以对应点的权重。

python opencv 二值图像边缘检测 python边缘检测算法_边缘检测_05

python opencv 二值图像边缘检测 python边缘检测算法_边缘检测_06

将得到的9个值求和,就是中心点的高斯模糊值。

python opencv 二值图像边缘检测 python边缘检测算法_高斯金字塔_07

简单来说就是使用Guess模板在原始图像中进行移位、相乘、相加的过程。

03

计算幅值图像、角度图像

求变化率时,对于一元函数,即求导;对于二元函数,求偏导。数字图像处理中,用一阶有限差分近似求取灰度值的梯度值(变化率)。

例:计算一点x方向和y方向的梯度幅值和方向

python opencv 二值图像边缘检测 python边缘检测算法_python图像边缘检测_08

上图中显示一段直的边缘线段放大后一部分,每个方块代表一个像素点,用一个方框强调点处边缘的幅值和方向。令灰色像素值为0,白色像素值为1。

如图关于一点为中心的3×3邻域,使用Prewittt卷积模板进行计算:

python opencv 二值图像边缘检测 python边缘检测算法_图像金字塔_09

根据x方向和y方向的卷积模板,可知,在3x3的邻域中从底部一行像素值减去顶部一行的像素,得到x方向的偏导数(梯度);同样,从右边一列像素值减去左边一列的像素,得到y方向的偏导数。

x方向的梯度:

y方向的梯度:

由此,可以得到该点梯度的幅值和方向:

python opencv 二值图像边缘检测 python边缘检测算法_边缘检测_10

如下图表示了中心点的梯度向量、方位角以及边缘方向。(任一点的边缘与梯度向量正交):

python opencv 二值图像边缘检测 python边缘检测算法_python图像边缘检测_11

Canny算子的卷积模板为:

python opencv 二值图像边缘检测 python边缘检测算法_图像金字塔_12

python opencv 二值图像边缘检测 python边缘检测算法_图像金字塔_13

04

对幅值图像进行非极大值抑制

首先将角度划分成四个方向范围:水平(0°)、−45°、垂直(90°)、+45°,如下图。

python opencv 二值图像边缘检测 python边缘检测算法_高斯金字塔_14

接着讨论对3x3区域的四个基本边缘方向进行非极大值抑制。

python opencv 二值图像边缘检测 python边缘检测算法_python图像边缘检测_15

做法:若中心点(即:访问点)在沿其方向上邻域的梯度幅值最大,则保留;否则,抑制。

05

双阈值检测和连接边缘

选取系数TH和TL,比率为2:1或3:1。(一般取TH=0.3或0.2,TL=0.1)

取出非极大值抑制后的图像中的最大梯度幅值,定义高低阈值。即:TH×Max,TL×Max(当然可以自己给定)。

将小于低阈值的点抛弃,赋0;将大于高阈值的点立即标记(这些点就是边缘点),赋1。

将小于高阈值,大于低阈值的点使用8连通区域确定(即:只有与TH像素连接时才会被接受,成为边缘点,赋 1)。

现在我们来看看函数原型:

edge=cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient ]]])

(1)必要参数:

第一个参数是需要处理的原图像,该图像必须为单通道的灰度图。

第二个参数是阈值1。

第三个参数是阈值2。

其中较大的阈值2用于检测图像中明显的边缘,但一般情况下检测的效果不会那么完美,边缘检测出来是断断续续的。所以这时候用较小的第一个阈值用于将这些间断的边缘连接起来。

(2)可选参数

apertureSize就是Sobel算子的大小。

L2gradient参数是一个布尔值,如果为真,则使用更精确的L2范数进行计算(即两个方向的倒数的平方和再开放),否则使用L1范数(直接将两个方向导数的绝对值相加)。

现在我们来看代码:

import cv2
import numpy as np
img = cv2.imread( "credit_card_.png",0) 
dst = cv2.Canny(img,150,200) 
cv2.imshow( "img",img) 
cv2.imshow( "res",dst)
cv2.waitKey(0)
cv2.destroyAllWindows

python opencv 二值图像边缘检测 python边缘检测算法_python图像边缘检测_16

图像金字塔

在上文中,我们谈到了关于图像Canny边缘检测算子,同时也是OpenCV中应用最为广泛的边缘检测算法。

现在我们来谈谈图像金字塔,它的本质是对图像进行放缩变换。一般情况下,我们要处理是一副具有固定分辨率的图像。但是有些情况下,我们需要对同一图像的不同分辨率的子图像进行处理。比如,我们要在一幅图像中查找某个目标,比如脸,我们不知道目标在图像中的尺寸大小。这种情况下,我们需要创建一组图像,这些图像是具有不同分辨率的原始图像。我们把这组图像叫做图像金字塔(简单来说就是同一图像的不同分辨率的子图集合)。如果我们把最大的图像放在底部,最小的放在顶部,看起来像一座金字塔,故而得名图像金字塔。

图像金字塔最初用于机器视觉和图像压缩,一幅图像的金字塔是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到达到某个终止条件才停止采样。金字塔的底部是待处理图像的高分辨率表示,而顶部是低分辨率的近似。我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低。

python opencv 二值图像边缘检测 python边缘检测算法_图像金字塔_17

有两类图像金字塔:高斯金字塔和拉普拉斯金字塔。

高斯金字塔(Gaussianpyramid):用来向下采样,主要的图像金字塔。

拉普拉斯金字塔(Laplacianpyramid):用来从金字塔低层图像重建上层未采样图像,在数字图像处理中也即是预测残差,可以对图像进行最大程度的还原,配合高斯金字塔一起使用。

两者的简要区别:高斯金字塔用来向下降采样图像,而拉普拉斯金字塔则用来从金字塔底层图像中向上采样重建一个图像。要从金字塔第i层生成第i+1层(我们表示第i+1层为G_i+1),我们先要用高斯核对G_1进行卷积,然后删除所有偶数行和偶数列。当然的是,新得到图像面积会变为源图像的四分之一。按上述过程对输入图像G_0执行操作就可产生出整个金字塔。

当图像向金字塔的上层移动时,尺寸和分辨率就降低。OpenCV中,从金字塔中上一级图像生成下一级图像的可以用PryDown。而通过PryUp将现有的图像在每个维度都放大两遍。

图像金字塔中的向上和向下采样分别通过OpenCV函数pyrUp和pyrDown实现。

对图像向上采样:pyrUp函数。

对图像向下采样:pyrDown函数。

这里的向下与向上采样,是对图像的尺寸而言的(和金字塔的方向相反),向上就是图像尺寸加倍,向下就是图像尺寸减半。而如果我们按上图中演示的金字塔方向来理解,金字塔向上图像其实在缩小,这样刚好是反过来了。

但需要注意的是,PryUp和PryDown不是互逆的,即PryUp不是降采样的逆操作。这种情况下,图像首先在每个维度上扩大为原来的两倍,新增的行(偶数行)以0填充。然后给指定的滤波器进行卷积(实际上是一个在每个维度都扩大为原来两倍的过滤器)去估计“丢失”像素的近似值。

PryDown是一个会丢失信息的函数。为了恢复原来更高的分辨率的图像,我们要获得由降采样操作丢失的信息,这些数据就和拉普拉斯金字塔有关系了。

▼ 高斯金字塔

高斯金字塔是通过高斯平滑和亚采样获得一些列下采样图像,也就是说第K层高斯金字塔通过平滑、亚采样就可以获得K+1层高斯图像,高斯金字塔包含了一系列低通滤波器,其截至频率从上一层到下一层是以因子2逐渐增加,所以高斯金字塔可以跨越很大的频率范围。

金字塔的图像如下:

python opencv 二值图像边缘检测 python边缘检测算法_python图像边缘检测_18

另外,每一层都按从下到上的次序编号,层级G_i+1(表示为G_i+1尺寸小于第i层G_i)。

(1)对图像的向下取样

为了获取层级为G_i+1的金字塔图像,我们采用如下方法:

对图像G_i进行高斯内核卷积;

将所有偶数行和列去除。

得到的图像即为G_i+1的图像,显而易见,结果图像只有原图的四分之一。通过对输入图像G_i(原始图像)不停迭代以上步骤就会得到整个金字塔。同时我们也可以看到,向下取样会逐渐丢失图像的信息。以上就是对图像的向下取样操作,即缩小图像。

我们来看函数原型:

cv2. pyrDown (src, dst=None, dstsize=None, borderType=None)

src:表示输入图像。

dst:表示输出图像。

dstsize:表示输出图像的大小。

borderType:表示图像边界的处理方式。

我们来看源代码:

import cv2
import numpy as np
img = cv2.imread( "cat.jpg") 
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) 
dst = cv2.pyrDown(img) 
cv2.imshow( "img",img) 
cv2.imshow( "res",dst)
cv2.waitKey(0)
cv2.destroyAllWindows

python opencv 二值图像边缘检测 python边缘检测算法_图像金字塔_19

(2)对图像的向上取样

同样的,我们来看函数原型:

cv2. pyrUp (src, dst=None, dstsize=None, borderType=None)

src:表示输入图像。

dst:表示输出图像。

dstsize:表示输出图像的大小。

borderType:表示图像边界的处理方式。

直接来看代码:

import cv2
import numpy as np
img = cv2.imread( "cat.jpg") 
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) 
dst = cv2.pyrUp(img) 
cv2.imshow( "img",img) 
cv2.imshow( "res",dst)
cv2.waitKey(0)
cv2.destroyAllWindows

python opencv 二值图像边缘检测 python边缘检测算法_边缘检测_20

python opencv 二值图像边缘检测 python边缘检测算法_高斯金字塔_21

具体的效果大家可以自己实验。

▼ 拉普拉斯金字塔

与其说是拉普拉斯金字塔,不如叫做残差金字塔。因为我们刚刚不是说了吗,图像的降采样和上采样不是可逆的。那么拉普拉斯金字塔的构建方法如下:

假设高斯金字塔就3个 level(0、1、2),那么,我们先把高斯金字塔的最高层(最小的那个)上采样(放大)。我们知道,上采样之后和原金字塔第1个level相比是有信息丢失的,我们就把原高斯金字塔的第1个level的图像和这个经过第2个level图像上采样得到的图像相减(第2个level图像上采样之后就和原高斯金字塔第1个level图像的大小一样了),相减就会得到一个残差,这就是拉普拉斯金字塔的最高级(下图中就应该是level 1)。

同样地,把原高斯金字塔的level 0的图像 和 原高斯金字塔level 1经过上采样得到的图像相减,就得到了拉普拉斯金字塔的第0层。至此,下图所示的拉普拉斯金字塔就构建完毕了。

值得注意的是:如果一幅图像的高斯金字塔有N+1层,那么其对应的拉普拉斯金字塔就有N层。

python opencv 二值图像边缘检测 python边缘检测算法_python图像边缘检测_22

拉普拉斯金字塔的整个计算过程如上图所示:

左上角的图片为原始图片。

对原始图像进行高斯平滑。

执行一次下采样,图像变为原来的1/4。

执行一次上采样,图像变为原图的大小。

再次执行高斯模糊。

用原图像减去高斯模糊后的图像,得到拉普拉斯图像。

我们来看代码实战:

import cv2
import numpy as np
img = cv2.imread( "cat.jpg") 
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) 
down = cv2.pyrDown(img) 
dst = cv2.pyrUp(down)
res = img - dst 
cv2.imshow( "img",img) 
cv2.imshow( "res",res)
cv2.waitKey(0)
cv2.destroyAllWindows

python opencv 二值图像边缘检测 python边缘检测算法_python图像边缘检测_23

图像金字塔实际上是有着很大的用途的,包括图像融合,但这些我们将在后面的项目实战中介绍到。