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

news2024/12/23 4:42:37

文章目录

  • 前言
  • 一、噪声种类与生成
    • 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 np


def add_salt_and_pepper_noise(image, salt_prob, pepper_prob):
    noisy_image = np.copy(image)

    # 椒噪声
    salt_noise = np.random.rand(*image.shape) < salt_prob
    noisy_image[salt_noise] = 255

    # 盐噪声
    pepper_noise = np.random.rand(*image.shape) < pepper_prob
    noisy_image[pepper_noise] = 0

    return 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 np


def add_gaussian_noise(image, mean=0, sigma=25):
    row, col = image.shape
    gauss = 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 np


def add_colored_noise(image, intensity=50):
    row, col, ch = image.shape
    colored_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.shape
    colored_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.shape
    colored_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.shape
    colored_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.shape
    colored_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.shape
    colored_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.shape
    colored_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.shape
    colored_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.coloradmin.cn/o/1224013.html

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

相关文章

手把手教你用C语言写出“走迷宫”小游戏(能看懂文字就会自己敲系列)

目录 设计迷宫地图 设计主角——小球 完整代码 这次教大家编写一个简单的“走迷宫”小游戏&#xff0c;我们可以通过键盘上的‘W’、‘S’、‘A’、‘D’四个键来控制一个“小球”向上&#xff0c;下&#xff0c;左&#xff0c;右移动&#xff0c;目的就是让这个“小球”从起…

element表格头部加入图标

首先看看效果 下面是代码 <el-table-column prop"integralBalance"><template slot"header" slot-scope"scope"><div style"display: flex;justify-content: center;align-items: center;">积分余额<i class&qu…

mac无法向移动硬盘拷贝文件怎么解决?不能读取移动硬盘文件怎么解决

有时候我们在使用mac的时候&#xff0c;会遇到一些问题&#xff0c;比如无法向移动硬盘拷贝文件或者不能读取移动硬盘文件。这些问题会给我们的工作和生活带来不便&#xff0c;所以我们需要找到原因和解决办法。本文将为你介绍mac无法向移动硬盘拷贝文件怎么回事&#xff0c;以…

Mysql -常见函数

目录 字符串函数 数值函数 日期函数 流程函数 字符串函数 -- 拼接 SELECT CONCAT(Hello, World); -- 小写 SELECT LOWER(Hello); -- 大写 SELECT UPPER(Hello); -- 左填充 SELECT LPAD(01, 5, -); -- 右填充 SELECT RPAD(01, 5, -); -- 去除空格 SELECT TRIM( Hello World )…

sqli-labs关卡20(基于http头部报错盲注)通关思路

文章目录 前言一、回顾上一关知识点二、靶场第二十关通关思路1、判断注入点2、爆数据库名3、爆数据库表4、爆数据库列5、爆数据库关键信息 总结 前言 此文章只用于学习和反思巩固sql注入知识&#xff0c;禁止用于做非法攻击。注意靶场是可以练习的平台&#xff0c;不能随意去尚…

基于 gin + websocket 即时通讯项目 (一、项目初始化)

基于 gin websocket 即时通讯项目 1、安装环境与初始化 搜索各种包官网 https://pkg.go.dev/ 1.1 安装 grom go get -u gorm.io/grom 1.2 安装 MySQL 驱动 go get -u gorm.io/driver/sqlite go get -u gorm.io/driver/mysql 1.3 安装 gin go get -u github.com/gin-gonic/gi…

深入了解Java 8 新特性:lambda表达式基础

阅读建议 嗨&#xff0c;伙计&#xff01;刷到这篇文章咱们就是有缘人&#xff0c;在阅读这篇文章前我有一些建议&#xff1a; 本篇文章大概000多字&#xff0c;预计阅读时间长需要5分钟。本篇文章的实战性、理论性较强&#xff0c;是一篇质量分数较高的技术干货文章&#xf…

RIP路由信息协议

RIP路由信息协议(Routing Information Protocol) 最先得到广泛应用的协议&#xff0c;最大优点是简单要求网络中的每个路由器都要维护一张表&#xff0c;表中记录了从它自己到其他每一个目的网络的距离RIP是应用层协议&#xff0c;它在传输层使用UDP&#xff0c;RIP报文作为UD…

Git 简介及使用

前言 假设有这样一个场景&#xff0c;老板让员工做一个档案&#xff0c;员工这个档案做好了之后交给老板看&#xff0c;此时老板不满意&#xff0c;又让回去改&#xff0c;改完给老板看&#xff0c;但是老板又不是很满意&#xff0c;就这样改了又改&#xff0c;给老板看过之后&…

