Opencv(python)图像梯度和边缘检测算法

1.图像梯度

 

        图像梯度计算的是图像的边缘信息 ,图像梯度计算的是图像变化的速度。对于图像的边缘部分,其灰度值变化较大,梯度值也较大,对于图像中比较平滑的部分,其灰度值变化较小,相应的梯度值也较小。图像梯度计算需要求导数,但是图像梯度一般通过计算像素值的差来得到梯度的近似值(近似导数值)。

2.图像梯度计算方式

倒数其实是求解图像的像素灰度值的变化率,在数学上可以通过求倒数实现。可以分别原图像G对x方向和y方向求导数(假设灰度变化是连续的)

Gx和Gy分别隐含了x和y方向的灰度变化信息,也就隐含了边缘信息。如果要在同一图像上包含两个方向的边缘信息,可以用一个值表示,原图像的梯度向量Gxy为(Gx,Gy),梯度向量的大小和方向可以用下面两个式子计算。

        实际上,图像矩阵是离散的。连续函数求变化率用的是导数,而离散函数求变化率用的是差分。差分的概念很容易理解,就是用相邻两个数的差来表示变化率

下面公式是向后差分

x方向的差分:Gx(n,y) = G(n,y)-G(n-1,y)

y方向的差分:Gy(x,n) = G(x,n)-G(x,n-1)

 实际计算图像导数时,是通过原图像和一个算子进行卷积来完成的(这种方法是求图像的近似导数)。

最简单的求图像导数的算子是 Prewitt算子 :

x方向的Prewitt算子为

y方向的Prewitt算子为

Opencv(python)图像梯度和边缘检测算法_第1张图片

原图像和一个算子进行卷积的大概过程如下,如果图像矩阵中一块区域为

那么x5处的x方向的导数是,将x方向算子的中心和x5重合,然后对应元素相乘再求和,即x5处的x方向导数为x3+x6+x9-x1-x4-x7,对矩阵中所有元素进行上述计算,就是卷积的过程。因此,利用原图像和x方向Prewitt算子进行卷积就可以得到图像的x方向导数矩阵Gx,利用原图像和y方Prewitt算子进行卷积就可以得到图像的y方向导数矩阵Gy。利用公式

就可以得到图像的梯度矩阵Gxy,这个矩阵包含图像x方向和y方向的边缘信息。

3.边缘类型

       边缘检测的目的是标识数字图像中亮度变化明显的点。图像属性中的显著变化通常反映了属性的重要事件和变化。其中包括“深度上的不连续”、“表面方向不连续”、“物质属性变化”和“场景照明变化。

        边缘类型:简单分为4种类型,阶跃型、屋脊型、斜坡型、脉冲型,其中阶跃型和斜坡型是类似的,只是变化的快慢不同。下图是阶跃型和屋脊型示意图。

在这里插入图片描述

 4.边缘检测算子分类
4.1一阶导数的边缘算子

        通过模板作为核与图像的每个像素点做卷积和运算,然后选取合适的阈值来提取图像的边缘。常见的有Roberts算子、Sobel算子和Prewitt算子。

4.1.1Roberts算子

(1)原理

        Roberts算子又称为交叉微分算法,它是基于交叉差分的梯度算法,通过局部差分计算检测边缘线条。常用来处理具有陡峭的低噪声图像,当图像边缘接近于正45度或负45度时,该算法处理效果更理想。其缺点是对边缘的定位不太准确,提取的边缘线条较粗。oberts算子的模板分为水平方向和垂直方向,如下式所示,从其模板可以看出,Roberts算子能较好的增强正负45度的图像边缘。例如,下面给出Roberts算子的模板,在像素点P5处  和  方向上的梯度大小  和  分别计算为:

 (2)算法实现

        在Python中,Roberts算子主要通过numpy定义模板,使用OpenCV的 filter2D() 函数实现边缘提取。该函数主要是利用内核实现对图像的卷积运算。filter2D() 函数用法如下所示:
 

dst = filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]])

其中,参数:

src 表示输入图像;

dst 表示输出的边缘图,其大小和通道数与输入图像相同;

ddepth 表示目标图像所需的深度;

kernel 表示卷积核,一个单通道浮点型矩阵;

anchor 表示内核的基准点,其默认值为 (-1,-1),位于中心位置;

delta 表示在储存目标图像前可选的添加到像素的值,默认值为0;

borderType 表示边框模式。

实现显函数

import cv2
import numpy as np
import matplotlib.pyplot as plt
# 用来正常显示中文标签
plt.rcParams['font.sans-serif'] = ['SimHei']

img = cv2.imread("test.jpg")
img_RGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) #转成RGB 方便后面显示
# 灰度化处理图像
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 自定义Roberts算子
kernelx = np.array([[-1, 0], [0, 1]], dtype=int)
kernely = np.array([[0, -1], [1, 0]], dtype=int)
x = cv2.filter2D(grayImage, cv2.CV_16S, kernelx)
y = cv2.filter2D(grayImage, cv2.CV_16S, kernely)
# 转uint8
absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)
#按照相同的权重相加
Roberts = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
# 显示图形
plt.subplot(121),plt.imshow(img_RGB),plt.title('原始图像'), plt.axis('off') #坐标轴关闭
plt.subplot(122),plt.imshow(Roberts, cmap=plt.cm.gray ),plt.title('Roberts算子'), plt.axis('off')
plt.show()

