OpenCV快速入门:图像滤波与边缘检测

文章目录

  • 前言
  • 一、噪声种类与生成
    • 1.1 椒盐噪声
    • 1.2 高斯噪声
    • 1.3 彩色噪声
  • 二、卷积操作
    • 2.1 卷积基本原理
    • 2.2 卷积操作代码实现
  • 三、线性滤波
    • 3.1 均值滤波
      • 均值滤波原理
      • 均值滤波公式
      • 均值滤波代码实现
    • 3.2 方框滤波
      • 方框滤波原理
      • 方框滤波公式
      • 方框滤波代码实现
    • 3.3 高斯滤波
      • 高斯滤波原理
      • 高斯滤波公式
      • 高斯滤波代码实现
    • 3.4 可分离滤波
      • 可分离滤波原理
      • 可分离滤波公式
      • 可分离滤波代码实现
  • 四、非线性滤波
    • 4.1 中值滤波
      • 中值滤波原理
      • 中值滤波公式
      • 中值滤波代码实现
    • 4.2 双边滤波
      • 双边滤波原理
      • 双边滤波公式
      • 双边滤波代码实现
  • 五、 边缘检测
    • 5.1 Sobel算子
      • Sobel算子原理
      • Sobel算子公式
      • 计算梯度
      • Sobel算子代码实现
    • 5.2 Scharr算子
      • Scharr算子原理
      • Scharr算子公式
      • 计算梯度
      • Scharr算子代码实现
    • 5.3 Laplacian算子
      • Laplacian算子原理
      • Laplacian算子公式
      • Laplacian算子代码实现
    • 5.4 Canny算子
      • Canny算子原理
      • Canny算子公式
      • Canny算子代码实现
    • 5.5 自定义边缘检测滤波器
      • 自定义边缘检测滤波器原理
      • 自定义边缘检测滤波器的一般步骤
      • 自定义边缘检测滤波器的例子
      • 自定义边缘检测代码实现
  • 总结

前言

在计算机视觉领域,图像处理是一个不可或缺的环节。图像滤波和边缘检测是图像处理中的两个关键任务,它们在图像增强、特征提取等方面发挥着重要作用。本文将介绍噪声的种类与生成、卷积操作、线性滤波、非线性滤波以及边缘检测原理等内容。
手绘OpenCV

一、噪声种类与生成

在图像处理中,噪声是指图像中不希望出现的随机扰动。了解噪声的种类以及如何生成是图像处理中的重要一步。本节将详细介绍两种常见的噪声类型:椒盐噪声和高斯噪声。另外追加一种彩色噪声,原理与椒盐噪声和高斯噪声相同,只是针对每个通道都进行噪声的扰动。并使用 OpenCV 编写示例代码来生成这三种噪声。

1.1 椒盐噪声

椒盐噪声是一种随机出现在图像中的黑白像素点的噪声,通常模拟了图像传感器或传输过程中的不确定性。在椒盐噪声中,一些像素被设为黑色(椒噪声),一些像素被设为白色(盐噪声),从而模拟图像中的随机点噪声。

下面是使用 OpenCV 生成椒盐噪声的示例代码:

import cv2
import numpy as npdef add_salt_and_pepper_noise(image, salt_prob, pepper_prob):noisy_image = np.copy(image)# 椒噪声salt_noise = np.random.rand(*image.shape) < salt_probnoisy_image[salt_noise] = 255# 盐噪声pepper_noise = np.random.rand(*image.shape) < pepper_probnoisy_image[pepper_noise] = 0return noisy_image# 读取图像
original_image = cv2.imread("tulips.jpg", cv2.IMREAD_GRAYSCALE)# 设定椒盐噪声的概率
salt_probability = 0.02
pepper_probability = 0.02# 生成带有椒盐噪声的图像
noisy_image = add_salt_and_pepper_noise(original_image, salt_probability, pepper_probability)# 显示原始图像和带有噪声的图像
cv2.imshow("Noisy Image (Salt and Pepper)", cv2.hconcat([original_image, noisy_image]))
cv2.waitKey(0)
cv2.destroyAllWindows()

Salt and Pepper
在上述代码中,add_salt_and_pepper_noise 函数通过设定椒盐噪声的概率,在图像中添加了随机的黑白像素点,从而生成了椒盐噪声。

1.2 高斯噪声

高斯噪声是一种连续的随机过程,其数学模型符合正态分布。在图像中,高斯噪声表现为像素值的随机波动,通常是由于环境、设备等因素引起。下面是使用 OpenCV 生成高斯噪声的示例代码:

import cv2
import numpy as npdef add_gaussian_noise(image, mean=0, sigma=25):row, col = image.shapegauss = np.random.normal(mean, sigma, (row, col))noisy_image = np.clip(image + gauss, 0, 255)return noisy_image.astype(np.uint8)# 读取图像
original_image = cv2.imread("tulips.jpg", cv2.IMREAD_GRAYSCALE)# 生成带有高斯噪声的图像
noisy_image = add_gaussian_noise(original_image)# 显示原始图像和带有噪声的图像
cv2.imshow("Noisy Image (Gaussian)", cv2.hconcat([original_image, noisy_image]))
cv2.waitKey(0)
cv2.destroyAllWindows()

Gaussian

在上述代码中,add_gaussian_noise 函数使用 NumPy 生成了服从正态分布的随机数,然后将其添加到图像中,生成了高斯噪声。通过调整 meansigma 参数,可以控制噪声的均值和标准差。

1.3 彩色噪声

彩色噪声是指在图像中引入的随机彩色扰动。下面是使用 OpenCV 生成彩色噪声的示例代码:

import cv2
import numpy as npdef add_colored_noise(image, intensity=50):row, col, ch = image.shapecolored_noise = np.random.randint(-intensity, intensity, (row, col, ch))noisy_image = np.clip(image + colored_noise, 0, 255)return noisy_image.astype(np.uint8)# 读取图像
original_image = cv2.imread("tulips.jpg")# 生成带有彩色噪声的图像
noisy_image = add_colored_noise(original_image)# 显示原始图像和带有噪声的图像
cv2.imshow("Original Image", original_image)
cv2.imshow("Noisy Image (Colored)", noisy_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Colored
在上述代码中,add_colored_noise 函数通过生成随机彩色扰动并将其添加到图像中,实现了彩色噪声的生成。调整 intensity 参数可以控制噪声的强度。

二、卷积操作

卷积操作是图像处理中的核心步骤,它通过滤波器与图像进行卷积来实现特征提取、去噪或进行边缘检测等任务。本节将详细说明卷积操作的基本原理,并使用 OpenCV 编写示例代码来演示卷积在图像处理中的应用。

2.1 卷积基本原理

卷积操作的基本原理是通过一个滤波器(也称为卷积核或卷积矩阵)在图像上进行滑动,滤波器的每个元素与图像对应位置的像素值相乘,然后将所有结果相加,最终形成新的图像。这个过程可以用数学公式表示为:

( f ∗ g ) ( x , y ) = ∑ i ∑ j f ( x − i , y − j ) ⋅ g ( i , j ) (f * g)(x, y) = \sum_{i} \sum_{j} f(x-i, y-j) \cdot g(i, j) (fg)(x,y)=ijf(xi,yj)g(i,j)

其中, f f f 是原始图像, g g g 是滤波器, ( f ∗ g ) (f * g) (fg) 是卷积操作的结果。

2.2 卷积操作代码实现

下面是一个简单的示例代码,演示如何使用 OpenCV 进行卷积操作。在这个例子中,我们将使用一个简单的平均滤波器(均值滤波)对图像进行卷积,以实现图像的平滑效果。

import cv2
import numpy as np# 读取图像
image = cv2.imread("tulips.jpg")def add_colored_noise(image, intensity=50):row, col, ch = image.shapecolored_noise = np.random.randint(-intensity, intensity, (row, col, ch))noisy_image = np.clip(image + colored_noise, 0, 255)return noisy_image.astype(np.uint8)# 生成带有彩色噪声的图像
noisy_image = add_colored_noise(image)
# 定义一个均值滤波器(卷积核)
kernel_size = (5, 5)
kernel = np.ones(kernel_size, np.float32) / (kernel_size[0] * kernel_size[1])
# 应用卷积操作
convolved_image = cv2.filter2D(noisy_image, -1, kernel)# 在每张图像的左上角添加文字描述
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 1
thickness = 1
padding = 30
color = tuple((0, 255, 0))cv2.putText(image, 'Original Image', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)
cv2.putText(noisy_image, 'Noisy Image', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)
cv2.putText(convolved_image, 'Convolved Image', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)# 显示原始图像和卷积操作的结果
cv2.imshow("Convolved Image", cv2.hconcat([image, noisy_image, convolved_image]))
cv2.waitKey(0)
cv2.destroyAllWindows()

Convolved Image

在上述代码中,cv2.filter2D 函数接受图像、数据类型和卷积核作为参数,然后应用卷积操作。在这个例子中,我们使用了一个简单的均值滤波器,但根据任务的不同,可以选择不同的卷积核来实现不同的图像处理效果。

更多卷积操作请参考卷积操作快速入门

三、线性滤波

在图像处理中,线性滤波是一种通过卷积操作对图像进行平滑处理的技术,其主要目的是去除图像中的噪声。本节将详细介绍四种常见的线性滤波方法:均值滤波、方框滤波、高斯滤波和可分离滤波,并使用 OpenCV 编写示例代码来演示它们的应用。

3.1 均值滤波

均值滤波原理

均值滤波是一种平滑图像的方法,它基于一个简单的思想:用图像中某一像素点周围邻域的像素值的平均值来代替该像素点的值。这个邻域可以是一个矩形、圆形或者其他形状的区域,其大小由滤波器的大小决定。

均值滤波公式

对于一个大小为 m × n m \times n m×n 的滤波器,其中心位于图像的像素位置 ( x , y ) (x, y) (x,y),均值滤波的公式可以表示为:

Output ( x , y ) = 1 m n ∑ i = 0 m − 1 ∑ j = 0 n − 1 Input ( x + i − ⌊ m / 2 ⌋ , y + j − ⌊ n / 2 ⌋ ) \text{Output}(x, y) = \frac{1}{mn} \sum_{i=0}^{m-1} \sum_{j=0}^{n-1} \text{Input}(x + i - \lfloor m/2 \rfloor, y + j - \lfloor n/2 \rfloor) Output(x,y)=mn1i=0m1j=0n1Input(x+im/2,y+jn/2⌋)

其中:

  • Output ( x , y ) \text{Output}(x, y) Output(x,y) 是滤波后图像的像素值。
  • Input ( x + i − ⌊ m / 2 ⌋ , y + j − ⌊ n / 2 ⌋ ) \text{Input}(x + i - \lfloor m/2 \rfloor, y + j - \lfloor n/2 \rfloor) Input(x+im/2,y+jn/2⌋) 是原始图像中邻域内像素的值。
  • m m m n n n 是滤波器的大小, ⌊ ⋅ ⌋ \lfloor \cdot \rfloor 表示向下取整。

这个公式表示了在滤波器覆盖的区域内,取所有像素的平均值作为中心像素的新值,从而实现图像的平滑效果。

均值滤波代码实现

下面是使用 OpenCV 实现均值滤波的示例代码:

import cv2
import numpy as np# 读取图像
image = cv2.imread("tulips.jpg")def add_colored_noise(image, intensity=50):row, col, ch = image.shapecolored_noise = np.random.randint(-intensity, intensity, (row, col, ch))noisy_image = np.clip(image + colored_noise, 0, 255)return noisy_image.astype(np.uint8)# 生成带有彩色噪声的图像
noisy_image = add_colored_noise(image)
# 应用均值滤波
kernel_size = (5, 5)
blurred_image = cv2.blur(noisy_image, kernel_size)# 在每张图像的左上角添加文字描述
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 1
thickness = 1
padding = 30
color = tuple((0, 255, 0))cv2.putText(image, 'Original Image', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)
cv2.putText(noisy_image, 'Noisy Image', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)
cv2.putText(blurred_image, 'Blurred Image (Mean)', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)# 显示原始图像和经过均值滤波的图像
cv2.imshow("Blurred Image (Mean)", cv2.hconcat([image, noisy_image, blurred_image]))
cv2.waitKey(0)
cv2.destroyAllWindows()

Blurred Image (Mean)

在上述代码中,cv2.blur 函数接受图像和卷积核的大小作为参数,然后应用均值滤波。

3.2 方框滤波

方框滤波原理

方框滤波是一种线性滤波方法,类似于均值滤波,它通过在滤波器窗口内对像素进行加权平均来减小图像噪声。

方框滤波公式

对于一个大小为 m × n m \times n m×n 的方框滤波器,其中心位于图像的像素位置 ( x , y ) (x, y) (x,y),方框滤波的公式可以表示为:

Output ( x , y ) = 1 kernel_size ∑ i = 0 m − 1 ∑ j = 0 n − 1 Input ( x + i − ⌊ m / 2 ⌋ , y + j − ⌊ n / 2 ⌋ ) \text{Output}(x, y) = \frac{1}{\text{kernel\_size}} \sum_{i=0}^{m-1} \sum_{j=0}^{n-1} \text{Input}(x + i - \lfloor m/2 \rfloor, y + j - \lfloor n/2 \rfloor) Output(x,y)=kernel_size1i=0m1j=0n1Input(x+im/2,y+jn/2⌋)

其中:

  • Output ( x , y ) \text{Output}(x, y) Output(x,y) 是滤波后图像的像素值。
  • Input ( x + i − ⌊ m / 2 ⌋ , y + j − ⌊ n / 2 ⌋ ) \text{Input}(x + i - \lfloor m/2 \rfloor, y + j - \lfloor n/2 \rfloor) Input(x+im/2,y+jn/2⌋) 是原始图像中邻域内像素的值。
  • m m m n n n 是方框滤波器的大小, ⌊ ⋅ ⌋ \lfloor \cdot \rfloor 表示向下取整。
  • kernel_size = m × n \text{kernel\_size} = m \times n kernel_size=m×n 是滤波器的大小,表示权重的总和。

方框滤波器中的每个像素都具有相同的权重,这与均值滤波类似。这种滤波器在减小噪声的同时会导致图像的细节丢失。

方框滤波代码实现

下面是使用 OpenCV 实现方框滤波的示例代码:

import cv2
import numpy as np# 读取图像
image = cv2.imread("tulips.jpg")def add_colored_noise(image, intensity=50):row, col, ch = image.shapecolored_noise = np.random.randint(-intensity, intensity, (row, col, ch))noisy_image = np.clip(image + colored_noise, 0, 255)return noisy_image.astype(np.uint8)# 生成带有彩色噪声的图像
noisy_image = add_colored_noise(image)
# 应用方框滤波
kernel_size = (5, 5)
box_filtered_image = cv2.boxFilter(noisy_image, -1, kernel_size, normalize=1)# 在每张图像的左上角添加文字描述
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 1
thickness = 1
padding = 30
color = tuple((0, 255, 0))cv2.putText(image, 'Original Image', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)
cv2.putText(noisy_image, 'Noisy Image', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)
cv2.putText(box_filtered_image, 'Filtered Image (Box)', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)# 显示原始图像和经过方框滤波的图像
cv2.imshow("Filtered Image (Box)", cv2.hconcat([image, noisy_image, box_filtered_image]))
cv2.waitKey(0)
cv2.destroyAllWindows()

Filtered Image (Box)

在OpenCV的boxFilter函数中,normalize参数控制是否对方框滤波的结果进行归一化。

下面解释一下normalize=1normalize=0的情况:

  1. normalize=1: 当normalize参数设置为1时,方框滤波器的结果会进行归一化,即除以方框滤波器窗口内所有像素的权重之和。这可以防止输出像素值超过原始范围(0到255),从而保持图像的亮度一致性。归一化后的输出像素值计算公式见上面的公式。

  2. normalize=0: 当normalize参数设置为0时,方框滤波器的结果不进行归一化,即直接将窗口内像素值的和作为输出像素值。这可能导致输出像素值超过原始范围,因此在使用时需要注意。这种情况适用于特定需求,例如在不关心亮度一致性的情况下。

综上,选择normalize参数的值取决于具体的应用需求和对输出图像范围的要求。通常情况下,如果希望输出图像的亮度与原始图像一致,建议使用normalize=1

3.3 高斯滤波

高斯滤波原理

高斯滤波是一种线性滤波方法,它使用高斯函数作为核函数对图像进行卷积。高斯滤波的主要思想是对图像的每个像素赋予一个权重,权重由高斯分布函数决定,距离中心像素越远的像素拥有更小的权重。

高斯滤波公式

对于一个大小为 m × n m \times n m×n 的高斯滤波器,其中心位于图像的像素位置 ( x , y ) (x, y) (x,y),高斯滤波的公式可以表示为:

Output ( x , y ) = 1 ∑ i = 0 m − 1 ∑ j = 0 n − 1 G ( i , j , σ ) ∑ i = 0 m − 1 ∑ j = 0 n − 1 G ( i , j , σ ) ⋅ Input ( x + i − ⌊ m / 2 ⌋ , y + j − ⌊ n / 2 ⌋ ) \text{Output}(x, y) = \frac{1}{\sum_{i=0}^{m-1} \sum_{j=0}^{n-1} G(i, j, \sigma)} \sum_{i=0}^{m-1} \sum_{j=0}^{n-1} G(i, j, \sigma) \cdot \text{Input}(x + i - \lfloor m/2 \rfloor, y + j - \lfloor n/2 \rfloor) Output(x,y)=i=0m1j=0n1G(i,j,σ)1i=0m1j=0n1G(i,j,σ)Input(x+im/2,y+jn/2⌋)

其中:

  • Output ( x , y ) \text{Output}(x, y) Output(x,y) 是滤波后图像的像素值。
  • Input ( x + i − ⌊ m / 2 ⌋ , y + j − ⌊ n / 2 ⌋ ) \text{Input}(x + i - \lfloor m/2 \rfloor, y + j - \lfloor n/2 \rfloor) Input(x+im/2,y+jn/2⌋) 是原始图像中邻域内像素的值。
  • m m m n n n 是高斯滤波器的大小, ⌊ ⋅ ⌋ \lfloor \cdot \rfloor 表示向下取整。
  • G ( i , j , σ ) G(i, j, \sigma) G(i,j,σ) 是高斯分布函数,表示离中心像素 ( 0 , 0 ) (0, 0) (0,0) 的偏移为 ( i , j ) (i, j) (i,j) 的权重, σ \sigma σ 是高斯函数的标准差。

高斯滤波通过调整标准差 σ \sigma σ 的值可以控制滤波器的形状,较大的 σ \sigma σ 值会使滤波器更加平滑,而较小的 σ \sigma σ 值则会保留更多细节。

高斯滤波代码实现

下面是使用 OpenCV 实现高斯滤波的示例代码:

import cv2
import numpy as np# 读取图像
image = cv2.imread("tulips.jpg")def add_colored_noise(image, intensity=50):row, col, ch = image.shapecolored_noise = np.random.randint(-intensity, intensity, (row, col, ch))noisy_image = np.clip(image + colored_noise, 0, 255)return noisy_image.astype(np.uint8)# 生成带有彩色噪声的图像
noisy_image = add_colored_noise(image)
# 应用高斯滤波
kernel_size = (5, 5)
sigma = 1.5
gaussian_blurred_image = cv2.GaussianBlur(noisy_image, kernel_size, sigma)# 在每张图像的左上角添加文字描述
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 1
thickness = 1
padding = 30
color = tuple((0, 255, 0))cv2.putText(image, 'Original Image', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)
cv2.putText(noisy_image, 'Noisy Image', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)
cv2.putText(gaussian_blurred_image, 'Blurred Image (Gaussian)', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)# 显示原始图像和经过高斯滤波的图像
cv2.imshow("Blurred Image (Gaussian)", cv2.hconcat([image, noisy_image, gaussian_blurred_image]))
cv2.waitKey(0)
cv2.destroyAllWindows()

Blurred Image (Gaussian)

在上述代码中,cv2.GaussianBlur 函数接受图像、卷积核的大小和高斯核的标准差作为参数,然后应用高斯滤波。

3.4 可分离滤波

可分离滤波原理

可分离滤波是一种优化的滤波方法,它将二维滤波操作分解为两个一维滤波操作,从而降低了计算复杂度。这种分解的思想基于卷积操作的结合律,即二维卷积可以分解为先在水平方向进行一维卷积,然后在垂直方向进行一维卷积。

可分离滤波公式

对于一个大小为 m × n m \times n m×n 的可分离滤波器,其中心位于图像的像素位置 ( x , y ) (x, y) (x,y),可分离滤波的公式可以表示为:

Output ( x , y ) = ∑ i = 0 m − 1 H ( i ) ⋅ ( ∑ j = 0 n − 1 H ( j ) ⋅ Input ( x + i − ⌊ m / 2 ⌋ , y + j − ⌊ n / 2 ⌋ ) ) \text{Output}(x, y) = \sum_{i=0}^{m-1} H(i) \cdot \left( \sum_{j=0}^{n-1} H(j) \cdot \text{Input}(x + i - \lfloor m/2 \rfloor, y + j - \lfloor n/2 \rfloor) \right) Output(x,y)=i=0m1H(i)(j=0n1H(j)Input(x+im/2,y+jn/2⌋))

其中:

  • Output ( x , y ) \text{Output}(x, y) Output(x,y) 是滤波后图像的像素值。
  • Input ( x + i − ⌊ m / 2 ⌋ , y + j − ⌊ n / 2 ⌋ ) \text{Input}(x + i - \lfloor m/2 \rfloor, y + j - \lfloor n/2 \rfloor) Input(x+im/2,y+jn/2⌋) 是原始图像中邻域内像素的值。
  • m m m n n n 是可分离滤波器的大小, ⌊ ⋅ ⌋ \lfloor \cdot \rfloor 表示向下取整。
  • H ( i ) H(i) H(i) H ( j ) H(j) H(j) 是水平和垂直方向的一维滤波核。

通过这种分解,可分离滤波器的计算量大大减少,因为原始的二维卷积操作被拆分为两个一维卷积操作。这在实际应用中提高了滤波的效率,特别是对于大尺寸的滤波器。

可分离滤波代码实现

下面是使用 OpenCV 实现可分离滤波的示例代码:

import cv2
import numpy as np# 读取图像
image = cv2.imread("tulips.jpg")def add_colored_noise(image, intensity=50):row, col, ch = image.shapecolored_noise = np.random.randint(-intensity, intensity, (row, col, ch))noisy_image = np.clip(image + colored_noise, 0, 255)return noisy_image.astype(np.uint8)# 生成带有彩色噪声的图像
noisy_image = add_colored_noise(image)
# 定义一个一维滤波核
kernel_size = 5
kernel_1d = cv2.getGaussianKernel(kernel_size, -1)# 将一维核分解为水平和垂直核
kernel_2d = np.outer(kernel_1d, kernel_1d.T)# 应用可分离滤波
separable_filtered_image = cv2.filter2D(noisy_image, -1, kernel_2d)# 在每张图像的左上角添加文字描述
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 1
thickness = 1
padding = 30
color = tuple((0, 255, 0))cv2.putText(image, 'Original Image', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)
cv2.putText(noisy_image, 'Noisy Image', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)
cv2.putText(separable_filtered_image, 'Filtered Image (Separable)', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)# 显示原始图像和经过可分离滤波的图像
cv2.imshow("Filtered Image (Separable)", cv2.hconcat([image, noisy_image, separable_filtered_image]))
cv2.waitKey(0)
cv2.destroyAllWindows()

Filtered Image (Separable)

在上述代码中,首先使用 cv2.getGaussianKernel 获取一个一维的高斯核,然后通过 np.outer 函数将其分解为二维核,最后应用可分离滤波。

四、非线性滤波

在图像处理中,非线性滤波是一种通过非线性操作对图像进行处理的方法,主要用于去除各种类型的噪声。本节将详细介绍两种常见的非线性滤波方法:中值滤波和双边滤波。

4.1 中值滤波

中值滤波原理

中值滤波是一种非线性滤波方法,它的基本原理是用像素邻域中的中值来代替该像素的灰度值。这种方法对于去除椒盐噪声(图像中突然出现的亮或暗的像素点)效果较好,因为中值对异常值不敏感。

  1. 邻域选择: 对于每个像素,选择一个邻域,通常是一个正方形或矩形区域,包含该像素及其周围的一些邻近像素。

  2. 中值计算: 在选定的邻域中,将像素的灰度值按大小排序,然后选择中间值作为该像素的新灰度值。

  3. 替换: 将原始像素的值替换为计算得到的中值。

中值滤波公式

假设有一个大小为 m × n m \times n m×n 的邻域,其中 m m m 表示邻域的行数, n n n 表示邻域的列数。对于位置 ( i , j ) (i, j) (i,j) 的像素,中值滤波的公式可以表示为:

I med ( i , j ) = median { I ( p , q ) ∣ i − m 2 ≤ p ≤ i + m 2 , j − n 2 ≤ q ≤ j + n 2 } I_{\text{med}}(i, j) = \text{median}\left\{ I(p, q) \mid i-\frac{m}{2} \leq p \leq i+\frac{m}{2},\; j-\frac{n}{2} \leq q \leq j+\frac{n}{2} \right\} Imed(i,j)=median{I(p,q)i2mpi+2m,j2nqj+2n}

其中,

  • I med ( i , j ) I_{\text{med}}(i, j) Imed(i,j) 是中值滤波后得到的像素值。
  • I ( p , q ) I(p, q) I(p,q) 是位置 ( p , q ) (p, q) (p,q) 处的原始像素值。
  • median 表示中值运算,即将一组值按大小排序后选择中间的值。

这个公式表示,对于图像中的每个像素,选择其周围邻域内像素值的中值作为新的像素值。这样的操作对于消除椒盐噪声等突发性噪声效果较好。

中值滤波代码实现

下面是使用 OpenCV 实现中值滤波的示例代码:

import cv2
import numpy as np# 读取图像
image = cv2.imread("tulips.jpg")def add_colored_noise(image, intensity=50):row, col, ch = image.shapecolored_noise = np.random.randint(-intensity, intensity, (row, col, ch))noisy_image = np.clip(image + colored_noise, 0, 255)return noisy_image.astype(np.uint8)# 生成带有彩色噪声的图像
noisy_image = add_colored_noise(image)# 应用中值滤波
kernel_size = 3  # 可根据需要调整核的大小
median_filtered_image = cv2.medianBlur(noisy_image, kernel_size)# 在每张图像的左上角添加文字描述
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 1
thickness = 1
padding = 30
color = tuple((0, 255, 0))cv2.putText(image, 'Original Image', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)
cv2.putText(noisy_image, 'Noisy Image', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)
cv2.putText(median_filtered_image, 'Filtered Image (Median)', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)# 显示原始图像和经过中值滤波的图像
cv2.imshow("Filtered Image (Median)",  cv2.hconcat([image, noisy_image, median_filtered_image]))
cv2.waitKey(0)
cv2.destroyAllWindows()

Filtered Image (Median)

在上述代码中,cv2.medianBlur 函数接受图像和核的大小作为参数,然后应用中值滤波。

4.2 双边滤波

双边滤波原理

双边滤波是一种结合了空间域和灰度值域信息的滤波方法。它考虑了像素之间的空间距离和像素值之间的差异,以达到既去噪又保留图像细节的目的。这对于保留边缘信息、维持图像的整体结构很有用。

  1. 空间域权重: 双边滤波首先计算像素之间的空间距离,距离越近的像素,其权重越大。这一部分确保了在滤波过程中考虑到像素的相邻关系。

  2. 灰度值域权重: 双边滤波还考虑了像素值之间的差异。如果两个像素在灰度值上差异很大,它们的权重会相应减小。这一部分确保了在滤波过程中对于边缘区域的保护。

  3. 综合权重: 将空间域权重和灰度值域权重相乘,得到综合的权重。

  4. 滤波: 使用计算得到的权重对邻域内的像素进行加权平均,得到最终的滤波结果。

双边滤波公式

对于图像中的每个像素 ( i , j ) (i, j) (i,j),双边滤波的计算可以表示为:

I bf ( i , j ) = 1 W p ∑ p = 1 m ∑ q = 1 n w ( i − p , j − q , σ s ) ⋅ w ( I ( i , j ) − I ( p , q ) , σ r ) ⋅ I ( p , q ) I_{\text{bf}}(i, j) = \frac{1}{W_{\text{p}}}\sum_{p=1}^{m}\sum_{q=1}^{n} w(i-p, j-q, \sigma_s) \cdot w(I(i, j) - I(p, q), \sigma_r) \cdot I(p, q) Ibf(i,j)=Wp1p=1mq=1nw(ip,jq,σs)w(I(i,j)I(p,q),σr)I(p,q)

其中,

  • I bf ( i , j ) I_{\text{bf}}(i, j) Ibf(i,j) 是双边滤波后得到的像素值。
  • W p W_{\text{p}} Wp 是归一化因子,确保权重和为1。
  • w ( ⋅ , ⋅ ) w(\cdot, \cdot) w(,) 是权重函数,分别表示空间域权重和灰度值域权重。
  • I ( i , j ) I(i, j) I(i,j) 是位置 ( i , j ) (i, j) (i,j) 处的原始像素值。
  • I ( p , q ) I(p, q) I(p,q) 表示位置 ( p , q ) (p, q) (p,q) 处的原始像素值。
  • m m m n n n 是邻域的大小。
  • σ s \sigma_s σs σ r \sigma_r σr 是控制空间域和灰度值域权重的两个参数。

这个公式表示,对于图像中的每个像素,通过计算空间域和灰度值域的权重,对邻域内的像素进行加权平均,以得到最终的滤波结果。这样可以在去噪的同时保留图像的边缘信息。

双边滤波代码实现

下面是使用 OpenCV 实现双边滤波的示例代码:

import cv2
import numpy as np# 读取图像
image = cv2.imread("tulips.jpg")def add_colored_noise(image, intensity=50):row, col, ch = image.shapecolored_noise = np.random.randint(-intensity, intensity, (row, col, ch))noisy_image = np.clip(image + colored_noise, 0, 255)return noisy_image.astype(np.uint8)# 生成带有彩色噪声的图像
noisy_image = add_colored_noise(image)
# 应用双边滤波
d = 15  # 控制像素值相似性的参数,可根据需要调整
sigma_color = 75  # 控制空间相似性的参数,可根据需要调整
bilateral_filtered_image = cv2.bilateralFilter(noisy_image, d, sigma_color, sigma_color)# 在每张图像的左上角添加文字描述
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 1
thickness = 1
padding = 30
color = tuple((0, 255, 0))cv2.putText(image, 'Original Image', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)
cv2.putText(noisy_image, 'Noisy Image', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)
cv2.putText(bilateral_filtered_image, 'Filtered Image (Bilateral)', (padding, padding), font, font_scale, color, thickness, cv2.LINE_AA)# 显示原始图像和经过双边滤波的图像
cv2.imshow("Filtered Image (Bilateral)",  cv2.hconcat([image, noisy_image, bilateral_filtered_image]))
cv2.waitKey(0)
cv2.destroyAllWindows()

Filtered Image (Bilateral)

在上述代码中,cv2.bilateralFilter 函数接受图像、像素值相似性参数 d、空间相似性参数 sigma_color 作为参数,然后应用双边滤波。调整这些参数可以影响滤波效果。

五、 边缘检测

边缘检测用于寻找图像中的物体边界,为后续的分析和识别提供了重要的信息。
本节将详细介绍四种常见的边缘检测算子:Sobel算子、Scharr算子、Laplacian算子和Canny算子,并介绍生成自定义边缘检测滤波器的方法。

5.1 Sobel算子

Sobel算子原理

Sobel算子是一种基于卷积操作的边缘检测算子,常用于图像处理中。它的基本原理是通过对图像进行卷积操作,计算每个像素点的梯度,从而突出图像中的边缘信息。Sobel算子分为水平方向和垂直方向两种,分别用于检测图像中的水平边缘和垂直边缘。

Sobel算子公式

垂直方向的Sobel算子(Gy)

G y = [ − 1 − 2 − 1 0 0 0 1 2 1 ] G_y = \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{bmatrix} Gy= 101202101

这个矩阵即为垂直方向的Sobel算子。通过将这个矩阵与图像进行卷积,可以得到每个像素在垂直方向上的梯度。

对于图像中的每个像素 ( i , j ) (i, j) (i,j),计算垂直梯度的公式为:

G y ( i , j ) = ∑ m = − 1 1 ∑ n = − 1 1 G y ( m , n ) ⋅ I ( i + m , j + n ) G_y(i, j) = \sum_{m=-1}^{1}\sum_{n=-1}^{1} G_y(m, n) \cdot I(i+m, j+n) Gy(i,j)=m=11n=11Gy(m,n)I(i+m,j+n)

其中, G y ( m , n ) G_y(m, n) Gy(m,n) 是Sobel算子矩阵中位于位置 ( m , n ) (m, n) (m,n) 处的元素, I ( i + m , j + n ) I(i+m, j+n) I(i+m,j+n) 是图像中位置 ( i + m , j + n ) (i+m, j+n) (i+m,j+n) 处的像素值。这个卷积操作实际上就是将Sobel算子与图像进行逐像素的乘积和求和。

计算垂直梯度的示例:

G y ( i , j ) = ( − 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 ) G_y(i, j) = (-1) \cdot I(i-1, j-1) + (-2) \cdot I(i, j-1) + (-1) \cdot I(i+1, j-1) +\\ 0 \cdot I(i-1, j) + 0 \cdot I(i, j) + 0 \cdot I(i+1, j) + \\ 1 \cdot I(i-1, j+1) + 2 \cdot I(i, j+1) + 1 \cdot I(i+1, j+1) Gy(i,j)=(1)I(i1,j1)+(2)I(i,j1)+(1)I(i+1,j1)+0I(i1,j)+0I(i,j)+0I(i+1,j)+1I(i1,j+1)+2I(i,j+1)+1I(i+1,j+1)

水平方向的Sobel算子(Gx)

G x = [ − 1 0 1 − 2 0 2 − 1 0 1 ] G_x = \begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{bmatrix} Gx= 121000121

这个矩阵即为水平方向的Sobel算子。通过将这个矩阵与图像进行卷积,可以得到每个像素在水平方向上的梯度。

对于图像中的每个像素 ( i , j ) (i, j) (i,j),梯度的计算可以使用矩阵表示:

G x ( i , j ) = ∑ m = − 1 1 ∑ n = − 1 1 G x ( m , n ) ⋅ I ( i + m , j + n ) G_x(i, j) = \sum_{m=-1}^{1}\sum_{n=-1}^{1} G_x(m, n) \cdot I(i+m, j+n) Gx(i,j)=m=11n=11Gx(m,n)I(i+m,j+n)

其中, G x ( m , n ) G_x(m, n) Gx(m,n) 是Sobel算子矩阵中位于位置 ( m , n ) (m, n) (m,n) 处的元素, I ( i + m , j + n ) I(i+m, j+n) I(i+m,j+n) 是图像中位置 ( i + m , j + n ) (i+m, j+n) (i+m,j+n) 处的像素值。这个卷积操作实际上就是将Sobel算子与图像进行逐像素的乘积和求和。

计算水平梯度的的示例:

G x ( i , j ) = ( − 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 ) G_x(i, j) = (-1) \cdot I(i-1, j-1) + 0 \cdot I(i, j-1) + 1 \cdot I(i+1, j-1) + \\ (-2) \cdot I(i-1, j) + 0 \cdot I(i, j) + 2 \cdot I(i+1, j) + \\ (-1) \cdot I(i-1, j+1) + 0 \cdot I(i, j+1) + 1 \cdot I(i+1, j+1) Gx(i,j)=(1)I(i1,j1)+0I(i,j1)+1I(i+1,j1)+(2)I(i1,j)+0I(i,j)+2I(i+1,j)+(1)I(i1,j+1)+0I(i,j+1)+1I(i+1,j+1)

计算梯度

梯度的计算是Sobel算子在边缘检测中的一个关键步骤。通过在图像上应用水平方向的Sobel算子 G x G_x Gx 和垂直方向的Sobel算子 G y G_y Gy,可以得到每个像素点在水平和垂直方向上的梯度。然后,利用这些梯度值,可以计算梯度的大小和方向。

计算梯度大小
梯度大小表示边缘的强度,可以通过以下公式计算:

Gradient magnitude = G x 2 + G y 2 \text{Gradient magnitude} = \sqrt{G_x^2 + G_y^2} Gradient magnitude=Gx2+Gy2

其中, G x G_x Gx G y G_y Gy 分别是水平和垂直方向上的梯度。

计算梯度方向

梯度方向表示边缘的方向,可以通过以下公式计算:

Gradient direction = arctan ⁡ ( G y G x ) \text{Gradient direction} = \arctan\left(\frac{G_y}{G_x}\right) Gradient direction=arctan(GxGy)

这个公式使用反正切函数,计算 G y G_y Gy G x G_x Gx 的比值,得到的角度表示梯度的方向。

通过这两个公式,可以得到每个像素点的梯度大小和方向。在边缘检测中,梯度较大的地方通常对应着图像中的边缘,而梯度方向则指示着边缘的方向。这些信息对于分割图像中的目标、检测边缘等应用非常有用。

Sobel算子代码实现

下面是使用 OpenCV 实现Sobel算子的示例代码:

import cv2
import numpy as np# 读取图像
image = cv2.imread("tulips.jpg")# 应用Sobel算子
sobel_x = cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(image, cv2.CV_64F, 0, 1, ksize=3)# 计算梯度幅值和方向
gradient_magnitude = np.sqrt(sobel_x**2 + sobel_y**2)
gradient_direction = np.arctan2(sobel_y, sobel_x)# # 分别显示原始图像和Sobel算子的结果
# cv2.imshow("Original Image", image)
# cv2.imshow("Gradient Magnitude", cv2.convertScaleAbs(gradient_magnitude))
# cv2.imshow("Gradient Direction", gradient_direction)
# cv2.imshow("Sobel X", cv2.convertScaleAbs(sobel_x))
# cv2.imshow("Sobel Y", cv2.convertScaleAbs(sobel_y))
# cv2.imshow("Sobel X+Y", cv2.convertScaleAbs(sobel_x)+cv2.convertScaleAbs(sobel_y))# 将浮点数图像缩放到0到255的范围
normal_gradient_direction = cv2.normalize(gradient_direction, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)# 共享的参数
shared_params = {"org": (10, 30),"fontFace": cv2.FONT_HERSHEY_SIMPLEX,"fontScale": 1,"thickness": 2,"color": (0, 255, 0),"lineType": cv2.LINE_AA,
}
# 添加文字
original_image = cv2.putText(image.copy(), "Original Image",**shared_params)
gradient_magnitude_image = cv2.putText(cv2.convertScaleAbs(gradient_magnitude.copy()), "Gradient Magnitude", **shared_params)
gradient_direction_image = cv2.putText(normal_gradient_direction, "Gradient Direction", **shared_params)
sobel_x_image = cv2.putText(cv2.convertScaleAbs(sobel_x.copy()), "Sobel X", **shared_params)
sobel_y_image = cv2.putText(cv2.convertScaleAbs(sobel_y.copy()), "Sobel Y", **shared_params)
sobel_xy_image = cv2.putText(cv2.convertScaleAbs(sobel_x + sobel_y), "Sobel X+Y", **shared_params)# 水平拼接
row1 = cv2.hconcat([original_image, gradient_magnitude_image, gradient_direction_image])
row2 = cv2.hconcat([sobel_x_image, sobel_y_image, sobel_xy_image])# 垂直拼接
sobel_image = cv2.vconcat([row1, row2])# 显示合并后的图像
cv2.imshow("Sobel Images", sobel_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Sobel Images

在上述代码中,cv2.Sobel 函数接受图像、数据类型、x和y方向的导数、以及卷积核大小作为参数,然后分别计算x和y方向的梯度,最后计算梯度幅值和方向。

5.2 Scharr算子

Scharr算子原理

Scharr算子是一种用于边缘检测的算子,类似于Sobel算子,但其卷积核设计更为复杂,旨在更敏感地捕捉图像中的细节。Scharr算子的目标是对图像的变化更敏感,尤其是对于细小的、低频的边缘。

Scharr算子公式

水平方向的Scharr算子(Gx)

G x = [ − 3 − 10 − 3 0 0 0 3 10 3 ] G_x = \begin{bmatrix} -3 & -10 & -3 \\ 0 & 0 & 0 \\ 3 & 10 & 3 \end{bmatrix} Gx= 30310010303

垂直方向的Scharr算子(Gy)

G y = [ − 3 0 3 − 10 0 10 − 3 0 3 ] G_y = \begin{bmatrix} -3 & 0 & 3 \\ -10 & 0 & 10 \\ -3 & 0 & 3 \end{bmatrix} Gy= 31030003103

与Sobel算子相比,Scharr算子的卷积核中的权重更为平滑和对称,这使得它对图像中的高频细节更敏感。因此,在一些需要更好细节捕捉的应用场景下,Scharr算子可能表现得比Sobel算子更优秀。

计算梯度

计算梯度的方法与Sobel算子类似,通过将Scharr算子与图像进行卷积操作,分别得到水平方向 G x G_x Gx 和垂直方向 G y G_y Gy 上的梯度。然后,可以使用以下公式计算梯度的大小和方向:

Gradient magnitude = G x 2 + G y 2 \text{Gradient magnitude} = \sqrt{G_x^2 + G_y^2} Gradient magnitude=Gx2+Gy2

Gradient direction = arctan ⁡ ( G y G x ) \text{Gradient direction} = \arctan\left(\frac{G_y}{G_x}\right) Gradient direction=arctan(GxGy)

在一些图像处理任务中,Scharr算子在边缘检测中的性能可能会略优于Sobel算子,尤其是在需要更好的细节保留和对细小边缘更敏感的情况下。

Scharr算子代码实现

下面是使用 OpenCV 实现Scharr算子的示例代码:

import cv2
import numpy as np# 读取图像
image = cv2.imread("tulips.jpg")# 应用Scharr算子
scharr_x = cv2.Scharr(image, cv2.CV_64F, 1, 0)
scharr_y = cv2.Scharr(image, cv2.CV_64F, 0, 1)# 计算梯度幅值和方向
gradient_magnitude = np.sqrt(scharr_x**2 + scharr_y**2)
gradient_direction = np.arctan2(scharr_y, scharr_x)# # 分别显示原始图像和Sobel算子的结果
# cv2.imshow("Original Image", image)
# cv2.imshow("Gradient Magnitude", cv2.convertScaleAbs(gradient_magnitude))
# cv2.imshow("Gradient Direction", gradient_direction)
# cv2.imshow("Scharr X", cv2.convertScaleAbs(scharr_x))
# cv2.imshow("Scharr Y", cv2.convertScaleAbs(scharr_y))
# cv2.imshow("Sobel X+Y", cv2.convertScaleAbs(scharr_x)+cv2.convertScaleAbs(scharr_y))# 将浮点数图像缩放到0到255的范围
normal_gradient_direction = cv2.normalize(gradient_direction, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)# 共享的参数
shared_params = {"org": (10, 30),"fontFace": cv2.FONT_HERSHEY_SIMPLEX,"fontScale": 1,"thickness": 2,"color": (0, 255, 0),"lineType": cv2.LINE_AA,
}
# 添加文字
original_image = cv2.putText(image.copy(), "Original Image",**shared_params)
gradient_magnitude_image = cv2.putText(cv2.convertScaleAbs(gradient_magnitude.copy()), "Gradient Magnitude", **shared_params)
gradient_direction_image = cv2.putText(normal_gradient_direction, "Gradient Direction", **shared_params)
scharr_x_image = cv2.putText(cv2.convertScaleAbs(scharr_x.copy()), "Scharr X", **shared_params)
scharr_y_image = cv2.putText(cv2.convertScaleAbs(scharr_y.copy()), "Scharr Y", **shared_params)
scharr_xy_image = cv2.putText(cv2.convertScaleAbs(scharr_x + scharr_y), "Scharr X+Y", **shared_params)# 水平拼接
row1 = cv2.hconcat([original_image, gradient_magnitude_image, gradient_direction_image])
row2 = cv2.hconcat([scharr_x_image, scharr_y_image, scharr_xy_image])# 垂直拼接
scharr_image = cv2.vconcat([row1, row2])# 显示合并后的图像
cv2.imshow("Scharr Images", scharr_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Scharr Images

与Sobel算子类似,cv2.Scharr 函数用于应用Scharr算子,其余步骤与Sobel算子相似。

5.3 Laplacian算子

Laplacian算子原理

Laplacian算子是一种边缘检测算子,它通过计算图像的二阶导数来突出图像中的边缘信息。该算子对图像中的灰度变化较大的区域有很好的响应,因此常用于边缘检测和图像锐化。

Laplacian算子公式

Laplacian算子可以用以下卷积核表示:

∇ 2 = [ 0 1 0 1 − 4 1 0 1 0 ] \nabla^2 = \begin{bmatrix} 0 & 1 & 0 \\ 1 & -4 & 1 \\ 0 & 1 & 0 \end{bmatrix} 2= 010141010

这个卷积核用于计算图像中每个像素点的二阶导数。对于图像中的每个像素 ( i , j ) (i, j) (i,j),使用以下公式计算Laplacian算子的响应:

∇ 2 ( i , j ) = ∑ m = − 1 1 ∑ n = − 1 1 ∇ 2 ( m , n ) ⋅ I ( i + m , j + n ) \nabla^2(i, j) = \sum_{m=-1}^{1}\sum_{n=-1}^{1} \nabla^2(m, n) \cdot I(i+m, j+n) 2(i,j)=m=11n=112(m,n)I(i+m,j+n)

其中, ∇ 2 ( m , n ) \nabla^2(m, n) 2(m,n) 是Laplacian算子卷积核中位于位置 ( m , n ) (m, n) (m,n) 处的权重, I ( i + m , j + n ) I(i+m, j+n) I(i+m,j+n) 是图像中位置 ( i + m , j + n ) (i+m, j+n) (i+m,j+n) 处的像素值。

计算结果

Laplacian算子的计算结果表示了图像中每个像素点的强度变化。对于图像中的边缘,Laplacian算子的响应通常呈现极值,可以通过阈值处理来检测边缘。此外,Laplacian算子对图像中的细节和纹理也具有一定的灵敏度。

在实际应用中,可以通过调整阈值来控制检测到的边缘的数量和强度。

Laplacian算子代码实现

下面是使用 OpenCV 实现Laplacian算子的示例代码:

import cv2# 读取图像
image = cv2.imread("tulips.jpg")# 应用Laplacian算子
laplacian = cv2.Laplacian(image, cv2.CV_64F)# 共享的参数
shared_params = {"org": (10, 30),"fontFace": cv2.FONT_HERSHEY_SIMPLEX,"fontScale": 1,"thickness": 2,"color": (0, 255, 0),"lineType": cv2.LINE_AA,
}
# 添加文字
original_image = cv2.putText(image.copy(), "Original Image", **shared_params)
laplacian_image = cv2.putText(cv2.convertScaleAbs(laplacian), "Laplacian Image", **shared_params)# 显示原始图像和Laplacian算子的结果
cv2.imshow("Laplacian Image", cv2.hconcat([original_image, laplacian_image]))
cv2.waitKey(0)
cv2.destroyAllWindows()

Laplacian Image

在上述代码中,cv2.Laplacian 函数用于应用Laplacian算子,得到图像的二阶导数。

5.4 Canny算子

Canny算子原理

Canny边缘检测是一种多阶段的算法,旨在准确而稳定地检测图像中的边缘。Canny算法包括以下几个主要步骤:

  1. 高斯平滑: 使用高斯滤波器对图像进行平滑,以减少噪声的影响。

  2. 梯度计算: 计算图像的梯度,使用Sobel、Scharr或其他梯度算子,以捕捉图像中的边缘。

  3. 非极大值抑制: 对梯度图像进行非极大值抑制,保留梯度方向上的局部极大值,以细化边缘。

  4. 边缘跟踪: 使用双阈值边缘跟踪,通过选择适当的高低阈值,将强边缘和弱边缘分离,并通过连接强边缘来形成完整的边缘。

Canny算子公式

1. 高斯平滑

高斯平滑使用一个二维高斯核进行卷积。假设 I I I 是原始图像, G G G 是高斯核, ∗ \ast 表示卷积操作,则高斯平滑可以表示为:

I smoothed = G ∗ I I_{\text{smoothed}} = G \ast I Ismoothed=GI

2. 梯度计算

梯度计算使用梯度算子(如Sobel或Scharr)计算图像在水平和垂直方向上的梯度:

G x = [ − 1 0 1 − 2 0 2 − 1 0 1 ] G_x = \begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{bmatrix} Gx= 121000121

G y = [ − 1 − 2 − 1 0 0 0 1 2 1 ] G_y = \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{bmatrix} Gy= 101202101

Gradient magnitude = G x 2 + G y 2 \text{Gradient magnitude} = \sqrt{G_x^2 + G_y^2} Gradient magnitude=Gx2+Gy2

Gradient direction = arctan ⁡ ( G y G x ) \text{Gradient direction} = \arctan\left(\frac{G_y}{G_x}\right) Gradient direction=arctan(GxGy)

3. 非极大值抑制

非极大值抑制将梯度图像中非极大值的位置置为零,保留梯度方向上的局部极大值。

4. 边缘跟踪

边缘跟踪使用双阈值法,将梯度图像中高于高阈值的像素点标记为强边缘,低于低阈值的像素点标记为弱边缘,并通过连接强边缘形成最终的边缘。

Canny算法的优点在于能够较好地抑制噪声、准确地检测边缘,并具有参数可调性。

Canny算子代码实现

下面是使用 OpenCV实现Canny算子的示例代码:

import cv2# 读取图像
image = cv2.imread("tulips.jpg")# 应用Canny算子
edges_low = cv2.Canny(image, 0, 50)
edges_mid = cv2.Canny(image, 100, 150)
edges_high = cv2.Canny(image, 200, 250)# 共享的参数
shared_params = {"org": (10, 30),"fontFace": cv2.FONT_HERSHEY_SIMPLEX,"fontScale": 1,"thickness": 2,"color": (0, 255, 0),"lineType": cv2.LINE_AA,
}
# 创建包含三个相同通道的图像
edges_low_bgr = cv2.merge([edges_low, edges_low, edges_low])
edges_mid_bgr = cv2.merge([edges_mid, edges_mid, edges_mid])
edges_high_bgr = cv2.merge([edges_high, edges_high, edges_high])
# 添加文字
original_image = cv2.putText(image.copy(), "Original Image", **shared_params)
edges_low_image = cv2.putText(edges_low_bgr, "Low Canny Edges", **shared_params)
edges_mid_image = cv2.putText(edges_mid_bgr, "Mid Canny Edges", **shared_params)
edges_high_image = cv2.putText(edges_high_bgr, "High Canny Edges", **shared_params)# 显示原始图像和Canny算子的结果
cv2.imshow("Canny Edges",cv2.vconcat([cv2.hconcat([original_image, edges_low_image]),cv2.hconcat([edges_mid_image, edges_high_image])]))
cv2.waitKey(0)
cv2.destroyAllWindows()

Canny Edges
在上述代码中,cv2.Canny 函数接受图像和两个阈值作为参数,然后应用Canny算子进行边缘检测。

5.5 自定义边缘检测滤波器

自定义边缘检测滤波器原理

生成自定义边缘检测滤波器的原理在于设计一个卷积核,该卷积核能够突出图像中的边缘信息。通常,边缘检测滤波器的设计需要考虑对图像梯度的响应,以及对噪声的鲁棒性。

自定义边缘检测滤波器的一般步骤

  1. 设计卷积核: 确定卷积核的大小和权重。卷积核的设计直接影响了滤波器的性能,需要考虑到边缘的方向和强度。

  2. 应用卷积操作: 将设计好的卷积核应用于原始图像,通过卷积操作得到滤波后的图像。

  3. 梯度计算: 如果边缘检测是通过梯度信息进行的,可以计算滤波后图像的梯度。梯度的大小和方向可以用于进一步分析边缘。

  4. 阈值处理: 根据梯度信息或其他特征,可以进行阈值处理来检测和强调边缘。

自定义边缘检测滤波器的例子

设计边缘检测滤波器的具体公式和权重需要根据实际需求和应用场景进行调整,可以通过试验和调整来获得最佳效果。
给定的边缘检测滤波器是:

Edge Filter = [ 2 0 1 0 − 6 0 1 0 2 ] \text{Edge Filter} = \begin{bmatrix} 2 & 0 & 1 \\ 0 & -6 & 0 \\ 1 & 0 & 2 \end{bmatrix} Edge Filter= 201060102

这个卷积核中心元素为-6,周围元素为0、1、2,是一个对角线方向的边缘检测滤波器。

如果将这个滤波器应用于原始图像 I I I,可以通过卷积操作得到滤波后的图像 I filtered I_{\text{filtered}} Ifiltered。卷积操作的一般形式如下:

I filtered ( i , j ) = ∑ m = − 1 1 ∑ n = − 1 1 Edge Filter ( m , n ) ⋅ I ( i + m , j + n ) I_{\text{filtered}}(i, j) = \sum_{m=-1}^{1}\sum_{n=-1}^{1} \text{Edge Filter}(m, n) \cdot I(i+m, j+n) Ifiltered(i,j)=m=11n=11Edge Filter(m,n)I(i+m,j+n)

其中, Edge Filter ( m , n ) \text{Edge Filter}(m, n) Edge Filter(m,n) 是滤波器中位于位置 ( m , n ) (m, n) (m,n) 处的权重, I ( i + m , j + n ) I(i+m, j+n) I(i+m,j+n) 是图像中位置 ( i + m , j + n ) (i+m, j+n) (i+m,j+n) 处的像素值。

这个特定的滤波器对图像中的边缘进行强调,尤其是在对角线方向。应用该滤波器后,可以通过观察滤波后的图像,看到在对角线方向上的边缘特征更为突出。

自定义边缘检测代码实现

以下是一个简单的示例代码,用于生成自定义的边缘检测滤波器:

import cv2
import numpy as np# 生成自定义的边缘检测滤波器
custom_filter = np.array([[2, 0, 1],[0, -6, 0],[1, 0, 2]])# 读取图像
image = cv2.imread("tulips.jpg")# 应用自定义滤波器
custom_edges = cv2.filter2D(image, cv2.CV_64F, custom_filter)# 共享的参数
shared_params = {"org": (10, 30),"fontFace": cv2.FONT_HERSHEY_SIMPLEX,"fontScale": 1,"thickness": 2,"color": (0, 255, 0),"lineType": cv2.LINE_AA,
}# 添加文字
original_image = cv2.putText(image.copy(), "Original Image", **shared_params)
custom_image = cv2.putText(cv2.convertScaleAbs(custom_edges), "Custom Edges", **shared_params)# 显示原始图像和自定义滤波器的结果
cv2.imshow("Custom Edges", cv2.hconcat([original_image, custom_image]))
cv2.waitKey(0)
cv2.destroyAllWindows()

Custom Edges

在上述代码中,通过 cv2.filter2D 函数可以应用自定义的边缘检测滤波器。根据实际需求,可以调整滤波器的权重以达到期望的边缘检测效果。


总结

在本篇博客中,我们深入探讨了图像处理中常见的噪声种类与生成方法,简单介绍了卷积操作的基本原理和代码实现。随后,我们详细讨论了线性滤波和非线性滤波的多种方法,包括均值滤波、方框滤波、高斯滤波、中值滤波、双边滤波等,分别阐述了它们的原理、公式和代码实现。

在边缘检测方面,我们介绍了Sobel算子、Scharr算子、Laplacian算子和Canny算子等经典边缘检测算法的原理、公式和代码实现。此外,我们还学习了如何生成自定义的边缘检测滤波器,并通过例子展示了滤波器的设计和应用。

通过这篇博客,不仅可以深入了解图像处理中常用的滤波方法和边缘检测算法,还可以学习如何自定义滤波器以满足特定需求。希望本文能够更好地理解图像处理领域的一些关键概念和技术,并为实际应用提供有益的指导。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/152567.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

redis非关系型数据库(缓存型数据库)——中间件

【重点】redis为什么这么快&#xff1f;&#xff08;应届&#xff09; ①redis是纯内存结构&#xff0c;避免磁盘I/O的耗时 ②redis核心模块是一个单进程&#xff0c;减少线程切换和回收线程资源时间 ③redis采用的是I/O的多路复用机制&#xff08;每一个执行线路可以同时完…

npm install 下载不下来依赖解决方案

背景 最近在构建 前端自动化部署 的方案中发现了一个问题&#xff0c;就是我在npm install的时候&#xff0c;有时候成功&#xff0c;有时候不成功&#xff0c;而且什么代码也没发生更改&#xff0c;报错也就是那么几个错&#xff0c;所以在此也整理了一下遇到这种情况&#xf…

如何使用 WPF 应用程序连接 FastReport报表

随着期待已久的FastReport WPF的发布&#xff0c;您不再需要使用 FastReport .NET 来处理基于 WPF 的项目。 不久前&#xff0c;在 FastReport .NET 中使用 WPF 还相当不方便。并非一切都进展顺利&#xff1b;连接 FastReport.dll 和许多其他问题存在问题。我们重新思考了该方…

2023年中职“网络安全“—Web 渗透测试①

2023年中职"网络安全"—Web 渗透测试① Web 渗透测试任务环境说明&#xff1a;1.访问地址http://靶机IP/task1&#xff0c;分析页面内容&#xff0c;获取flag值&#xff0c;Flag格式为flag{xxx}&#xff1b;2.访问地址http://靶机IP/task2&#xff0c;访问登录页面。…

面试题c/c++--语言基础

一 、语言基础 1.1 指针 野指针&#xff1a;指针指向的位置是不可知的 悬空指针&#xff1a;指针最初指向的内存已经被释放了的一种指针 两种指针都指向无效内存空间&#xff0c; 即不安全不可控 。需要在定义指针后且在使用之前完成初始化或者使用 智能指针来避免 智能指针 智…

获取阿里云Docker镜像加速器

1、阿里云官网&#xff08;www.aliyun.com&#xff09;注册账号 2、打开“控制台首页” 控制台首页地址&#xff1a;https://home.console.aliyun.com/home/dashboard/ProductAndService 3、点击“概览->容器镜像服务 ACR” 4、打开“镜像工具->镜像加速器”页面&#x…

【grafana | clickhouse】实现展示多折线图

说明&#xff1a; 采用的是 Visualizations 的 Time series&#xff0c;使用的 clickhouse 数据源 在工作中遇到了一个需求&#xff0c;写好了代码&#xff0c;需要在grafana上展示在一个项目中所有人的&#xff0c;随时间的代码提交量变化图 目前遇到的问题&#xff1a;展示…

FFmpeg常用命令行讲解及实战一

文章目录 前言一、学习资料参考二、FFmpeg 选项1、主要选项①、主要命令选项②、举例 2、视频选项①、主要命令选项②、举例1&#xff09;提取固定帧2&#xff09;禁止输出视频3&#xff09;指定视频的纵横比 3、音频选项①、主要命令选项②、举例 4、字幕选项①、主要命令选项…

负载均衡简介

负载均衡 负载均衡&#xff08;Load Balance&#xff0c;简称 LB&#xff09;是高并发、高可用系统必不可少的关键组件&#xff0c;目标是 尽力将网络流量平均分发到多个服务器上&#xff0c;以提高系统整体的响应速度和可用性。 负载均衡的分类和OSI模型息息相关&#xff0c…

【CHI】Ordering保序

本节介绍CHI协议所包含的支持系统保序需求的机制&#xff0c;包括&#xff1a; • Multi-copy atomicity • Completion response and ordering • Completion acknowledgment • Transaction ordering 一、 Multi-copy atomicity CHI协议中所使用的memory model要求为mu…

【面试经典150 | 数学】Pow(x, n)

文章目录 写在前面Tag题目来源题目解读解题思路方法一&#xff1a;快速幂-递归方法二&#xff1a;快速幂-迭代 其他语言python3 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主…

王者荣耀游戏

游戏运行如下&#xff1a; sxt Background package sxt;import java.awt.*; //背景类 public class Background extends GameObject{public Background(GameFrame gameFrame) {super(gameFrame);}Image bg Toolkit.getDefaultToolkit().getImage("C:\\Users\\24465\\D…

5分钟教你轻松搭建Web自动化测试框架

在程序员的世界中&#xff0c;一切重复性的工作&#xff0c;都应该通过程序自动执行。「自动化测试」就是一个最好的例子。 随着互联网应用开发周期越来越短&#xff0c;迭代速度越来越快&#xff0c;只会点点点&#xff0c;不懂开发的手工测试&#xff0c;已经无法满足如今的…

3.8-镜像的发布

如果我们想将image push到docker hub里面&#xff0c;那么我们的image的名字一定要是这种格式&#xff1a;docker hub id/imageName&#xff0c;例如&#xff1a;lvdapiaoliang/hello-docker docker hub个人账户设置地址&#xff1a; 在push之前要先登录&#xff1a; docker l…

数学建模值TOPSIS法及代码

TOPSIS法 TOPSIS法简称为优劣距离解法&#xff0c;是一种常见法综合评价方法&#xff0c;其能充分利用原始数据的信息&#xff0c;其结果能精确地反映各个评价方案之间的差距。 模型介绍 上篇文章谈到的层次分析法是有局限性的。比如评价的决策层不能太多&#xff0c;太多的…

ISP--Black Level Correction(黑电平矫正)

图像的每一个像素点都是由一个光电二极管控制的&#xff0c;由二极管将电信号&#xff0c;转换为数字信号。 那么&#xff0c;我们知道了&#xff0c;图像的像素值是与电信号强度相关的。但是&#xff0c;我们得知道&#xff0c;每一个光电二极管要想工作&#xff0c;都得有一定…

Three.js相机模拟

有没有想过如何在 3D Web 应用程序中模拟物理相机? 在这篇博文中,我将向你展示如何使用 Three.js和 OpenCV 来完成此操作。 我们将从模拟针孔相机模型开始,然后添加真实的镜头畸变。 具体来说,我们将仔细研究 OpenCV 的两个失真模型,并使用后处理着色器复制它们。 拥有逼…

C/C++最大质因子 2021年12月电子学会中小学生软件编程(C/C++)等级考试一级真题答案解析

目录 C/C最大质因子 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 C/C最大质因子 一、题目要求 1、编程实现 质因子是指能整除给定正整数的质数。而最大质因子是指一个整数的所有质因子中最大的那个。…

开源集群管理系统对比分析:Kubernetes 与 Apache Mesos

集群管理系统是关键的软件解决方案&#xff0c;可以在互连机器网络中有效分配和利用计算资源。毫无疑问&#xff0c;它们通过确保可扩展性、高可用性和有效的资源管理在现代计算中发挥着至关重要的作用&#xff0c;这使得它们对于运行复杂的应用程序、管理数据中心以及进一步增…

数据分析基础之《jupyter notebook工具》

一、安装库 1、linux库 yum install python3-devel 2、python库 pip3 install -U matplotlib pip3 install -U numpy pip3 install -U pandas pip3 install -U TA-Lib pip3 install -U tables pip3 install -U notebook 3、如果TA-Lib安装不上&#xff0c;先手动安装依赖库 …