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 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.mfbz.cn/a/161749.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Matalab插值详解和源码

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

kernel32.dll下载地址分享,Kernel32.DLL文件丢失的修复指南

作为计算机用户&#xff0c;我们可能都曾遭遇过这样一条令人烦恼的错误信息&#xff1a; "程序无法启动&#xff0c;因为您的计算机中缺少Kernel32.dll"。在这种情况下&#xff0c;往往会引发一系列疑问&#xff1a; Kernel32.dll是什么&#xff1f;为什么它对我的电…

51.Sentinel微服务保护

目录 &#xff08;1&#xff09;初识Sentinel。 &#xff08;1.1&#xff09;雪崩问题及解决方案。 &#xff08;1.1.1&#xff09;雪崩问题。 &#xff08;1.1.2&#xff09;解决雪崩问题的四种方式。 &#xff08;1.1.3&#xff09;总结。 &#xff08;1.2&#xff09;…

c语言:模拟实现qsort函数

qsort函数的功能&#xff1a; qsort相较于冒泡排序法&#xff0c;不仅效率更快&#xff0c;而且能够比较不同类型的元素&#xff0c;如&#xff1a;浮点数&#xff0c;结构体等等。这里我们来模拟下qsort是如何实现这一功能的&#xff0c;方便我们对指针数组有一个更深层次的理…

【APUE】补充 — 基于管道的线程池

目录 一、引言 二、代码实现 三、思考 一、引言 在线程章节的 3.2 部分&#xff0c;我们曾经提到过线程池的实现 在当时的代码中&#xff0c;我们仅仅用的一个 int 类型的变量来表示这个“池”&#xff0c;用来存放任务 显然这个池太小了&#xff0c;如果下游线程很多&am…

代码随想录 Day49 单调栈01 LeetCode LeetCodeT739每日温度 T496 下一个最大元素I

前言 折磨的死去活来的动态规划终于结束啦,今天秋秋给大家带来两题非常经典的单调栈问题,可能你不清楚单调栈是什么,可以用来解决什么问题,今天我们就来一步一步的逐渐了解单调栈,到能够灵活使用单调栈.注意以下讲解中&#xff0c;顺序的描述为 从栈头到栈底的顺序 什么时候用单…

3D建模基础教程:编辑样条线【子层级】

了解子层级编辑样条线 在3D建模中&#xff0c;样条线是创建各种形状和曲线的重要工具。而编辑样条线是3D建模过程中不可或缺的一部分。今天&#xff0c;我们将一起学习如何编辑样条线&#xff0c;以及了解其子层级的相关知识。 样条线的子层级介绍 样条线的子层级包括&#xff…

Java的IO流-缓冲流

字节缓冲流 package com.itheima.d2;import java.io.*;public class Test1 {public static void main(String[] args) {try (InputStream is new FileInputStream("IO/src/itheima01.txt");//1、定义一个字节缓冲输入流包装原始的字节输入流InputStream bis new Bu…

任你五花八门预训练方法,我自监督学习依然能打!

长时间没看论文&#xff0c;外面已经发展成这样了&#xff1f; 以下都是新paper&#xff0c;挑了几个感兴趣的&#xff0c;一起粗略看看吧~ Battle of the Backbones: A Large-Scale Comparison of Pretrained Models across Computer Vision Tasks GitHub | https://github.…

linux基本指令总结--文件和目录

前言&#xff1a; 想要学好Linux操作系统&#xff0c;理解并熟悉一些基本的指令是必要的&#xff0c;下面我将整理出关于文件和目录操作的一些基本指令和用法&#xff0c;我的linux环境部署在服务器端&#xff0c;使用xshell软件进行远程操作。 本章指令整合&#xff1a; ls查…

十个一手app拉新地推拉新推广接单平台,放单/接任务渠道

做过地推拉新的朋友一定都非常清楚&#xff0c;app拉新推广一手接单平台&#xff0c;和非一手接任务平台之间的收益差&#xff0c;可以用天壤之别来形容。那么一手app拉新渠道应该怎么找&#xff1f;下面这十个常见的地推拉新app接单平台&#xff0c;一定要收藏。 1. 聚量推客…

学习c#的第十四天

目录 C# 接口&#xff08;Interface&#xff09; 接口的特点 定义接口 接口继承 接口和抽象类的区别 C# 命名空间&#xff08;Namespace&#xff09; using 关键字 定义命名空间 嵌套命名空间 C# 接口&#xff08;Interface&#xff09; 接口定义了所有类继承接口时应…

036、目标检测-锚框

之——对边缘框的简化 目录 之——对边缘框的简化 杂谈 正文 1.锚框操作 2.IoU交并比 3.锚框标号 4.非极大值抑制 5.实现 拓展 杂谈 边缘框这样一个指定roi区域的操作对卷积神经网络实际上是很不友好的&#xff0c;这可能会对网络感受野提出一些特定的要求&#xff0…

HUAWEI华为笔记本MateBook X 2021款i5集显(EULD-WFH9,WXX9)原装出厂Windows11系统工厂模式包

下载链接&#xff1a;https://pan.baidu.com/s/1gQ_O203SSm83Nc-zDk1iNA?pwd4exz 提取码&#xff1a;4exz 系统带F10一键智能还原功能隐藏恢复分区、所有驱动、Office办公软件、华为电脑管家等预装程序 所需要工具&#xff1a;32G或以上的U盘 文件格式&#xff1a;zip …

智慧工地APP全套源码,智慧工地云平台

智慧工地平台 &#xff0c;智慧工地源码&#xff0c;智慧工地APP全套源码 智慧工地以施工现场风险预知和联动预控为目标&#xff0c;将智能AI、传感技术、人像识别、监控、虚拟现实、物联网、5G、大数据、互联网等新一代科技信息技术植入到建筑、机械、人员穿戴设施、场地进出关…

Linux下查看pytorch运行时真正调用的cuda版本

一般情况我们会安装使用多个cuda版本。而且pytorch在安装时也会自动安装一个对应的版本。 正确查看方式&#xff1a; 想要查看 Pytorch 实际使用的运行时的 cuda 目录&#xff0c;可以直接输出 cpp_extension.py 中的 CUDA_HOME 变量。 import torch import torch.utils imp…

Nginx反向代理和负载均衡

1.反向代理 反向代理&#xff08;Reverse Proxy&#xff09;方式是指以代理服务器来接受internet上的连接请求&#xff0c;然后将请求转发给内部网络上的服务器&#xff0c;并将从服务器上得到的结果返回给internet上请求连接的客户端&#xff0c;此时代理服务器对外就表现为一…

springboot+vue+element简单实现教学课程申报管理系统

目录 一、项目预览 二、项目效果图及说明 1.项目说明 1.登录 2.欢迎页 3.教师管理 4.课程申报 ​5.管理员管理 三、代码实现 1.后端项目结构图 2.数据库表脚本 3.路由配置 四、总结 一、项目预览 在线预览&#xff1a;点击访问其他项目访问&#xff1a;点击访问后端实…

Java学习之路 —— Java高级

文章目录 前言1. 单元测试2. 反射2.1 获取Class对象的三种方式2.2 获取类的构造器的方法2.3 获取类的成员变量2.4 获取类的成员方法2.5 反射的作用 3. 注解3.1 自定义注解3.2 注解的原理3.3 元注解3.4 注解的解析 4. 动态代理5. 总结 前言 终于走到新手村的末端了&#xff0c;…
最新文章