特征检测-第二部分-拉普拉斯与高斯算子

特征检测,第二部分:拉普拉斯与高斯算子

towardsdatascience.com/feature-detection-part-2-laplace-gaussian-operators/

简介

特征检测是计算机视觉的一个领域,它侧重于使用工具检测图像中的感兴趣区域。大多数特征检测算法的一个重要方面是它们在底层不使用机器学习,这使得结果更具可解释性,在某些情况下甚至更快。

在本系列的上一篇文章中,我们学习了关于边缘的内容——图像强度急剧变化的区域。边缘通常代表可以找到感兴趣物体的局部区域。

例如,考虑一张蓝天和一架小飞机飞越其上的图像。整个图像的强度差异几乎为零,除了飞机与蓝色天空背景的交点所在的小区域。因此,我们可以很容易地使用图像导数或 Sobel 滤波器检测飞机所在区域及其形状。

边缘位于像素强度变化显著的区域。在这个例子中,边缘可以在平面的边界和云附近找到。

*换句话说,我们可以通过取强度函数一阶导数的最小/最大峰值来检测边缘,这由中间的图像表示。

第二个图像导数

让我们回到前面提到的例子,并问自己如果我们取二阶导数会发生什么?答案显示在下面的右图。在这种情况下,一阶导数的最小/最大峰值将对应于二阶导数的零点

X 轴上的强度值(左侧图像)。

第一阶导数(中间图像):局部极值表示边缘。

第二阶导数(右侧图像):零交叉区域表示边缘。

图像由作者改编。来源:拉普拉斯算子 (OpenCV 文档)

这意味着我们现在可以将二阶导数的零值作为一个额外的边缘检测标准。然而,我们必须保持谨慎,因为图像上的其他点也可能导致二阶导数等于 0,但它们本身并不是边缘。为此,建议使用其他过滤器来消除这些情况。

*通常,图像边缘会导致非常尖锐的零交叉区域。

定义

拉普拉斯算子正式由以下公式定义:

拉普拉斯公式

如我们所见,拉普拉斯实际上是沿 x 和 y 方向二阶导数的总和。

拉普拉斯不提供关于边缘方向的信息

离散近似

对于上述连续情况的拉普拉斯公式,让我们尝试为图像的离散情况获得一个近似值。

为了做到这一点,让我们假设我们想要计算图像下方部分的像素 I₂₂的拉普拉斯。让我们计算 X 轴方向的导数。

在这个例子中,我们想要近似中心像素 I₂₂的拉普拉斯。

为了做到这一点,我们重复使用前一篇文章中的导数公式两次,选择两个Δx 的值:-1 和 1。我们得到:

  • 对于Δx = -1:dI / dx = I₂₃ – I₂₂

  • 对于Δx = 1:dI / dx = I₂₂ – I₂₁

要计算一阶导数,我们基本上是计算相邻像素之间的差值。对于二阶导数,我们可以用相同的逻辑进行计算,可以非正式地将其视为差分的差值。

因此,我们可以取我们刚刚计算的两个差值,并找到它们的差。更正式地说,如果我们通过将Δx 设置为-1 来应用标准的导数公式,结果将是相同的。所以我们得到:

  • d²I / dx = (I₂₃ – I₂₂) – (I₂₂ – I₂₁) = I₂₃ – 2I₂₂ + I₂₁

太好了!我们已经计算了 X 方向的二阶导数。我们可以对 Y 方向的二阶导数执行类似的程序。我们得到以下表达式:

  • d²I / dy = (I₃₂ – I₂₂) – (I₂₂ – I₁₂) = I₃₂ – 2I₂₂ + I₁₂

最后,我们只剩下将两个导数相加。我们得到:

  • d²I / dx + d²I / dy = I₁₂ + I₂₁ + I₂₃ + I₃₂ – 4I₂₂

整个拉普拉斯计算过程可以通过下面的图表进行可视化:

获得拉普拉斯计算离散情况的公式

基于所得到的结果,我们还可以构建一个 3×3 卷积核用于拉普拉斯:

拉普拉斯核

有趣的是,这个核的元素之和为 0,这意味着如果输入图像是常数,那么应用这个滤波器将产生一个零元素的矩阵。这是一个逻辑上的结果,因为强度没有变化

与 Sobel 和 Scharr 核相比,拉普拉斯核可以检测两个方向上的强度变化。对于任何图像,应用 3×3 核就足够了,拉普拉斯算子将输出表示强度变化的最终标量值。