Opencv(python)图像梯度和边缘检测算法_第2张图片

4.1.2 Prewitt算子

(1)算法原理

        Prewitt算子是一种图像边缘检测的微分算子,其原理是利用特定区域内像素灰度值产生的差分实现边缘检测。由于Prewitt算子采用 3*3 模板对区域内的像素值进行计算,而Robert算子的模板为 2*2,故Prewitt算子的边缘检测结果在水平方向和垂直方向均比Robert算子更加明显。Prewitt算子适合用来识别噪声较多、灰度渐变的图像,其计算公式如下所示(以P5作为卷积核心):

          

Opencv(python)图像梯度和边缘检测算法_第3张图片

(2)编程实现

        在Python中,Prewitt算子的实现过程与Roberts算子比较相似。通过Numpy定义模板,再调用OpenCV的filter2D() 函数实现对图像的卷积运算,最终通过 convertScaleAbs() 和 addWeighted() 函数实现边缘提取。filter2D() 函数用法如下所示:

dst = filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]])
其中,参数:
src 表示输入图像;
dst 表示输出的边缘图,其大小和通道数与输入图像相同;
ddepth 表示目标图像所需的深度;
kernel 表示卷积核,一个单通道浮点型矩阵;
anchor 表示内核的基准点,其默认值为(-1,-1),位于中心位置;
delta 表示在储存目标图像前可选的添加到像素的值,默认值为0;
borderType 表示边框模式。

 4.1.3

(1)算法原理

        Sobel算子是一种离散的微分算子,该算子结合了高斯平滑微分求导运算。该算子利用局部差分寻找边缘,计算所得的是一个梯度的近似值。该算子用于计算图像明暗程度近似值,根据图像边缘旁边明暗程度把该区域内超过某个数的特定点记为边缘。Sobel算子在Prewitt算子的基础上增加了权重的概念,认为相邻点的距离远近对当前像素点的影响是不同的,距离越近的像素点对应当前像素的影响越大,从而实现图像锐化并突出边缘轮廓

Sobel算子根据像素点上下、左右邻点灰度加权差,在边缘处达到极值这一现象检测边缘。对噪声具有平滑作用,提供较为精确的边缘方向信息。因为Sobel算子结合了高斯平滑和微分求导(分化),因此结果会具有更多的抗噪性,当对精度要求不是很高时,Sobel算子是一种较为常用的边缘检测方法。

        Sobel算子的边缘定位更准确,常用于噪声较多、灰度渐变的图像。其算法模板如下面的公式所示,其中  表示水平方向, 表示垂直方向。

(2)代码实现

dst = Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])
其中,参数:
src 表示输入图像;
dst 表示输出的边缘图,其大小和通道数与输入图像相同;
ddepth 表示目标图像所需的深度,针对不同的输入图像,输出目标图像有不同的深度;
dx 表示  方向上的差分阶数,取值1或 0;
dy 表示  方向上的差分阶数,取值1或0;
ksize 表示Sobel算子的大小,其值必须是正数和奇数;
scale 表示缩放导数的比例常数,默认情况下没有伸缩系数;
delta 表示将结果存入目标图像之前,添加到结果中的可选增量值;
borderType 表示边框模式,更多详细信息查阅BorderTypes。

import cv2 as cv
import matplotlib.pyplot as plt
# 用来正常显示中文标签
plt.rcParams['font.sans-serif'] = ['SimHei']
# 读取图像
img = cv.imread('test.jpg', cv.COLOR_BGR2GRAY)
rgb_img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
# 灰度化处理图像
grayImage = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# Sobel 算子
x = cv.Sobel(grayImage, cv.CV_16S, 1, 0)
y = cv.Sobel(grayImage, cv.CV_16S, 0, 1)
# 转 uint8 ,图像融合
absX = cv.convertScaleAbs(x)
absY = cv.convertScaleAbs(y)
Sobel = cv.addWeighted(absX, 0.5, absY, 0.5, 0)
# 显示图形
titles = ['原始图像', 'Sobel算子']
images = [rgb_img, Sobel]
for i in range(2):
    plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

4.2二阶导数的边缘算子

        依据于二阶导数过零点,常见的有Laplacian 算子,此类算子对噪声敏感

4.2.1拉普拉斯(Laplacian) 算子

(1)原理

        Laplacian(拉普拉斯)算子是一种二阶导数算子,其具有旋转不变性,可以满足不同方向的图像边缘锐化(边缘检测)的要求。通常情况下,其算子的系数之和需要为零。拉普拉斯(Laplacian) 算子是 维欧几里德空间中的一个二阶微分算子,常用于图像增强领域和边缘提取。它通过灰度差分计算邻域内的像素。

算法基本流程

