在图像处理中,图像灰度值变化非常剧烈的地方定义为边缘。边缘不随光照和视角的变化而变化。用数学的方式来表达就是在边缘处一阶导数及二阶导数的值最大。
在边缘检测中,最常用的微分方法是梯度法。一幅数字图像f(x,y) ,它在位置 (x,y)的梯度是一个向量,定义为:
表示图像在 处沿着 方向的灰度值变化量,
表示图像在 处沿着 方向的灰度值变化量,
对于梯度而言,它具有大小和方向两个属性。
梯度值
梯度矢量
梯度值反映了当前像素灰度值与相邻像素的差值,梯度值大说明像素灰度值在这一区域陡然变化。梯度值小,则图像灰度值变化平缓。梯度方向反映了图像沿着哪个方向灰度值变化最大。
常用的一阶微分算子有Roberts、Prewitt算子、Sobel算子
Roberts算子
Roberts算子是基于交叉差分的梯度算法,它利用局部差分来检测边缘。对于待检测图像 ,其Roberts算子边缘梯度 为
由数学公式可以得出,Roberts算子能较好的增强正负45度的图像边缘,处理的效果更为理想。
Roberts算子模板是22大小的领域
Roberts算子模板
Prewitt算子
Prewitt是利用特定区域内像素灰度值产生的差分实现边缘检测。它将方向差分运算和局部平均结合,选择适当的阈值,如果计算的梯度值大于阈值,则判定为边缘点,以此来提取图像边缘。
Prewitt算子采用33模板对区域内的像素值进行计算,其模板如下
Prewitt算子模板
Sobel算子
Sobel算子与Prewitt算子很相似,但它采用了高斯平滑滤波的思想,在Prewitt算子的基础上引入了权重这一概念,相邻点的距离远近对当前像素点的影响是不同的,距离越近的像素点对应当前像素的影响越大,从而实现图像锐化并突出边缘轮廓。
Sobel算子模板如下
Sobel算子模板
Laplacian算子
拉普拉斯(Laplacian)算子是 n维欧几里德空间中的一个二阶微分算子,是一个标量,具有各向同性的性质。拉普拉斯算子与边缘方向无关,在处理图像时,只考虑边缘幅值。
在使用拉普拉斯算子提取边缘之前,先对图像进行高斯滤波去除噪声,然后再利用二阶导数进行边缘检测,这一过程称为高斯-拉普拉斯(LOG)算子,因此,通常用高斯-拉普拉斯进行边缘检测。
常用的拉普拉斯算子模板一般是3*3大小的卷积核,分为4-邻域和8-邻域两种,如下所示;
拉普拉斯算子模板
Canny算子
Canny算子在有效检测边缘的同时,也在一定程度上抑制了噪音。
Canny算子的步骤如下:
1.高斯滤波
Canny算法和LOG算法一样,在边缘检测之前都对图像做高斯平滑处理,主要目的是为了去除噪声。因为图像中的边缘和噪声都对应高频信号,噪声一般会被识别为伪边缘,所以高斯滤波降低了伪边缘的识别。同时,高斯滤波的半径选择也是相当重要的,过小的半径会降低去噪能力,过大的半径会造成弱边缘检测不到。
2.计算梯度的幅值和方向
梯度的幅值和方向是利用一阶偏导有限差分进行计算的,经典的Canny算法需要计算水平、垂直和对角线四个方向的梯度,在实际应用中,通常只需要计算水平和垂直方向的差分。
3.非极大值抑制
边缘粗大明亮是因为得出来的梯度边缘含有多个像素宽,造成边缘定位不精确。非极大值抑制是对边缘进行细化的一种处理方法,只保留局部最大梯度值,减小边缘宽度。
4.双阈值化
当边缘像素梯度值大于高阈值时,则判定为强边缘点;当边缘像素梯度值在高阈值和低阈值之间时,则判定为弱边缘点;如果边缘像素梯度值小于低阈值,则该像素点直接被抑制掉。
5滞后边界跟踪
滞后边界跟踪是进行强弱边缘检测,目的是检查弱边缘点的8邻域像素内是否有强边缘点的存在。真实边缘中的强边缘点和弱边缘点是连通的,噪声引起的弱边缘点是独立的,通过滞后边界追踪可以确定弱边缘是否为真实的边缘,去除伪边缘。
import cv2
import numpy as np
from matplotlib import pyplot as plt
src = cv2.imread(r'F:\OPENCV\Opencv\lenna.jpg', cv2.IMREAD_COLOR)
if src is None:
print('image is empty')
src_rgb = cv2.cvtColor(src, cv2.COLOR_BGR2RGB)
img = src.copy()
# 中值滤波
blur = cv2.medianBlur(src, 5)
# 灰度化
gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)
# 边缘检测
# roberts算子
robertsKernel_x = np.array([[-1, 0], [0, 1]], dtype=int)
robertsKernel_y = np.array([[0, -1], [1, 0]], dtype=int)
roberts_x = cv2.filter2D(gray, cv2.CV_64F, robertsKernel_x)
roberts_y = cv2.filter2D(gray, cv2.CV_64F, robertsKernel_y)
robertsX = cv2.convertScaleAbs(roberts_x)
robertsY = cv2.convertScaleAbs(roberts_y)
Roberts = cv2.addWeighted(robertsX, 0.5, robertsY, 0.5, 0)
# prewitt算子
prewittKernel_x = np.array([[1, 1, 1], [0, 0, 0], [-1, -1, -1]], dtype=int)
prewittKernel_y = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]], dtype=int)
prewitt_x = cv2.filter2D(gray, cv2.CV_64F, prewittKernel_x)
prewitt_y = cv2.filter2D(gray, cv2.CV_64F, prewittKernel_y)
prewittX = cv2.convertScaleAbs(prewitt_x)
prewittY = cv2.convertScaleAbs(prewitt_y)
Prewitt = cv2.addWeighted(prewittX, 0.5, prewittY, 0.5, 0)
# sobel 方法一. -1 表示输出图像和原始图像的位深一样,为np.uint8,参数1,1 表示在x,y两个方向求一阶导数
Sobel = cv2.Sobel(gray, -1, 1, 1, ksize=5)
# sobel 方法二
# cv2.CV_64F 为输出图像的深度(数据类型),参数1,0 表示只在x方向求一阶导数,ksize为核的大小
sobel_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
# 参数0,1 表示只在x方向求一阶导数
sobel_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
# 将图像深度转换为np.uint8
# scale_absX = np.uint8(np.absolute(sobel_x))
# scale_absY = np.uint8(np.absolute(sobel_y))
# 将图像深度转换为np.uint8
sobelX = cv2.convertScaleAbs(sobel_x)
sobelY = cv2.convertScaleAbs(sobel_y)
result = cv2.addWeighted(sobelX, 0.5, sobelY, 0.5, 0)
# laplacian 检测
Laplacian = cv2.Laplacian(gray, -1)
# Canny边缘检测
# 30为低阈值, 120为高阈值
Canny = cv2.Canny(gray, 30, 120)
titles = ['src_rgb', 'gray', 'roberts', 'prewitt', 'sobel1', 'sobel2', 'laplacian', 'canny']
images = [src_rgb, gray, Roberts, Prewitt, result, Sobel, Laplacian, Canny]
plt.figure(figsize=(6, 6))
for i in range(len(images)):
plt.subplot(3, 3, i + 1)
plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([])
plt.yticks([])
plt.show()