提醒一下,使用 Sobel 和 Scharr 算子时,核分别应用于 X 和 Y 轴,然后计算它们的幅度

对角线边缘检测

我们已经推导出一个表示 X 和 Y 轴上二阶导数之和的核心。因此,这种方法非常适合检测水平和垂直边缘。但是,如果图像中的边缘具有对角方向怎么办? 我们还没有考虑到这一点。

因此,上面的核心通常稍微调整以考虑对角方向。最受欢迎的选项之一是使用以下核心:

一种替代拉普拉斯核心的方法,它在水平、垂直和对角线上都是对称的。

各向同性

我们可以观察到拉普拉斯矩阵是对称的,这使得它是各向同性的。各向同性是一个属性,根据这个属性,核是旋转不变的,这意味着对图像及其旋转版本应用各向同性滤波器产生的输出是相同的。

噪声

我们上面看到的拉普拉斯核心在边缘检测方面效果很好。然而,我们没有考虑到另一个方面,这会阻止我们在现实生活中有效地应用该滤波器:噪声

在本文开头,我们看到了几个图像强度沿 X 轴变化的图表,其中我们绘制了图像强度的第一和第二导数。实际上,这些图表是为一个完美的图像构建的,其中没有噪声。

如果存在噪声,我们可能会遇到下图中描述的情况。

考虑到输入图像(左侧)上存在噪声,X 轴上的强度值会导致振荡,使得右侧的第一导数(以及结果,第二导数)难以分析。

左侧的图表展示了图像轴上强度值的一个更现实的场景。尽管噪声不是很强,但它仍然在局部区域快速波动。同时,我们使用导数来检测相同的快速变化,这造成了一个问题。

最终,取第一导数会导致强度变化,如右图所示。显然,在存在这种振荡的情况下,使用导数进行正常的边缘检测是不可能的。

高斯滤波器

高斯滤波器是一种抑制图像噪声的方法,允许拉普拉斯算子或其他边缘检测算子无约束地应用。

形式上,高斯分布由以下公式给出(其中 μ = 0):

高斯公式。μ是平均值(在我们的情况下 μ = 0),σ表示标准差,可以根据特定问题进行调整。

高斯函数 g(x) 在右上角的图表中绘制如下:

右上角:强度函数。

左上角:高斯函数。

左下角:强度与高斯函数的乘积。

右下角:乘积的导数。

将强度函数 I(x)乘以高斯函数 g(x)通常会使强度平滑,这有助于分析。 示例显示在左下图中。

给定平滑函数的形式,然后我们可以取第一个导数,其极值点将指示边缘(右下图)。

高斯导数

我们可以注意到,取第一个图像导数是一个线性操作,因此我们可以以下述方式分解我们的计算:

我们可以预先计算高斯导数,然后乘以强度函数;结果将相同,但需要的计算量更少。

这允许我们预先计算高斯的第一导数,然后乘以强度函数,从而优化计算。

高斯拉普拉斯算子

我们可以将相同的技巧应用于第二个导数,这是我们在这篇文章开头讨论的边缘检测。因此,我们可以预先计算第二个高斯导数,然后乘以强度函数。结果显示在右下角的图中:

左上角:强度函数。

右上角:高斯函数。

左下角:高斯函数的第二个导数(也称为倒置的墨西哥草帽)。

右下角:第二个高斯导数与强度函数的乘积。

得到的函数被称为高斯拉普拉斯算子,在边缘检测中得到了广泛的应用,同时也对图像噪声具有鲁棒性。

高斯函数的第二个导数(左下图)通常被称为倒置的“墨西哥草帽”,因为它与草帽形状的高度相似。

OpenCV

在使用第二个图像导数研究了边缘检测理论之后,现在是时候看看如何在 OpenCV 中实现拉普拉斯滤波器了。

高斯拉普拉斯算子

首先,让我们导入必要的库并上传一个输入图像:

import cv2
import numpy as np
import matplotlib.pyplot as plt

我们将使用包含几个硬币的图像示例:

输入图像

让我们读取图像:

image = cv2.imread('data/input/coins.png')
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

我们已经将输入图像转换为灰度格式,以便能够后来相对于强度变化取导数。

如上所述,在使用拉普拉斯算子之前,我们将应用高斯滤波器:

image = cv2.GaussianBlur(image, (7, 7), 0)
  • 第二个参数(7, 7)指的是核大小。

  • 第三个参数用于定义从上述高斯公式中给出的标准差σ的值。在这里,我们将其设置为 0,这意味着标准差σ将由 OpenCV 根据提供的核大小自动选择。

