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快速入门:图像滤波与边缘检测_第1张图片

一、噪声种类与生成

在图像处理中,噪声是指图像中不希望出现的随机扰动。了解噪声的种类以及如何生成是图像处理中的重要一步。本节将详细介绍两种常见的噪声类型:椒盐噪声和高斯噪声。另外追加一种彩色噪声,原理与椒盐噪声和高斯噪声相同,只是针对每个通道都进行噪声的扰动。并使用 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()

OpenCV快速入门:图像滤波与边缘检测_第2张图片
在上述代码中,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()

在上述代码中,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()


在上述代码中,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()

在上述代码中,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()

在上述代码中,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()

在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()

在上述代码中,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()

在上述代码中,首先使用 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()

在上述代码中,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()

在上述代码中,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()

在上述代码中,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()

与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()

OpenCV快速入门:图像滤波与边缘检测_第3张图片

在上述代码中,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()

OpenCV快速入门:图像滤波与边缘检测_第4张图片
在上述代码中,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()

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


总结

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

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

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

你可能感兴趣的:(Python,#,OpenCV,机器视觉,opencv,人工智能,计算机视觉)