这里需要区分开边缘检测和轮廓检测
边缘检测并非万能,边缘检测虽然能够检测出边缘,但边缘是不连续的,检测到的边缘并不是一个整体。图像轮廓是指将边缘连接起来形成的一个整体,用于后续的计算。
OpenCV 提供了查找图像轮廓的函数 cv2.findContours(),该函数能够查找图像内的轮廓信息,而函数cv2.drawContours()能够将轮廓绘制出来。
一、图像梯度
图像梯度是指图像中灰度强度变化的方向和幅度。梯度计算对于许多计算机视觉和图像处理任务非常重要,如边缘检测、特征提取等。
图像梯度通常使用Sobel、Scharr或其他卷积核进行计算。这些卷积核可以在图像上进行卷积操作,以便检测图像中的水平和垂直边缘。梯度的方向表示灰度变化最快的方向,而梯度的幅度表示这个变化的强度。
·
图像梯度是指图像中像素值变化的快慢和方向。在图像处理中,常用于检测图像中的边缘和轮廓。图像梯度的计算可以通过一阶或二阶导数的方法来实现。
1.1 介绍
-
一阶导数(梯度):
- 水平方向梯度(Gx): 表示图像在水平方向上的变化率。
- 垂直方向梯度(Gy): 表示图像在垂直方向上的变化率。
-
梯度幅值: 通过Gx和Gy的组合计算,通常使用欧几里得范数(平方和的平方根)表示:
-
梯度方向:
- 梯度方向角度(θ): 通过arctan(Gy/Gx)计算,表示梯度的方向。
-
边缘检测:
- 基于图像梯度的边缘检测算法,如Sobel、Prewitt、Roberts等,通过卷积运算来获取图像梯度。
-
Canny边缘检测:
- Canny边缘检测是一种常用的边缘检测方法,它利用图像梯度的幅值和方向来检测边缘。
-
图像增强:
- 图像梯度也可以用于图像增强,通过调整图像梯度的幅值和方向来改善图像的视觉效果。
在实际图像处理中,图像梯度是一个重要的特征,它不仅用于边缘检测,还可以应用于目标检测、图像分割和其他计算机视觉任务。通过分析图像梯度,可以获取关于图像结构和内容的有用信息。
1.2 涉及函数
-
Sobel算子:
-
cv2.Sobel(src, ddepth, dx, dy, ksize)
: 对图像进行Sobel算子的卷积操作,其中dx
和dy
表示求导的阶数,ksize
是卷积核的大小。
-
-
Scharr算子:
-
cv2.Scharr(src, ddepth, dx, dy)
: 类似于Sobel,用于对图像进行Scharr算子的卷积操作。
-
-
Laplacian算子:
-
cv2.Laplacian(src, ddepth)
: 对图像进行Laplacian算子的卷积操作,用于增强图像中的高频信息。
-
-
Canny边缘检测:
-
cv2.Canny(image, threshold1, threshold2)
: 使用Canny边缘检测算法,threshold1
和threshold2
是梯度阈值,用于定义边缘的强弱。
-
-
角度和幅值计算:
-
cv2.phase(x, y)
: 计算输入图像梯度的相位角度。 -
cv2.magnitude(x, y)
: 计算输入图像梯度的幅值。
-
-
图像梯度计算:
-
cv2.Sobel()
,cv2.Scharr()
, 和cv2.Laplacian()
的输出可以通过cv2.magnitude()
和cv2.phase()
计算幅值和相位。
-
-
图像增强:
- 借助梯度信息可以进行图像增强,例如通过调整梯度的幅值来实现锐化。
-
图像显示:
-
cv2.imshow(window_name, image)
: 用于显示图像,可以用来显示梯度图等中间结果。
-
二、高频强调滤波器
在OpenCV中,梯度滤波器或高通滤波器通常用于突出图像中的边缘和细节。这些滤波器是一种高频强调滤波器,可以通过卷积操作来应用于图像。、
-
Sobel滤波器:
- Sobel滤波器是一种常用的梯度滤波器,用于在图像中检测边缘。它分为水平和垂直两个方向。
sobelx = cv2.Sobel(src, cv2.CV_64F, 1, 0, ksize=3) sobely = cv2.Sobel(src, cv2.CV_64F, 0, 1, ksize=3)
这里,
1
和0
分别表示水平和垂直方向,ksize
是卷积核的大小。 -
Scharr滤波器:
- Scharr滤波器类似于Sobel,但对边缘的响应更强。你可以使用
cv2.Scharr()
函数来应用Scharr滤波器。
scharrx = cv2.Scharr(src, cv2.CV_64F, 1, 0) scharry = cv2.Scharr(src, cv2.CV_64F, 0, 1)
- Scharr滤波器类似于Sobel,但对边缘的响应更强。你可以使用
-
Laplacian滤波器:
- Laplacian滤波器用于增强图像的高频部分,通常用于边缘检测。
laplacian = cv2.Laplacian(src, cv2.CV_64F)
-
Canny边缘检测:
- Canny边缘检测是一种综合了梯度信息的方法,包括非极大值抑制和双阈值处理。
edges = cv2.Canny(src, threshold1, threshold2)
其中,
threshold1
和threshold2
是梯度阈值,用于定义边缘的强弱。
这些滤波器可以帮助突出图像中的边缘信息,对于图像处理中的特定任务,你可以根据需要选择合适的滤波器。
2.1 Sobel 算子
2.1.1 Sobel 理论基础
Sobel算子是一种常用的边缘检测算法,用于计算图像中像素点的梯度强度。在Sobel算子中,中心点相连的系数为2,是为了更好地捕捉图像中的边缘信息。
Sobel算子包含两个3x3的卷积核(一个用于检测水平边缘,另一个用于检测垂直边缘)
- 水平方向Sobe检测核:
-1 -2 -1
0 0 0
1 2 1
水平方向Sobel算子的卷积核主要关注图像中水平方向的变化。它通过将权重分配给中间列和左右列的像素,来检测图像中的水平边缘。
- 垂直方向Sobel检测核:
-1 0 1
-2 0 2
-1 0 1
垂直方向Sobel算子的卷积核主要关注图像中垂直方向的变化。它通过将权重分配给中间行和上下行的像素,来检测图像中的垂直边缘。
- 梯度计算:
假设要计算图像中某个像素位置的值,例如该位置为图像中的中心点(i,j)位置。卷积操作的计算步骤如下:
-
将Sobel算子的中心(1,1)位置与图像中以该点为中心的3x3区域进行对应元素相乘,并将结果相加。
-
(1)对于水平边缘检测核:
(-1 * I(i-1, j-1)) + (-2 * I(i, j-1)) + (-1 * I(i+1, j-1)) + (0 * I(i-1, j)) + (0 * I(i, j)) + (0 * I(i+1, j)) + (1 * I(i-1, j+1)) + (2 * I(i, j+1)) + (1 * I(i+1, j+1))
其中,I(i, j) 表示图像中像素位置为 (i, j) 的值。
简化:
P5x = (P3-P1) + 2·(P6-P4) + (P9-P7)
- (2) 垂直边缘检测核的计算公式:
(-1 * I(i-1, j-1)) + (0 * I(i, j-1)) + (1 * I(i+1, j-1)) +
(-2 * I(i-1, j)) + (0 * I(i, j)) + (2 * I(i+1, j)) +
(-1 * I(i-1, j+1)) + (0 * I(i, j+1)) + (1 * I(i+1, j+1))
简化:
P5y = (P7-P1) + 2·(P8-P2) + (P9-P3)
Sobel算子的应用可以帮助检测图像中的边缘,因为在边缘处,图像的强度发生明显的变化,导致梯度的幅值较大。Sobel算子在实际图像处理中广泛应用,尤其是在计算机视觉和图像分析领域,为后续的边缘检测和图像特征提取提供了基础。
2.1.2 Sobel 算子及函数使用
dst = cv2.Sobel(src, ddepth, dx, dy[, ksize[, scale[, delta[, borderType]]]])
其中参数的含义如下:
-
src
: 输入图像,通常是灰度图像。 -
ddepth
: 输出图像的深度(数据类型)。通常使用cv2.CV_64F
或-1
(让处理结果与原始图像保持一致)。
当 ddepth 设置为 -1 时,如果 Sobel 滤波器的计算导致了负数,这些负数将被截断为 0。这可能导致信息的丢失,因为负数通常在图像处理中是有意义的。
为了避免信息丢失,推荐使用更高的数据类型来进行计算,例如 cv2.CV_64F,即64位浮点型。这样,计算结果将以浮点数的形式保存,包括负数。然后,可以通过取绝对值或其他操作将其映射为需要的数据类型,例如 cv2.CV_8U,即8位无符号整数。
-
dx
和dy
: 分别表示在 x 和 y 方向上的导数的阶数,通常是 0 或 1。 -
ksize
: 可选参数,表示 Sobel 滤波器的大小,通常是 1、3、5 或 7。默认值是 3。
当该值为-1 时,则会使用 Scharr 算子进行运算。 -
scale
: 可选参数,用于缩放导数的比例因子。默认值是 1,是没有缩放的。 -
delta
: 可选参数,表示可选的增加到输出图像的值,用于调整图像的亮度。默认值是 0。 -
borderType
: 可选参数,表示图像边界的处理方式。默认值是cv2.BORDER_DEFAULT
。
这个函数的作用是应用 Sobel 滤波器,计算图像中每个像素点的梯度。dx
和 dy
参数决定了计算的梯度方向,常见的取值为 0、1,表示水平和垂直方向的梯度。ksize
参数用于指定 Sobel 滤波器的大小。
绝对值的原因:为了让偏导数正确地显示出来,需要将值为负数的近似偏导数转换为正数。即,要将偏导数取绝对值,以保证偏导数总能正确地显示出来。
`
如果不取绝对值,梯度的正负将分别表示变化的方向。但在实际应用中,我们更关心图像中是否存在边缘以及边缘的强度,而不太在意边缘的具体方向。因此,为了简化计算并准确表示边缘的变化强度,常常使用绝对值来忽略方向信息。设想在二维图像中有两个线条 A 和 B,A 线条是一条黑到白的线,B 线条是一条白到黑的线。
若针对 A 线条所在列,右侧像素值减去左侧像素值所得近似偏导数的值为-1。 针对 B 线条所在列,右侧像素值减去左侧像素值所得近似偏导数的值为 1。针对 A 线条所在行,下方像素值减去上方像素值所得近似偏导数为-1。 针对 B 线条所在行,下方像素值减去上方像素值所得近似偏导数为 1。
示例:
import cv2
import numpy as np
# 读取图像
image = cv2.imread('input_image.jpg', cv2.IMREAD_GRAYSCALE)
# 应用 Sobel 滤波器计算水平方向的梯度
sobelx = cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize=3)
# 应用 Sobel 滤波器计算垂直方向的梯度
sobely = cv2.Sobel(image, cv2.CV_64F, 0, 1, ksize=3)
这将分别计算图像中每个像素点的水平和垂直方向的梯度。你可以根据需要进一步处理这些梯度图像,例如计算梯度幅值和方向,用于边缘检测或其他图像处理任务。
(1)对参数取绝对值
cv2.convertScaleAbs()
是 OpenCV 中用于线性缩放和截断的函数。它的目的是将输入数组进行线性缩放并进行截断,最后将结果转换为无符号8位整数(cv2.CV_8U
)类型。
该函数的基本语法如下:
#该函数的作用是将原始图像 src 转换为 256 色位图
import cv2
dst = cv2.convertScaleAbs(src [, alpha[, beta]])
其中:
src
: 输入数组,即待处理的图像或数据。alpha
: 缩放因子,用于乘以输入数组的每个元素。默认值为 1。beta
: 偏移量,用于在缩放后对每个元素进行加法运算。默认值为 0。
import cv2
import numpy as np
# 读取图像
image = cv2.imread('input_image.jpg', cv2.IMREAD_GRAYSCALE)
# 缩放和平移图像,然后取绝对值
alpha = 1.5
beta = 20
result = cv2.convertScaleAbs(image, alpha=alpha, beta=beta)
# 显示原图和处理后的图像
cv2.imshow('Original Image', image)
cv2.imshow('Result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
(2)控制dx,dy方向的求导阶数
需要注意的是,dx和dy的值不能同时为0,因为至少要在一个方向上进行梯度计算,否则Sobel算子无法发挥作用。
1. 计算 x 方向边缘(梯度):
-
dx=1, dy=0
,表示只计算图像中每个像素点在 x 方向上的梯度。这有助于检测图像中水平方向的边缘。
注意里面的ddepth的不同和求导的数字1和2造成的差别:
*
ddepth=-1
ddepth=cv2.CV_64F
2. 计算 y 方向边缘(梯度):
-
dx=0, dy=1
,表示只计算图像中每个像素点在 y 方向上的梯度。这有助于检测图像中垂直方向的边缘。
3. 参数 dx
和参数 dy
的值均为 1
-
dx=1, dy=1
,表示同时计算图像中每个像素点在 x 和 y 方向上的梯度。这将产生一个包含综合梯度信息的结果,用于检测边缘的强度和方向。
出现零星的微小白点,每个点的大小为一个像素。
4. 计算 x 方向和 y 方向的边缘叠加:
- 通过组合不同的参数,如
dx=1, dy=0
和dx=0, dy=1
,可以实现计算 x 方向和 y 方向的边缘的叠加,得到更全面的边缘信息。
dx= cv2.Sobel( src , ddepth , 1 , 0 )
dy= cv2.Sobel( src , ddepth , 0 , 1 )
dst=cv2.addWeighted( src1 , alpha , src2 , beta , gamma )
2.1.3通过实际例子表示
(1)简单图像
为了保留更多的有效信息,利用“cv2.CV_64F”,参数 dx
和 dy 的值设置为“dx=1, dy=0”后执行该函数,再对该函数的结果计算绝对值并进行对比
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
img = cv.imread('img8/testSobelimg.jpg', cv.IMREAD_GRAYSCALE)
sobelx = cv.Sobel(img, cv.CV_64F, 1, 0)
testsoblex = cv.Sobel(img, -1, 1, 0)
print(sobelx)
sobelxbeside = cv.convertScaleAbs(sobelx)
#%%
sobely = cv.Sobel(img, cv.CV_64F, 0, 1)
testsobley = cv.Sobel(img, -1, 0, 1)
print(sobely)
sobelybeside = cv.convertScaleAbs(sobely)
#%%
sobelxy = cv.Sobel(img, -1, 1, 1)
print(sobelxy)
Sobelxyy = cv.Sobel(img, -1, 1, 1)
plt.imshow(sobelxy, cmap='gray')
#%%
sobelxy = cv.addWeighted(sobelx, 1, sobely, 1, 0)
print(sobelxy)
plt.imshow(sobelxy, cmap='gray')
Sobelxybeside = cv.convertScaleAbs(sobelxy)
#%%
Sobelx = cv.convertScaleAbs(sobelxy)
print(Sobelx)
plt.imshow(Sobelx, cmap='gray')
#%% md
#%%
plt.figure(figsize=(12, 8))
plt.subplot(3, 3, 1), plt.imshow(img, cmap='gray'), plt.title('原始图像')
plt.subplot(3, 3, 2), plt.imshow(sobelx, cmap='gray'), plt.title('sobelx')
plt.subplot(3, 3, 3), plt.imshow(sobely, cmap='gray'), plt.title('sobely')
plt.subplot(3, 3, 4), plt.imshow(Sobelxyy, cmap='gray')
plt.subplot(3, 3, 5), plt.imshow(sobelxbeside, cmap='gray'), plt.title('sobelxbeside')
plt.subplot(3, 3, 6), plt.imshow(sobelybeside, cmap='gray'), plt.title('sobelybeside')
plt.subplot(3, 3, 7), plt.imshow(Sobelxybeside, cmap='gray')
plt.subplot(3, 3, 8), plt.imshow(testsoblex, cmap='gray'), plt.title('testsoblex')
plt.subplot(3, 3, 9), plt.imshow(testsobley, cmap='gray'), plt.title('testsobley')
plt.show()
(2)复杂的,实际的相片
我们可以看到ddepth是-1和cv_64F的区别,在人物和这个细微
2.1.4 近似值
2.2 Scharr 算子
为了弥补Sobel不太精准的情况,我们可以用 Scharr 算子并看作对 Sobel 算子的改进:
dst = cv2.Scharr( src, ddepth, dx, dy[, scale[, delta[, borderType]]] )
# 最好ddepth-cv2.cv_64F
dst= cv2.convertScaleAbs(dst)
2.2.1 等价的函数。
dst=cv2.Scharr(src, ddepth, dx, dy)
和 cv2.Sobel()中,如果 ksize=-1,则会使用 Scharr 滤波器。
dst=cv2.Sobel(src, ddepth, dx, dy, -1)
2.2.2需要满足的条件:dx >= 0 && dy >= 0 && dx+dy = 1
参数 ddepth 的值应该设置为“cv2.CV_64F”,并对函数
cv2.Scharr()的计算结果取绝对值,才能保证得到正确的处理结果。
2.2.3 Sobel 算子和 Scharr 算子的比较
Sobel算子和Scharr算子都是用于图像边缘检测的滤波器,它们在一些方面有相似之处,但也存在一些差异。以下是Sobel算子和Scharr算子的比较:
-
灵敏度:
-
Sobel算子:
Sobel算子对边缘的响应相对较强,但相对来说,对噪声的敏感性也较高。 -
Scharr算子:
Scharr算子在对边缘的响应上更加平滑,对噪声的敏感性相对较低。
-
-
计算复杂度:
-
Sobel算子:
Sobel算子的计算较为简单,适用于一些对计算资源要求较低的场合。 -
Scharr算子:
Scharr算子的计算复杂度相对较高,但在一些对精确性要求较高的场景中可能更为合适。
-
-
性能:
-
Sobel算子:
Sobel算子广泛应用于一般的边缘检测任务,性能较好。 -
Scharr算子:
Scharr算子在一些特定的图像分析任务中可能表现得更好,尤其是对于强边缘的检测。
-
在选择使用Sobel算子或Scharr算子时,通常会根据具体的应用场景和任务需求来权衡它们之间的差异。
2.3 拉普拉斯 Laplacian 算子
它是通过对图像进行二阶微分操作来实现的,能够突出图像中像素值变化较大的区域。拉普拉斯算子通常应用于灰度图像。,需要计算两个方向的梯度值。
前两个算子,都是m连通型的(即:对角),现在介绍一个4连通的。
计算像素点 P5 的近似导数值,
P5lap = (P2 + P4 + P6 + P8) - 4·P5
计算结果的值可能为正数,也可能为负数。所以,需要对计算
结果取绝对值,以保证后续运算和显示都是正确的。
2.3.1 函数
laplacian = cv2.Laplacian(src, ddepth[, ksize[, scale[, delta[, borderType]]]])
laplacian = cv2.Laplacian(o,cv2.CV_64F)laplacian = cv2.convertScaleAbs(laplacian) # 回uint8
-
src
:输入图像,通常是灰度图像。 -
ddepth
:输出图像的深度(数据类型),通常使用cv2.CV_64F
。 -
ksize
:拉普拉斯算子的卷积核大小,通常为1。 -
scale
:拉普拉斯算子的缩放因子,用于调整结果的幅值。 -
delta
:可选的增加到输出图像的值,用于调整图像的亮度。 -
borderType
:图像边界处理类型,默认为cv2.BORDER_DEFAULT
。
不用像Sobel算子一样,需要算x和y方向的再加起来。
三、Canny 边缘检测
3.1 Canny 原理
【去噪–计算梯度的幅度与方向–非极大值抑制–确定边缘】
Canny 边缘检测是一种经典的图像处理技术,通常包括以下几个步骤:
-
高斯滤波(Gaussian Blur):
- 首先,图像会经过高斯滤波以平滑噪声和细节。这有助于减少后续步骤中的误差和不必要的细节。
让临近的像素具有更高的重要度。对周围像素计算加权平均值,较近的像素具有较大的权重值。
-
灰度转换(Grayscale Conversion):
- 将图像转换为灰度,将彩色图像转换为单通道灰度图像。这样可以简化处理,并减少计算复杂性。
-
计算梯度(Gradient Calculation):
- 使用卷积操作,如Sobel算子,计算图像的梯度。梯度的方向和强度有助于找到图像中的边缘。
- 使用卷积操作,如Sobel算子,计算图像的梯度。梯度的方向和强度有助于找到图像中的边缘。
梯度:
方向:
-
非极大值抑制(Non-maximum Suppression):
- 对梯度图进行非极大值抑制,以保留局部梯度最大的点,从而使边缘更细化。
-
滞后阈值处理(Hysteresis Thresholding):
- 使用两个阈值进行滞后阈值处理。通常,有一个较低的阈值(弱边缘)和一个较高的阈值(强边缘)。根据梯度强度,将像素标记为强边缘、弱边缘或非边缘。强边缘会被保留,而与强边缘连接的弱边缘也会被保留。
设定阈值:
- 选择两个阈值,一个是高阈值(maxVal),另一个是低阈值(minVal)。这两个阈值的选择通常依赖于具体的应用和图像特性。
判断边缘属性:
- 对图像中的每个像素进行检查,根据其梯度值与设定的阈值之间的关系,判断其边缘的属性:
- 如果梯度值大于或等于高阈值(maxVal),则将该像素标记为强边缘。
- 如果梯度值介于高阈值和低阈值之间(minVal到maxVal之间),则将该像素标记为虚边缘(需要保留)。
- 如果梯度值小于或等于低阈值(minVal),则抑制该像素,认为它不是边缘。
处理虚边缘:文章来源:https://www.toymoban.com/news/detail-761302.html
- 对于被标记为虚边缘的像素,进一步判断其是否与强边缘相连。如果与强边缘相连,则将其标记为强边缘,否则抑制它。 这个步骤的目标是最终确定哪些边缘是真实的、强的,哪些是需要保留的虚边缘,以及哪些应该被抑制。这样的处理可以有效减少噪声和虚假边缘,提高Canny边缘检测的准确性。
3.2 Canny 函数及使用
edges = cv.Canny( image, threshold1, threshold2[, apertureSize[, L2gradient]])
edges 为计算得到的边缘图像。
image 为 8 位输入图像。
如果为了细节,就设置的小一些
threshold1 表示处理过程中的第一个阈值。
threshold2 表示处理过程中的第二个阈值。文章来源地址https://www.toymoban.com/news/detail-761302.html
import cv2
import matplotlib.pyplot as plt
# 读取图像
image_path = './'
original_image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 高斯滤波,去除噪声
blurred_image = cv2.GaussianBlur(original_image, (5, 5), 0)
# 使用Canny函数进行边缘检测
edges = cv2.Canny(blurred_image, 50, 150) # 50是低阈值,150是高阈值
# 显示原始图像、模糊图像和边缘图像
plt.figure(figsize=(10, 6))
plt.subplot(131)
plt.imshow(original_image, cmap='gray')
plt.title('Original Image')
plt.subplot(132)
plt.imshow(blurred_image, cmap='gray')
plt.title('Blurred Image')
plt.subplot(133)
plt.imshow(edges, cmap='gray')
plt.title('Canny Edges')
plt.show()
到了这里,关于我在Vscode学OpenCV 图像处理三(图像梯度--边缘检测【图像梯度、Sobel 算子、 Scharr 算子、 Laplacian 算子、Canny 边缘检测】)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!