现在我们已经准备好应用拉普拉斯滤波器:

laplacian_signed = cv2.Laplacian(image, cv2.CV_16S, ksize=3)

这里我们指定输出类型为 cv2.CV_16S,它等同于短整型,其值在范围[-32768, 32767]内。我们这样做是因为拉普拉斯滤波器可以产生超出标准像素区间[0, 255]的值。如果我们没有这样做,那么输出值将被截断到[0, 255],并且我们会丢失信息。

OpenCV 还提供了一个有用的函数,将原始拉普拉斯输出(或任何其他结果值)映射到标准范围[0, 255]:

laplacian_absolute = cv2.convertScaleAbs(laplacian_signed)

结果变量laplacian_absolutelaplacian_signed具有相同的形状,但值被截断到[0, 255]。映射是通过使用函数cv2.convertScaleAbs()来完成的,以保留关于零交叉的信息:

  • 绝对值大于 255 的值被截断到 255。

  • 对于介于-255 和 255 之间的值,取其绝对值。

cv2.convertScaleAbs()函数将像素值映射到[0, 255]区间。

零交叉检测

不幸的是,OpenCV 文档只显示了一个拉普拉斯应用示例,但没有演示如何使用其结果进行零交叉边缘检测。下面是一个简单的代码片段示例,实现了这一点:

laplacian_sign = np.sign(laplacian_signed)

zero_crossings = np.zeros_like(laplacian_sign, dtype=bool)
for shift in [-5, 5]:
    zero_crossings |= (np.roll(laplacian_sign, shift, axis=0) * laplacian_sign < 0)
    zero_crossings |= (np.roll(laplacian_sign, shift, axis=1) * laplacian_sign < 0)

threshold = 20
edges = np.uint8(zero_crossings & (np.abs(laplacian_signed) > threshold)) * 255

简而言之,我们创建一个zero_crossings变量,它包含有关位置(x, y)处的像素是否为零交叉候选者的信息。在我们的代码中,我们考虑一个像素为零交叉,如果它的符号(+或-)与任何位移像素的符号在水平或垂直方向上相差五个位置。

我们可以选择另一个位移常数(不一定是 5)或者将几个它们组合起来,并且/或者还考虑对角位移。

为了排除非相关零交叉,我们只保留那些绝对拉普拉斯值大于定义的阈值(20)的零交叉。

可视化

最后,让我们绘制我们在整个分析过程中检索到的图像:

plt.figure(figsize=(12, 4))

plt.subplot(1, 3, 1)
im1 = plt.imshow(laplacian_signed, cmap='gray', vmin=laplacian_signed.min(), vmax=laplacian_signed.max())
plt.title("Laplacian (signed)")
plt.axis('off')
plt.colorbar(im1, fraction=0.05, pad=0.05, label='Pixel value')

plt.subplot(1, 3, 2)
im2 = plt.imshow(laplacian_absolute, cmap='gray', vmin=laplacian_absolute.min(), vmax=laplacian_absolute.max())
plt.title("Laplacian (absolute)")
plt.axis('off')
plt.colorbar(im2, fraction=0.05, pad=0.05, label='Pixel value')

plt.subplot(1, 3, 3)
im2 = plt.imshow(edges, cmap='gray', vmin=edges.min(), vmax=edges.max())
plt.title("Edges from zero-crossings")
plt.axis('off')
plt.colorbar(im2, fraction=0.05, pad=0.05, label='Pixel value')

plt.tight_layout()
plt.show()

这里是我们得到的结果输出:

应用拉普拉斯滤波器后的输出。右侧图像是二值图像(仅包含值为 0 或 255 的像素)。

如您所见,我们成功地检测到了右侧二值图像上的边缘。下一步可以,例如,涉及应用 OpenCV 方法cv2.findContours()来检测轮廓并进一步分析它们。

鉴于符号拉普拉斯输出的简单形式,我们也可以用它来检测硬币的边缘。

结论

利用前一部分关于图像导数的知识,本文分析了二阶导数和零交叉检测的作用。这也是了解拉普拉斯和高斯滤波器的好方法,这些滤波器可以用 OpenCV 轻松实现边缘检测。

资源

所有图像除非另有说明,均为作者所有。

posted @ 2026-03-28 10:03  布客飞龙III  阅读(14)  评论(0)    收藏  举报