OpenCV(五)之图像边缘检测
Edge detection系列之Canny边缘检测
理论:
Canny 的目标是找到一个最优的边缘检测算法,最优边缘检测的含义是:
(1)最优检测:算法能够尽可能多地标识出图像中的实际边缘,漏检真实边缘的概率和误检非边缘的概率都尽可能小;
(2)最优定位准则:检测到的边缘点的位置距离实际边缘点的位置最近,或者是由于噪声影响引起检测出的边缘偏离物体的真实边缘的程度最小;
(3)检测点与边缘点一一对应:算子检测的边缘点与实际边缘点应该是一一对应。
为了满足这些要求 Canny 使用了变分法(calculus of variations),这是一种寻找优化特定功能的函数的方法。最优检测使用四个指数函数项表示,但是它非常近似于高斯函数的一阶导数。
步骤:
Canny边缘检测算法可以分为以下5个步骤:
1.应用高斯滤波来平滑图像,目的是去除噪声
2.找寻图像的强度梯度(intensity gradients)
3.应用非最大抑制(non-maximum suppression)技术来消除边误检(本来不是但检测出来是)
4.应用双阈值的方法来决定可能的(潜在的)边界
5.利用滞后技术来跟踪边界
Edge detection-高斯滤波
在之前的学习中,知道:图像的噪声,会影响到图像的处理,例如在本节中要做的边缘检测,需要计算每个像素点的梯度强度和方向,需要用高斯滤波做一次图像的平滑处理。
滤波操作-高斯滤波
function
def GaussianBlur(src, ksize, sigmaX, dst=None, sigmaY=None, borderType=None):
使用高斯滤波器平滑图像。
该函数将源图像与指定的高斯内核进行卷积,支持就地过滤。
参数
SRC 输入图像; 图像可以有任意数量的通道,这些通道是独立处理的,但深度应该是CV_8U,CV_16U,CV_16S,CV_32F或CV_64F。
DST 输出与src相同大小和类型的图像。
ksize 高斯核大小。ksize.width和ksize.height可以不同,但它们都必须为正数且为奇。
或者,它们可以是零,然后它们是从sigma计算得到。
sigmaX X方向的高斯核标准偏差。
sigmaY Y方向的高斯核标准偏差; 如果sigmaY为零,则将其设置为等于sigmaX;
如果两个sigma均为零,则分别从ksize.width和ksize.height计算(详见cv :: getGaussianKernel);
为了完全控制结果,无论将来可能修改所有这些语义,建议指定所有ksize,sigmaX和sigmaY。
borderType 像素外推法,参见cv :: BorderTypes
滤波操作-高斯内核-getGaussianKernel()
function
def getGaussianKernel(ksize, sigma, ktype=None):
返回高斯滤波器系数。
该函数计算并返回高斯滤波器系数的?????×1矩阵:
其中两个生成的内核可以传递给sepFilter2D。
这些函数自动识别平滑内核(一个权重总和等于1的对称内核)并相应地处理它们。 您也可以使用更高级别的GaussianBlur。
Edge detection-Sobel算子
用Sobel算子来计算图像中每个像素点的梯度强度和方向。
梯度计算-梯度强度
function:
def Sobel(src, ddepth, dx, dy, dst=None, ksize=None, scale=None, delta=None, borderType=None):
使用扩展的Sobel算子计算第一,第二,第三或混合图像导数。
在除1之外的所有情况下,使用?????×?????可分内核来计算导数。
当?????=?时,使用3×1或1×3内核(即不进行高斯平滑),ksize = 1时只能用于第一个或第二个x或y衍生物。
还有特殊值ksize = CV_SCHARR(-1),它对应于3×3 Scharr滤波器,可以提供比3×3 Sobel更准确的结果。
Scharr aperture 是:
对于x-导数,或对y-导数进行转置。
该函数通过将图像与适当的内核进行卷积来计算图像导数:
Sobel算子结合了高斯平滑和微分,因此结果或多或少地抵抗噪声。
大多数情况下,使用(xorder = 1,yorder = 0,ksize = 3)或(xorder = 0,yorder = 1,ksize = 3),
调用函数以计算第一个x或y图像导数。
第一种情况对应于以下内核:
第二种情况对应于以下内核:
参数
SRC 输入图像。
DST 输出与src相同大小和相同通道数的图像。
ddepth 输出图像深度,见#combinations ; 在8位输入图像的情况下,它将导致截断的导数。
DX 导数x的顺序。
DY 导数y的顺序。
ksize 扩展的Sobel内核的大小; 它必须是1,3,5或7。
scale 计算导数值的可选比例因子; 默认情况下,不应用缩放(有关详细信息,请参阅# getDerivKernels)。
delta 在将结果存储在dst之前添加到结果中的可选delta值。
borderType 像素外推法,参见#BorderTypes
梯度计算-梯度方向
用Sobel算子得到相对应的X,Y方向之后,计算其梯度方向,其中G表示梯度的方向,则G的计算公式为:
G
=
G
x
2
+
G
y
2
G = \sqrt {G_x^2+G_y^2}
G=Gx2+Gy2
θ
=
arctan
(
G
y
G
x
)
\theta = \arctan (\frac{G_y}{G_x})
θ=arctan(GxGy)
Edge detection-非极大值抑制
应用非极大值抑制,以消除边缘检测带来的杂散响应,在应用Sobel算子计算得到像素点的梯度强度和方向后,要突出显示“边界值”最为明显的,这一步的目的是将模糊(blurred)的边界变得清晰(sharp)。
通俗的讲,就是保留了每个像素点上梯度强度的极大值,而删掉其他的值。
非极大值抑制-线性插值法
非极大值抑制-近似方向
对于每个像素点,进行如下操作:
a) 将其梯度方向近似为以下值中的一个(0,45,90,135,180,225,270,315)(即上下左右和45度方向,共8个方向)
b) 比较该像素点,和其梯度方向正负方向的像素点的梯度强度
c) 如果该像素点梯度强度最大则保留,否则抑制(删除,即置为0)
为了更好的解释这个概念,看下图。
图中的数字代表了像素点的梯度强度,箭头方向代表了梯度方向。以第二排第三个像素点为例,由于梯度方向向上,则将这一点的强度(7)与其上下两个像素点的强度(5和4)比较,由于这一点强度最大,则保留。
可以想象,边界处的梯度方向总是指向垂直于边界的方向,即最后会保留一条边界处最亮的一条细线。
Edge detection-双阈值检测
应用双阈值检测来确定真实的和潜在的边缘,再对边缘值进行一次区间筛选。
经过非极大抑制后图像中仍然有很多噪声点。Canny算法中应用了一种叫双阈值的技术。即设定一个阈值上界和阈值下界(opencv中通常由人为指定的),图像中的像素点如果大于阈值上界则认为必然是边界(称为强边界,strong edge),小于阈值下界则认为必然不是边界,两者之间的则认为是候选项(称为弱边界,weak edge),需进行进一步处理。
比较 | 边界 | 舍弃 |
---|---|---|
梯度值>maxVal | 是 | 否 |
minVal<梯度值<maxVal | 是 | 否 |
梯度值<maxVal | 否 | 是 |
Edge detection-边界跟踪
和强边界相连的弱边界认为是边界,其他的弱边界则被抑制。
较高的亮度梯度比较有可能是边缘,但是没有一个确切的值来限定多大的亮度梯度是边缘多大又不是,所以 Canny 使用了滞后阈值。
滞后阈值(Hysteresis thresholding)需要两个阈值,即高阈值与低阈值。假设图像中的重要边缘都是连续的曲线,这样我们就可以跟踪给定曲线中模糊的部分,并且避免将没有组成曲线 的噪声像素当成边缘。所以我们从一个较大的阈值开始,这将标识出我们比较确信的真实边缘,使用前面导出的方向信息,我们从这些真正的边缘开始在图像中跟踪 整个的边缘。在跟踪的时候,我们使用一个较小的阈值,这样就可以跟踪曲线的模糊部分直到我们回到起点。
一旦这个过程完成,我们就得到了一个二值图像,每点表示是否是一个边缘点。
一个获得亚像素精度边缘的改进实现是在梯度方向检测二阶方向导数的过零点,它在梯度方向的三阶方向导数满足符号条件。
滞后阈值也可以用于亚像素边缘检测。
Edge detection-Canny函数
function:
def Canny(image, threshold1, threshold2, edges=None, apertureSize=None, L2gradient=None):
使用Canny算法在图像中查找边缘。
该函数在输入图像图像中查找边缘,并使用Canny算法在输出图像边缘标记它们。
threshold1和threshold2之间的最小值用于边缘链接,最大值用于查找强边的初始段。
参数
image 输入单通道图像(可以是彩色图像)对于多通道的图像可以用cvCvtColor()修改。
threshold1 第一个阈值
threshold2 第二个阈值
edges 输出的边缘图像 ,也是单通道的,但是是黑白的
apertureSize Sobel算子内核大小
L2gradient 一个标志,表示更准确的L2范数为:
L 2 = ( d I d x 2 ) + ( d I d y 2 ) L_2=\sqrt {(\frac{dI}{dx}^2)+(\frac{dI}{dy}^2)} L2=(dxdI2)+(dydI2)
应该用来计算图像梯度幅度(L2gradient = true),或者是否默认为L1范数:
L 1 = ∣ d I d x ∣ + ∣ d I d y ∣ L_1 = |\frac{dI}{dx}|+|\frac{dI}{dy}| L1=∣dxdI∣+∣dydI∣
就够了(L2gradient = false)。
代码实现:
#导入工具包
import cv2
import numpy as np
#定义一个函数,用来输出图像
def cv_show(name,img):
cv2.imshow(name,img)
cv2.waitKey()
cv2.destroyAllWindows()
#读取一张图像(灰度图)
img = cv2.imread('lena.jpg',cv2.IMREAD_GRAYSCALE)
cv_show('img',img)
v1 = cv2.Canny(img,80,150) #在双阈值检测中设置为80和150,初始阈值较大,会使细节强化
v2 = cv2.Canny(img,50,100) #在双阈值检测中设置为50和100,初始阈值较小,检测越多边界
res = np.hstack((v1,v2))
cv_show('res',res)
#再导入一张图像,做比较
img = cv2.imread('car.png',cv2.IMREAD_GRAYSCALE)
v1 = cv2.Canny(img,80,150) #在双阈值检测中设置为80和150,初始阈值较大,会使细节强化
v2 = cv2.Canny(img,50,100) #在双阈值检测中设置为50和100,初始阈值较小,检测越多边界
res = np.hstack((v1,v2))
cv_show('res',res)
结论:
当双阈值的minVal越小,检测到越多的边缘,或者说过滤留下更多边缘,能看到更多疑似边缘信息(包括噪音)
当双阈值的minVal越大,检测到越少的边缘,或者说过滤舍弃更多边缘,能看到边缘的细节强化
END