WPF中行为与触发器的概念及用法

完全来源于十月的寒流&#xff0c;感谢大佬讲解 一、行为 (Behaviors) behaviors的简单测试 <Window x:Class"Test_05.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winf…

YOLOv8改进 | DAttention (DAT)注意力机制实现极限涨点

论文地址&#xff1a; DAT论文地址 官方地址&#xff1a;官方代码的地址 代码地址&#xff1a;文末有修改了官方代码BUG的代码块复制粘贴即可 一、本文介绍 本文给大家带来的是YOLOv8改进DAT(Vision Transformer with Deformable Attention)的教程&#xff0c;其发布于2022…

java.net.UnknownHostException: eureka

java.net.UnknownHostException: eureka 哦。HOST漏了 #linux /etc/hosts #windows C:\Windows\System32\drivers\etc\hosts 127.0.0.1 eureka7000 127.0.0.1 eureka7001 127.0.0.1 eureka7002

Echarts柱状图配置代码详解,含常用图例代码

一、初识柱状图 从echarts官网引入基础的柱状图后&#xff0c;可以看到他有如下的配置项。我们可以改变各个配置项的属性&#xff0c;将图例调整为我们期望的效果。 二、常用配置项 因为引入echarts图例后&#xff0c;改变图例的东西都在option配置项中&#xff0c;所以其他部…

腾讯云轻量级服务器和云服务器什么区别?轻量服务器是干什么用的

随着互联网的迅速发展&#xff0c;服务器成为了许多人必备的工具。然而&#xff0c;面对众多的服务器选择&#xff0c;我们常常会陷入纠结之中。在这篇文章中&#xff0c;我们将探讨轻量服务器和标准云服务器的区别&#xff0c;帮助您选择最适合自己需求的服务器。 腾讯云双十…

python中sklearn库在数据预处理中的详细用法,及5个常用的Scikit-learn(通常简称为 sklearn)程序代码示例

文章目录 前言1. 数据清洗&#xff1a;使用 sklearn.preprocessing 中的 StandardScaler 和 MinMaxScaler 进行数据规范化。2. 缺失值处理&#xff1a;使用 sklearn.impute 中的 SimpleImputer 来填充缺失值。3. 数据编码&#xff1a;使用 sklearn.preprocessing 中的 OneHotEn…

python中的NumPy和Pandas往往都是同时使用,NumPy和Pandas的在数据分析中的联合使用

文章目录 前言一、numpy的介绍与用法二、pandas的介绍与用法三、numpy与pandas的联合使用说明四、numpy与pandas的联合使用程序代码4.1 读取CSV文件并进行数据清洗&#xff0c;如去除NaN值4.2 矩阵操作和特征工程&#xff0c;如标准化处理4.3 使用Pandas进行数据筛选和分组聚合…

ogrinfo不是内部或者外部命令

这个是GDAL的问题&#xff0c;我是通过OSGeo4w安装的&#xff0c;出来就是这个问题&#xff0c;教程没有仔细看干。 第一次安装&#xff0c;选择express install&#xff01;&#xff01;&#xff01;&#xff01; 第一次安装&#xff0c;选择express install&#xff01;&…

代码随想录算法训练营第三十九天【动态规划part02】 | 62.不同路径、63. 不同路径 II

62.不同路径 题目链接&#xff1a; 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 求解思路&#xff1a; 动规五部曲 确定dp数组及其下标含义&#xff1a;dp[i][j] 表示从&#xff08;0,0&#xff09;出发&#xff0c;到&#xff08;i,j&#x…

【算法】最短路径——弗洛伊德 (Floyd) 算法

目录 1.概述2.代码实现3.扩展3.应用 1.概述 &#xff08;1&#xff09;弗洛伊德 (Floyd) 算法又称为插点法&#xff0c;是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法&#xff0c;与 Dijkstra 算法类似。该算法名称以创始人之一、1978 年图灵奖获得者、…

Matalab插值详解和源码

转载&#xff1a;Matalab插值详解和源码 - 知乎 (zhihu.com) 插值法 插值法又称“内插法”&#xff0c;是利用函数f (x)在某区间中已知的若干点的函数值&#xff0c;作出适当的特定函数&#xff0c;在区间的其他点上用这特定函数的值作为函数f (x)的近似值&#xff0c;这种方…