1)判断图像中心像素灰度值与它周围其他像素的灰度值,如果中心像素的灰度更高,则提升中心像素的灰度;反之降低中心像素的灰度,从而实现图像锐化操作

2)在算法实现过程中,Laplacian算子通过对邻域中心像素的四方向或八方向求梯度,再将梯度相加起来判断中心像素灰度与邻域内其他像素灰度的关系;

3)最后通过梯度运算的结果对像素灰度进行调整。

Laplacian算子分为四邻域和八邻域,四邻域是对邻域中心像素的四个方向求梯度,八邻域是对八个方向求梯度。

其中,Laplacian算子四邻域模板如下所示:

                           

Opencv(python)图像梯度和边缘检测算法_第4张图片

Laplacian算子的八邻域模板如下所示:

Opencv(python)图像梯度和边缘检测算法_第5张图片

通过Laplacian算子的模板可以发现:

1)当邻域内像素灰度相同时,模板的卷积运算结果为0;

2)当中心像素灰度高于邻域内其他像素的平均灰度时,模板的卷积运算结果为正数;

3)当中心像素的灰度低于邻域内其他像素的平均灰度时,模板的卷积为负数。对卷积运算的结果用适当的衰弱因子处理并加在原中心像素上,就可以实现图像的锐化处理。
 

(2)代码实现

dst = Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
其中,参数:
src 表示输入图像;
dst 表示输出的边缘图,其大小和通道数与输入图像相同;
ddepth 表示目标图像所需的深度;
ksize 表示用于计算二阶导数的滤波器的孔径大小,其值必须是正数和奇数,且默认值为1,更多详细信息查阅getDerivKernels ;
scale 表示计算拉普拉斯算子值的可选比例因子。默认值为1,更多详细信息查阅getDerivKernels;
delta 表示将结果存入目标图像之前,添加到结果中的可选增量值,默认值为0;
borderType 表示边框模式,更多详细信息查阅BorderTypes。

4.3其他边缘算子

前面两类均是通过微分算子来检测图像边缘,还有一种就是Canny算子,其是在满足一定约束条件下推导出来的边缘检测最优化算子。

4.3.1 canny算子

(1)原理

        Canny 边缘检测是一种非常流行的边缘检测算法,是 John F.Canny 在1986 年提出的。它是一个有很多步构成的算法。

Canny边缘检测分为如下几个步骤。

1):去噪

噪声会影响边缘检测的准确性,因此首先要将噪声过滤掉,第一步是使用 5x5 的高斯滤波器去除噪声。

2):计算梯度的幅度与方向。

         对平滑后的图像使用 Sobel 算子计算水平方向和竖直方向的一阶导数(图像梯度)(Gx 和 Gy)。根据得到的这两幅梯度图(Gx 和 Gy)找到边界的梯度和方向,公式如下:

                                                       Edge\_Gradient \; (G) = \sqrt{G_x^2 + G_y^2} \\ Angle \; (\theta) = \tan^{-1} \bigg(\frac{G_y}{G_x}\bigg)

3):非极大值抑制

        在获得梯度的方向和大小之后,应该对整幅图像做一个扫描,去除那些非边界上的点。对每一个像素进行检查,看这个点的梯度是不是周围具有相同梯度方向的点中最大的。如下图所示:

A、B、C三点具有相同的方向(梯度方向垂直于边缘)。判断这三个点是否为各自的局部最大值:如果是,则保留该点;否则,抑制该点(归零)。经过比较判断可知,A点具有最大的局部值,所以保留A点(称为边缘),其余两点(B和C)被抑制(归零)。

        如下图黑色背景的点都是向上方向梯度(水平边缘)的局部最大值。因此,这些点会被保留;其余点被抑制(处理为0)。这意味着,这些黑色背景的点最终会被处理为边缘点,而其他点都被处理为非边缘点。

        Opencv(python)图像梯度和边缘检测算法_第6张图片

4):确定边缘

        现在要确定哪些边界才是真正的边界。这时我们需要设置两个阈值:minVal 和 maxVal。当图像的灰度梯度高于 maxVal 时被认为是真的边界,那些低于 minVal 的边界会被抛弃。如果介于两者之间的话,就要看这个点是否与某个被确定为真正的边界点相连,如果是就认为它也是边界点,如果不是就抛弃。如下图:

A 高于阈值 maxVal 所以是真正的边界点, C 虽然低于 maxVal 但高于minVal 并且与 A 相连,所以也被认为是真正的边界点。而 B 就会被抛弃,因为他不仅低于 maxVal 而且不与真正的边界点相连。所以选择合适的 maxVal和 minVal 对于能否得到好的结果非常重要。在这一步一些小的噪声点也会被除去,因为我们假设边界都是一些长的线段。

参考:


(1)https://blog.csdn.net/vermont_/article/details/108451563

(2)python计算机视觉2:图像边缘检测 - smallpi - 博客园

(3)https://blog.csdn.net/vermont_/article/details/108451563

你可能感兴趣的:(计算机视觉,机器视觉,opencv,python,算法)