使用python进行图像处理—图像滤波(5)

发布于:2025-06-10 ⋅ 阅读:(21) ⋅ 点赞:(0)

图像滤波是图像处理中最基本和最重要的操作之一。它的目的是在空间域上修改图像的像素值,以达到平滑(去噪)、锐化、边缘检测等效果。滤波通常通过卷积操作实现。

5.1卷积(Convolution)原理

卷积是滤波的核心。它是一种数学运算,将一个图像(输入信号)与一个核(Kernel或 滤波器,一个小的二维数组)进行运算,产生一个新的图像(输出信号)。

想象一个核在图像上滑动,在每一个位置,核的每个元素与图像对应位置的像素值相乘,然后将所有乘积相加,得到的和就是输出图像在该位置的像素值。

数学上,二维离散卷积可以表示为:

[

G[i, j] = \sum_m \sum_n I[i-m, j-n] K[m, n]

]

其中:

(G[i, j])是输出图像在( (i, j) )位置的像素值。

(I)是输入图像。

(K)是核(滤波器)。

(m, n)是核的索引。

在实际应用中,为了计算方便,通常使用相关的形式,但效果类似。OpenCV和scikit-image等库提供了高效的卷积实现。

5.2常见的空间滤波器

空间滤波器可以分为线性滤波器和非线性滤波器。

线性滤波器:输出像素值是输入像素值的线性组合,例如均值滤波、高斯滤波。它们可以通过卷积实现。

非线性滤波器:输出像素值不是输入像素值的线性组合,例如中值滤波。它们通常不能直接通过简单的卷积实现,但同样涉及在像素邻域内的操作。

5.2.1平滑滤波器(Smoothing Filters)

平滑滤波器用于去除图像中的噪声,模糊图像细节。它们通常是低通滤波器,衰减图像中的高频成分(如噪声和边缘)。

均值滤波器(Mean Filter):用像素邻域内的平均值替换当前像素值。

import numpy as np
from PIL import Image
# PIL 不直接提供卷积核应用函数,但我们可以用 NumPy 实现或使用其他库
# 这里我们使用 scikit-image,它提供了更方便的滤波器函数

from skimage import io, filters
import matplotlib.pyplot as plt # 用于显示图像

# 加载一个示例图像 (如果不存在,创建一个模拟图像)
try:
    img_sk = io.imread('example.jpg') # skimage io.imread 可以读取多种格式
    print("成功加载图像使用 skimage.io.imread")
except FileNotFoundError:
    print("示例图像 example.jpg 未找到,请确保文件存在。")
    # 如果找不到,我们创建一个模拟图像
    from PIL import Image
    img = Image.new('RGB', (300, 200), color = 'gray')
    img_sk = np.array(img) # 转换为 NumPy 数组供 skimage 使用
    print("已创建模拟图像数组。")


# 确保图像是灰度或彩色,这里示例以灰度进行,如果加载的是彩色,先转换
if img_sk.ndim == 3:
    print("图像是彩色,转换为灰度进行滤波示例。")
    # skimage.color.rgb2gray 可以方便转换
    from skimage.color import rgb2gray
    img_sk_gray = rgb2gray(img_sk)
    # rgb2gray 返回的是浮点数类型 (0.0 到 1.0),需要缩放到 0-255 并转换为 uint8 显示
    img_sk_gray_uint8 = (img_sk_gray * 255).astype(np.uint8)
else:
    img_sk_gray_uint8 = img_sk # 如果已经是灰度,直接使用


# 应用均值滤波器
# 使用 skimage.filters.rank.mean 
# size 参数指定邻域大小,例如 3 表示 3x3 邻域
# 这个函数需要输入是 uint8 类型
try:
    mean_filtered_img = filters.rank.mean(img_sk_gray_uint8, selem=np.ones((5, 5))) # 使用 5x5 的方形结构元素

    # 显示原始图像和均值滤波后的图像
    plt.figure(figsize=(10, 5))

    plt.subplot(1, 2, 1)
    plt.imshow(img_sk_gray_uint8, cmap='gray') # cmap='gray' 用于灰度图像显示
    plt.title('原始灰度图像')
    plt.axis('off') # 不显示坐标轴

    plt.subplot(1, 2, 2)
    plt.imshow(mean_filtered_img, cmap='gray')
    plt.title('均值滤波 (5x5)')
    plt.axis('off')

    plt.tight_layout() # 调整子图布局
    plt.show()

    # 保存结果 (使用Pillow或skimage.io.imsave)
    # Image.fromarray(mean_filtered_img).save('output_mean_filtered.jpg')
    io.imsave('output_mean_filtered_skimage.jpg', mean_filtered_img)
    print("均值滤波已完成并保存为: output_mean_filtered_skimage.jpg")


except Exception as e:
    print(f"均值滤波示例执行失败: {e}")
    print("请检查scikit-image版本是否支持 filters.rank.mean,或尝试不同的结构元素 (selem)。")
    print("或者使用 scipy.ndimage.uniform_filter 进行均值滤波")

# 替代方法:使用 scipy.ndimage 进行均值滤波
try:
    from scipy import ndimage

    uniform_filtered_img = ndimage.uniform_filter(img_sk_gray_uint8, size=5) # size=5 表示一个边长为5的方形核

    # 显示原始图像和均匀滤波后的图像
    plt.figure(figsize=(10, 5))

    plt.subplot(1, 2, 1)
    plt.imshow(img_sk_gray_uint8, cmap='gray')
    plt.title('原始灰度图像')
    plt.axis('off')

    plt.subplot(1, 2, 2)
    plt.imshow(uniform_filtered_img, cmap='gray')
    plt.title('均匀滤波 (scipy, size=5)')
    plt.axis('off')

    plt.tight_layout()
    plt.show()

    io.imsave('output_uniform_filtered_scipy.jpg', uniform_filtered_img)
    print("均匀滤波 (scipy) 已完成并保存为: output_uniform_filtered_scipy.jpg")


except ImportError:
    print("未安装 scipy 库,跳过均匀滤波示例。请使用 pip install scipy 进行安装。")
except Exception as e:
     print(f"均匀滤波示例执行失败: {e}")



代码解释:

  • from skimage import io, filters:导入scikit-image库的io模块(用于读写图像)和filters模块(包含各种滤波器)。
  • import matplotlib.pyplot as plt:导入Matplotlib库用于显示图像。
  • img_sk = io.imread('example.jpg'):使用skimage.io.imread加载图像。scikit-image加载的图像默认是NumPy数组。
  • 检查图像维度并转换为灰度:因为一些滤波函数期望灰度图像。skimage.color.rgb2gray()可以方便地将彩色图像转换为灰度图像。注意rgb2gray返回浮点数组,需要转换回uint8进行显示和一些滤波函数。
  • mean_filtered_img = filters.rank.mean(img_sk_gray_uint8, selem=np.ones((5, 5))):使用skimage.filters.rank.mean()函数进行均值滤波。
  • 第一个参数是输入的图像数组(需要是uint8类型)。
  • selem:结构元素(structuring element),定义了邻域的形状和大小。np.ones((5, 5))创建一个5x5的全部为1的NumPy数组,表示一个5x5的方形邻域。
  • plt.figure(), plt.subplot(), plt.imshow(), plt.title(), plt.axis('off'), plt.tight_layout(), plt.show():这些是Matplotlib用于创建图窗、子图,在子图中显示图像,设置标题,关闭坐标轴,调整布局并显示图像的函数。
  • io.imsave('output_mean_filtered_skimage.jpg', mean_filtered_img):使用skimage.io.imsave保存滤波后的图像。
  • from scipy import ndimage:导入SciPy库的ndimage模块,它也提供了图像处理函数。
  • uniform_filtered_img = ndimage.uniform_filter(img_sk_gray_uint8, size=5):使用scipy.ndimage.uniform_filter()进行均匀滤波,效果等同于均值滤波。size参数指定滤波核的边长。

高斯滤波器(Gaussian Filter):使用一个高斯函数作为权重对像素邻域进行加权平均。高斯滤波器比均值滤波器引起的模糊更少,并且对抑制高斯噪声特别有效。

import numpy as np
from PIL import Image
from skimage import io, filters
import matplotlib.pyplot as plt
from scipy import ndimage # SciPy 也有高斯滤波器

# 假设 img_sk_gray_uint8 是上面准备好的灰度图像 uint8 数组

# 应用高斯滤波器 (使用 skimage)
# sigma 参数是高斯核的标准差,控制平滑程度,值越大越模糊
# truncate 参数控制高斯核的大小,核窗口大小通常是 (2*truncate*sigma + 1) x (2*truncate*sigma + 1)
gaussian_filtered_img_sk = filters.gaussian(img_sk_gray_uint8, sigma=2, truncate=3.5)

# skimage 的 gaussian 滤波器返回浮点数数组 (0.0-1.0),需要转换回 uint8 显示和保存
gaussian_filtered_img_sk_uint8 = (gaussian_filtered_img_sk * 255).astype(np.uint8)


# 应用高斯滤波器 (使用 scipy)
# sigma 参数同样是标准差
gaussian_filtered_img_sp = ndimage.gaussian_filter(img_sk_gray_uint8, sigma=2)
# scipy 的 gaussian_filter 通常返回与输入相同的 dtype,这里是 uint8


# 显示原始图像和高斯滤波后的图像
plt.figure(figsize=(15, 5))

plt.subplot(1, 3, 1)
plt.imshow(img_sk_gray_uint8, cmap='gray')
plt.title('原始灰度图像')
plt.axis('off')

plt.subplot(1, 3, 2)
plt.imshow(gaussian_filtered_img_sk_uint8, cmap='gray')
plt.title('高斯滤波 (skimage, sigma=2)')
plt.axis('off')

plt.subplot(1, 3, 3)
plt.imshow(gaussian_filtered_img_sp, cmap='gray')
plt.title('高斯滤波 (scipy, sigma=2)')
plt.axis('off')


plt.tight_layout()
plt.show()

# 保存结果
io.imsave('output_gaussian_filtered_skimage.jpg', gaussian_filtered_img_sk_uint8)
io.imsave('output_gaussian_filtered_scipy.jpg', gaussian_filtered_img_sp)
print("高斯滤波已完成并保存结果。")



代码解释:

  • gaussian_filtered_img_sk = filters.gaussian(img_sk_gray_uint8, sigma=2, truncate=3.5):使用skimage.filters.gaussian()进行高斯滤波。
  • 第一个参数是输入的图像数组。
  • sigma=2:设置高斯核的标准差为2。标准差越大,核越大,平滑效果越强。
  • truncate=3.5:这个参数决定了高斯核的截断半径,表示高斯核将延伸到距离中心多少个标准差的位置。通常取3到4,因为高斯函数在这个范围之外的值非常小,可以忽略。
  • gaussian_filtered_img_sk_uint8 = (gaussian_filtered_img_sk * 255).astype(np.uint8): skimage.filters.gaussian返回的是浮点数数组(0.0到1.0),为了显示和保存为常见的图像格式(如JPEG),需要将其乘以255并转换为uint8类型。
  • gaussian_filtered_img_sp = ndimage.gaussian_filter(img_sk_gray_uint8, sigma=2):使用scipy.ndimage.gaussian_filter()进行高斯滤波。接口稍有不同,但sigma参数的含义是相同的。SciPy的函数通常会尽量保持输入的数据类型。

中值滤波器(Median Filter):用像素邻域内的中值替换当前像素值。中值滤波器是非线性滤波器,对椒盐噪声(Salt-and-pepper noise)特别有效,因为它不会引入新的极端值。

import numpy as np
from PIL import Image
from skimage import io, filters
import matplotlib.pyplot as plt
from scipy import ndimage

# 假设 img_sk_gray_uint8 是上面准备好的灰度图像 uint8 数组

# 为了更好地演示中值滤波,我们在图像中添加一些椒盐噪声
def add_salt_and_pepper_noise(image_array, amount=0.05):
    """在图像数组中添加椒盐噪声"""
    img_noisy = image_array.copy()
    # 添加盐噪声 (白色像素)
    num_salt = np.ceil(amount * image_array.size * 0.5).astype(int)
    coords_salt = [np.random.randint(0, i - 1, num_salt) for i in image_array.shape]
    img_noisy[tuple(coords_salt)] = 255

    # 添加椒噪声 (黑色像素)
    num_pepper = np.ceil(amount * image_array.size * 0.5).astype(int)
    coords_pepper = [np.random.randint(0, i - 1, num_pepper) for i in image_array.shape]
    img_noisy[tuple(coords_pepper)] = 0
    return img_noisy

# 添加噪声
img_noisy_uint8 = add_salt_and_pepper_noise(img_sk_gray_uint8, amount=0.03) # 添加3%的椒盐噪声

# 应用中值滤波器 (使用 skimage)
# size 参数指定邻域大小,例如 3 表示 3x3 邻域
# skimage.filters.median 同样需要 uint8 输入
median_filtered_img_sk = filters.median(img_noisy_uint8, selem=np.ones((3, 3))) # 使用 3x3 的方形结构元素


# 应用中值滤波器 (使用 scipy)
# size 参数指定邻域大小
median_filtered_img_sp = ndimage.median_filter(img_noisy_uint8, size=3)


# 显示原始噪声图像和中值滤波后的图像
plt.figure(figsize=(15, 5))

plt.subplot(1, 3, 1)
plt.imshow(img_noisy_uint8, cmap='gray')
plt.title('原始噪声图像')
plt.axis('off')

plt.subplot(1, 3, 2)
plt.imshow(median_filtered_img_sk, cmap='gray')
plt.title('中值滤波 (skimage, size=3)')
plt.axis('off')

plt.subplot(1, 3, 3)
plt.imshow(median_filtered_img_sp, cmap='gray')
plt.title('中值滤波 (scipy, size=3)')
plt.axis('off')


plt.tight_layout()
plt.show()

# 保存结果
io.imsave('output_median_filtered_skimage.jpg', median_filtered_img_sk)
io.imsave('output_median_filtered_scipy.jpg', median_filtered_img_sp)
print("中值滤波已完成并保存结果。")


代码解释:

  • def add_salt_and_pepper_noise(image_array, amount=0.05)::定义一个函数用于向图像中添加椒盐噪声,以便更好地展示中值滤波的效果。
  • amount:噪声的比例,0.05表示图像总像素数的5%会变成噪声点。
  • np.ceil(amount * image_array.size * 0.5).astype(int):计算需要添加的盐点和椒点的数量,假设盐点和椒点各占一半。image_array.size是图像的总像素数。
  • coords_salt = [np.random.randint(0, i - 1, num_salt) for i in image_array.shape]:为盐点生成随机的行和列坐标。对于二维图像,image_array.shape是(height, width),循环会为高度和宽度生成对应的坐标数组。np.random.randint(0, i - 1, num_salt)在指定范围内生成num_salt个随机整数。
  • img_noisy[tuple(coords_salt)] = 255:使用生成的随机坐标作为索引,将这些位置的像素值设置为255(白色,盐噪声)。tuple(coords_salt)是为了正确地使用NumPy的多维索引。
  • 添加椒噪声的过程类似,只是像素值设置为0(黑色)。
  • img_noisy_uint8 = add_salt_and_pepper_noise(img_sk_gray_uint8, amount=0.03):调用函数为灰度图像添加3%的椒盐噪声。
  • median_filtered_img_sk = filters.median(img_noisy_uint8, selem=np.ones((3, 3))):使用skimage.filters.median()进行中值滤波。selem参数同样指定邻域。
  • median_filtered_img_sp = ndimage.median_filter(img_noisy_uint8, size=3):使用scipy.ndimage.median_filter()进行中值滤波。size参数指定邻域大小。
5.2.2锐化滤波器(Sharpening Filters)

锐化滤波器用于增强图像的边缘和细节。它们通常是高通滤波器,增强图像中的高频成分。

拉普拉斯算子(Laplacian Operator):是一种二阶微分算子,用于检测图像中的边缘。它对噪声非常敏感。

import numpy as np
from PIL import Image
from skimage import io, filters
import matplotlib.pyplot as plt
from scipy import ndimage

# 假设 img_sk_gray_uint8 是上面准备好的灰度图像 uint8 数组

# 应用拉普拉斯滤波器 (使用 skimage)
# skimage.filters.laplace 返回浮点数
laplacian_filtered_img_sk = filters.laplace(img_sk_gray_uint8)

# 拉普拉斯滤波器的结果通常是正负值,表示梯度的二阶变化
# 为了显示,通常取绝对值并缩放到 0-255 范围
laplacian_filtered_img_sk_abs = np.abs(laplacian_filtered_img_sk)
# 缩放到 0-255
# 找到最大值 (非零)
max_val = np.max(laplacian_filtered_img_sk_abs)
if max_val > 0:
    laplacian_filtered_img_sk_scaled = (laplacian_filtered_img_sk_abs / max_val * 255).astype(np.uint8)
else:
    laplacian_filtered_img_sk_scaled = np.zeros_like(img_sk_gray_uint8) # 如果都是零,就创建一个全黑图像


# 应用拉普拉斯滤波器 (使用 scipy)
# scipy.ndimage.laplace 返回浮点数
laplacian_filtered_img_sp = ndimage.laplace(img_sk_gray_uint8)
# 同样处理结果以便显示
laplacian_filtered_img_sp_abs = np.abs(laplacian_filtered_img_sp)
max_val_sp = np.max(laplacian_filtered_img_sp_abs)
if max_val_sp > 0:
     laplacian_filtered_img_sp_scaled = (laplacian_filtered_img_sp_abs / max_val_sp * 255).astype(np.uint8)
else:
     laplacian_filtered_img_sp_scaled = np.zeros_like(img_sk_gray_uint8)


# 显示原始图像和拉普拉斯滤波后的图像
plt.figure(figsize=(15, 5))

plt.subplot(1, 3, 1)
plt.imshow(img_sk_gray_uint8, cmap='gray')
plt.title('原始灰度图像')
plt.axis('off')

plt.subplot(1, 3, 2)
plt.imshow(laplacian_filtered_img_sk_scaled, cmap='gray')
plt.title('拉普拉斯滤波 (skimage, 缩放显示)')
plt.axis('off')

plt.subplot(1, 3, 3)
plt.imshow(laplacian_filtered_img_sp_scaled, cmap='gray')
plt.title('拉普拉斯滤波 (scipy, 缩放显示)')
plt.axis('off')

plt.tight_layout()
plt.show()

# 保存结果
io.imsave('output_laplacian_filtered_skimage.jpg', laplacian_filtered_img_sk_scaled)
io.imsave('output_laplacian_filtered_scipy.jpg', laplacian_filtered_img_sp_scaled)
print("拉普拉斯滤波已完成并保存结果。")


代码解释:

  • laplacian_filtered_img_sk = filters.laplace(img_sk_gray_uint8):使用skimage.filters.laplace()应用拉普拉斯滤波器。
  • laplacian_filtered_img_sk_abs = np.abs(laplacian_filtered_img_sk):计算滤波结果的绝对值。拉普拉斯算子会产生正负值,表示边缘的方向信息,但为了显示边缘强度,通常取绝对值。
  • max_val = np.max(laplacian_filtered_img_sk_abs)和随后的缩放:将绝对值结果缩放到0-255范围以便以图像形式显示。这是通过将所有值除以最大值,然后乘以255实现简单的线性缩放。
  • laplacian_filtered_img_sk_scaled.astype(np.uint8):将缩放后的结果转换为uint8类型。
  • SciPy的ndimage.laplace()函数用法类似。

Sobel算子(Sobel Operator):是一种一阶微分算子,用于检测图像的边缘强度和方向。它包含两个卷积核,一个用于检测水平边缘(Gx),一个用于检测垂直边缘(Gy)。

  • 边缘强度(Magnitude)通常计算为 ( \sqrt{G_x^2 + G_y^2} ) 或 ( |G_x| + |G_y| )。
  • 边缘方向(Direction)通常计算为 ( \arctan(G_y / G_x) )。
import numpy as np
from PIL import Image
from skimage import io, filters
import matplotlib.pyplot as plt
from scipy import ndimage

# 假设 img_sk_gray_uint8 是上面准备好的灰度图像 uint8 数组

# 应用 Sobel 滤波器 (使用 skimage)
# filters.sobel 返回边缘强度图像 (浮点数)
sobel_filtered_img_sk = filters.sobel(img_sk_gray_uint8)

# skimage.filters 还提供了单独的水平和垂直 Sobel 核应用
# sobel_horizontal = filters.sobel_h(img_sk_gray_uint8)
# sobel_vertical = filters.sobel_v(img_sk_gray_uint8)
# sobel_magnitude = np.sqrt(sobel_horizontal**2 + sobel_vertical**2) # 边缘强度

# 缩放结果以便显示
# sobel 结果通常是正值,最大值可能远超255,需要缩放
sobel_filtered_img_sk_scaled = (sobel_filtered_img_sk / np.max(sobel_filtered_img_sk) * 255).astype(np.uint8)


# 应用 Sobel 滤波器 (使用 scipy)
# scipy.ndimage.sobel 返回水平和垂直梯度的数组 (浮点数)
sobel_horizontal_sp = ndimage.sobel(img_sk_gray_uint8, axis=1) # axis=1 对应 x 方向 (水平边缘)
sobel_vertical_sp = ndimage.sobel(img_sk_gray_uint8, axis=0)   # axis=0 对应 y 方向 (垂直边缘)

# 计算边缘强度 (使用勾股定理)
sobel_magnitude_sp = np.sqrt(sobel_horizontal_sp**2 + sobel_vertical_sp**2)

# 缩放结果以便显示
sobel_magnitude_sp_scaled = (sobel_magnitude_sp / np.max(sobel_magnitude_sp) * 255).astype(np.uint8)


# 显示原始图像和 Sobel 滤波后的图像
plt.figure(figsize=(15, 5))

plt.subplot(1, 3, 1)
plt.imshow(img_sk_gray_uint8, cmap='gray')
plt.title('原始灰度图像')
plt.axis('off')

plt.subplot(1, 3, 2)
plt.imshow(sobel_filtered_img_sk_scaled, cmap='gray')
plt.title('Sobel 滤波 (skimage, 强度, 缩放显示)')
plt.axis('off')

plt.subplot(1, 3, 3)
plt.imshow(sobel_magnitude_sp_scaled, cmap='gray')
plt.title('Sobel 滤波 (scipy, 强度, 缩放显示)')
plt.axis('off')

plt.tight_layout()
plt.show()

# 保存结果
io.imsave('output_sobel_filtered_skimage.jpg', sobel_filtered_img_sk_scaled)
io.imsave('output_sobel_filtered_scipy.jpg', sobel_magnitude_sp_scaled)
print("Sobel 滤波已完成并保存结果。")



代码解释:

  • sobel_filtered_img_sk = filters.sobel(img_sk_gray_uint8):使用skimage.filters.sobel()计算图像的Sobel边缘强度。它内部计算了水平和垂直梯度,然后结合起来得到边缘强度。返回浮点数数组。
  • sobel_filtered_img_sk_scaled = (sobel_filtered_img_sk / np.max(sobel_filtered_img_sk) * 255).astype(np.uint8):对Sobel结果进行缩放以便显示。Sobel结果通常是正值,最大值可能很大,需要除以最大值进行归一化(缩放到0-1范围),然后乘以255转换为0-255范围的uint8。
  • sobel_horizontal_sp = ndimage.sobel(img_sk_gray_uint8, axis=1)和sobel_vertical_sp = ndimage.sobel(img_sk_gray_uint8, axis=0):使用scipy.ndimage.sobel()分别计算水平(axis=1)和垂直(axis=0)方向的Sobel梯度。返回浮点数数组,可能包含负值。
  • sobel_magnitude_sp = np.sqrt(sobel_horizontal_sp2 + sobel_vertical_sp2):根据水平和垂直梯度计算边缘强度,这里使用了欧几里得距离(勾股定理)。
  • sobel_magnitude_sp_scaled = (sobel_magnitude_sp / np.max(sobel_magnitude_sp) * 255).astype(np.uint8):对SciPy计算的边缘强度进行缩放以便显示。

还有许多其他的滤波器,例如Prewitt算子、Roberts算子、Canny边缘检测器(通常认为是边缘检测算法而不是简单的滤波器,但涉及滤波步骤)。

5.3卷积核的创建与应用(手动)

虽然库函数很方便,但理解如何手动应用卷积核有助于深入理解滤波原理。我们可以创建一个自定义的卷积核,然后使用SciPy的convolve或convolve2d函数应用它。

import numpy as np
from PIL import Image
from skimage import io
from scipy.ndimage import convolve, convolve1d # convolve for multi-dim, convolve1d for 1D
import matplotlib.pyplot as plt

# 假设 img_sk_gray_uint8 是上面准备好的灰度图像 uint8 数组
# 转换为浮点数进行卷积,因为卷积结果可能有小数或超出 0-255 范围
img_float = img_sk_gray_uint8.astype(np.float64) # 使用 float64 确保精度


# 创建一个 3x3 的均值滤波核
mean_kernel = np.ones((3, 3)) / 9.0
print("3x3 均值滤波核:\n", mean_kernel)

# 创建一个 3x3 的高斯近似核 (简单的示例,不是精确的高斯核)
# weights = [1, 2, 1] for 1D, [1, 2, 1]x[1, 2, 1] for 2D (Outer product)
gaussian_approx_kernel = np.array([[1, 2, 1],
                                   [2, 4, 2],
                                   [1, 2, 1]], dtype=np.float64) / 16.0
print("3x3 高斯近似核:\n", gaussian_approx_kernel)


# 创建一个 3x3 的锐化核 (中心增强,周围抑制)
# 这是一个简单的锐化核,可以增强边缘
sharpen_kernel = np.array([[ 0, -1,  0],
                           [-1,  5, -1],
                           [ 0, -1,  0]], dtype=np.float64)
print("3x3 锐化核:\n", sharpen_kernel)

# 创建一个 3x3 的边缘检测核 (示例:简单的差分核)
edge_kernel = np.array([[ 0, -1,  0],
                        [-1,  4, -1],
                        [ 0, -1,  0]], dtype=np.float64) # 类似于拉普拉斯核
print("3x3 边缘检测核 (拉普拉斯近似):\n", edge_kernel)


# 应用卷积核
# SciPy 的 convolve 函数
# input: 输入数组
# weights: 卷积核
# mode: 边界处理方式 ('constant', 'nearest', 'reflect', 'wrap')
#   'constant': 填充常数 (cval 参数指定),默认0
#   'nearest': 边界像素值延伸
#   'reflect': 反射边界
#   'wrap': 环绕边界
# cval: constant 模式下的填充值
# origin: 核的中心点偏移 (通常为 0 表示中心)

# 应用均值滤波核
mean_convolved_img = convolve(img_float, mean_kernel, mode='nearest')

# 应用高斯近似核
gaussian_convolved_img = convolve(img_float, gaussian_approx_kernel, mode='nearest')

# 应用锐化核
sharpen_convolved_img = convolve(img_float, sharpen_kernel, mode='nearest')

# 应用边缘检测核
edge_convolved_img = convolve(img_float, edge_kernel, mode='nearest')


# 将结果转换回 uint8 进行显示和保存
# 卷积结果可能超出 0-255 范围,需要裁剪和转换
mean_convolved_img_uint8 = np.clip(mean_convolved_img, 0, 255).astype(np.uint8)
gaussian_convolved_img_uint8 = np.clip(gaussian_convolved_img, 0, 255).astype(np.uint8)
sharpen_convolved_img_uint8 = np.clip(sharpen_convolved_img, 0, 255).astype(np.uint8)
edge_convolved_img_uint8 = np.clip(edge_convolved_img, 0, 255).astype(np.uint8)


# 显示结果
plt.figure(figsize=(20, 10))

plt.subplot(2, 3, 1)
plt.imshow(img_sk_gray_uint8, cmap='gray')
plt.title('原始灰度图像')
plt.axis('off')

plt.subplot(2, 3, 2)
plt.imshow(mean_convolved_img_uint8, cmap='gray')
plt.title('均值卷积 (3x3)')
plt.axis('off')

plt.subplot(2, 3, 3)
plt.imshow(gaussian_convolved_img_uint8, cmap='gray')
plt.title('高斯近似卷积 (3x3)')
plt.axis('off')

plt.subplot(2, 3, 4)
plt.imshow(sharpen_convolved_img_uint8, cmap='gray')
plt.title('锐化卷积 (3x3)')
plt.axis('off')

plt.subplot(2, 3, 5)
plt.imshow(edge_convolved_img_uint8, cmap='gray')
plt.title('边缘检测卷积 (3x3)')
plt.axis('off')

plt.tight_layout()
plt.show()

# 保存结果
io.imsave('output_mean_convolved.jpg', mean_convolved_img_uint8)
io.imsave('output_gaussian_convolved.jpg', gaussian_convolved_img_uint8)
io.imsave('output_sharpen_convolved.jpg', sharpen_convolved_img_uint8)
io.imsave('output_edge_convolved.jpg', edge_convolved_img_uint8)
print("手动卷积已完成并保存结果。")


代码解释:

  • from scipy.ndimage import convolve:导入SciPy的convolve函数,用于多维卷积。
  • img_float = img_sk_gray_uint8.astype(np.float64):将图像数据转换为浮点数类型。这是进行卷积的标准做法,因为卷积涉及乘积求和,结果可能有小数或超出uint8的范围。使用float64提供更高的精度。
  • mean_kernel = np.ones((3, 3)) / 9.0:创建一个3x3的均值滤波核。所有元素都是1,然后除以总元素个数(9),使核的总和为1,这样在平坦区域卷积后亮度不会改变。
  • gaussian_approx_kernel = np.array([...]) / 16.0:创建一个3x3的高斯近似核。这个核的权重是根据二维离散高斯函数的形状设计的,中心权重最高,向外递减。核的总和为16,除以16进行归一化。
  • sharpen_kernel = np.array([...]):创建一个锐化核。这个核的设计原理是:中心像素减去其邻域像素的加权和(例如,中心权重为5,四个邻居权重为-1)。这会突出中心像素相对于其周围的变化,从而增强边缘。
  • edge_kernel = np.array([...]):创建一个边缘检测核,这里是一个简单的拉普拉斯核近似。中心权重为正,周围权重为负,总和为0。它检测像素值变化的“凸起”或“凹陷”,对应于边缘。
  • convolve(img_float, mean_kernel, mode='nearest'):使用scipy.ndimage.convolve()函数应用卷积核。
  • 第一个参数是输入图像数组(浮点数类型)。
  • 第二个参数是卷积核数组。
  • mode='nearest':指定边界处理方式。'nearest'模式表示在图像边界之外的区域,用最近的边界像素值进行填充。其他模式如'constant' (用常数填充,默认0)、'reflect' (反射边界)和'wrap' (环绕边界)适用于不同场景。边界处理会影响滤波结果在图像边缘的表现。
  • np.clip(..., 0, 255).astype(np.uint8):将卷积结果裁剪到0-255范围并转换回uint8类型,以便显示和保存为标准图像格式。锐化和边缘检测的结果可能包含负值或超出255的值,裁剪是必须的。

手动创建和应用卷积核,可以帮助我们理解各种滤波效果是如何通过不同的权重组合实现的。

5.4频域滤波简介(使用傅里叶变换)

除了在空间域进行滤波,图像处理也可以在频域进行。通过傅里叶变换,可以将图像从空间域转换到频域,表示图像中不同频率成分的强度。在频域中,高频成分对应于图像的细节、噪声和边缘,低频成分对应于图像的平滑区域和整体结构。

频域滤波的步骤:

  1. 对图像进行傅里叶变换。
  2. 在频域中设计一个滤波器(一个与频域图像相同大小的掩模),根据需要抑制或增强特定频率成分。
  3. 将频域图像与滤波器相乘(逐元素相乘)。
  4. 对结果进行逆傅里叶变换,将图像转换回空间域。
import numpy as np
from PIL import Image
from skimage import io, color
from scipy.fft import fft2, ifft2, fftshift, ifftshift # 导入二维傅里叶变换相关函数
import matplotlib.pyplot as plt

# 假设 img_sk_gray_uint8 是上面准备好的灰度图像 uint8 数组

# 转换为浮点数类型进行傅里叶变换
img_float = img_sk_gray_uint8.astype(np.float64)


# 1. 进行二维傅里叶变换 (FFT)
# fft2() 计算二维快速傅里叶变换
# 结果是复数数组
f_transform = fft2(img_float)

# 2. 将零频率成分(直流成分)移到频谱中心
# fftshift() 将零频率分量移动到频谱中心,便于可视化和频域滤波核的设计
f_transform_shifted = fftshift(f_transform)

# 可视化频谱 (幅度谱)
# 幅度谱表示不同频率成分的强度
# np.abs() 计算复数的幅度
# np.log() 用于压缩幅度范围,因为直流成分的幅度通常远大于其他频率成分
magnitude_spectrum = np.log(np.abs(f_transform_shifted) + 1) # 加1避免log(0)

# 显示原始图像和幅度谱
plt.figure(figsize=(12, 6))

plt.subplot(1, 2, 1)
plt.imshow(img_sk_gray_uint8, cmap='gray')
plt.title('原始灰度图像')
plt.axis('off')

plt.subplot(1, 2, 2)
plt.imshow(magnitude_spectrum, cmap='gray')
plt.title('幅度谱 (对数缩放)')
plt.axis('off')

plt.tight_layout()
plt.show()


# 3. 设计频域滤波器 (例如,一个简单的低通滤波器)
# 低通滤波器保留低频成分,抑制高频成分,用于平滑图像
# 我们创建一个中心是1(保留低频),边缘是0(抑制高频)的掩模

rows, cols = img_float.shape
center_row, center_col = rows // 2, cols // 2

# 创建一个与频域图像相同尺寸的掩模
low_pass_filter = np.zeros((rows, cols), dtype=np.float64)

# 定义截止频率 (距离中心多远算低频)
cutoff_radius = 30 # 像素单位的距离

# 在掩模中,距离中心小于截止频率的区域设置为1
# 使用 NumPy 的广播和距离计算
y, x = np.ogrid[:rows, :cols]
distance_from_center = np.sqrt((x - center_col)**2 + (y - center_row)**2)
low_pass_filter[distance_from_center <= cutoff_radius] = 1.0

# 注意:这个简单的低通滤波器是一个理想低通滤波器,在截止频率处突变,可能会引入振铃效应
# 实际应用中常使用 Butterworth 或 Gaussian 低通滤波器,它们过渡更平滑


# 4. 在频域中应用滤波器 (逐元素相乘)
# 将移频后的频谱与滤波器掩模相乘
filtered_f_transform_shifted = f_transform_shifted * low_pass_filter

# 5. 将零频率成分移回左上角
# ifftshift() 将零频率分量移回原始位置
filtered_f_transform = ifftshift(filtered_f_transform_shifted)

# 6. 进行二维逆傅里叶变换 (IFFT)
# ifft2() 计算二维逆快速傅里叶变换
# 结果是复数,但对于实数图像,其逆变换的实部就是滤波后的图像
filtered_img_float = ifft2(filtered_f_transform).real

# 裁剪并转换回 uint8 进行显示和保存
filtered_img_uint8 = np.clip(filtered_img_float, 0, 255).astype(np.uint8)


# 显示原始图像和频域低通滤波后的图像
plt.figure(figsize=(12, 6))

plt.subplot(1, 2, 1)
plt.imshow(img_sk_gray_uint8, cmap='gray')
plt.title('原始灰度图像')
plt.axis('off')

plt.subplot(1, 2, 2)
plt.imshow(filtered_img_uint8, cmap='gray')
plt.title('频域低通滤波 (截止频率=30)')
plt.axis('off')

plt.tight_layout()
plt.show()

# 保存结果
io.imsave('output_frequency_lowpass_filtered.jpg', filtered_img_uint8)
print("频域低通滤波已完成并保存结果。")


# 设计一个高通滤波器
# 高通滤波器保留高频成分,抑制低频成分,用于边缘检测或锐化
# 高通滤波器 = 1 - 低通滤波器 (对于理想滤波器)
high_pass_filter = 1.0 - low_pass_filter


# 在频域中应用高通滤波器
filtered_f_transform_shifted_hp = f_transform_shifted * high_pass_filter

# 逆移频
filtered_f_transform_hp = ifftshift(filtered_f_transform_shifted_hp)

# 逆傅里叶变换
filtered_img_float_hp = ifft2(filtered_f_transform_hp).real

# 裁剪并转换回 uint8
filtered_img_uint8_hp = np.clip(filtered_img_float_hp, 0, 255).astype(np.uint8)


# 显示原始图像和频域高通滤波后的图像
plt.figure(figsize=(12, 6))

plt.subplot(1, 2, 1)
plt.imshow(img_sk_gray_uint8, cmap='gray')
plt.title('原始灰度图像')
plt.axis('off')

plt.subplot(1, 2, 2)
plt.imshow(filtered_img_uint8_hp, cmap='gray')
plt.title('频域高通滤波 (截止频率=30)')
plt.axis('off')

plt.tight_layout()
plt.show()

# 保存结果
io.imsave('output_frequency_highpass_filtered.jpg', filtered_img_uint8_hp)
print("频域高通滤波已完成并保存结果。")


代码解释:

  • from scipy.fft import fft2, ifft2, fftshift, ifftshift:导入SciPy库中用于二维傅里叶变换和逆变换以及频谱移位的函数。
  • img_float = img_sk_gray_uint8.astype(np.float64):将图像数据转换为浮点数类型。进行傅里叶变换通常需要浮点数输入,因为结果是复数。
  • f_transform = fft2(img_float):对图像数组进行二维快速傅里叶变换。结果f_transform是一个与输入图像同尺寸的复数NumPy数组。
  • f_transform_shifted = fftshift(f_transform):使用fftshift()将频谱的零频率成分(DC分量,代表图像的平均亮度)从左上角移动到频谱的中心。这使得低频成分集中在中心,高频成分分布在边缘,更便于设计频域滤波器和可视化频谱。
  • magnitude_spectrum = np.log(np.abs(f_transform_shifted) + 1):计算并可视化幅度谱。np.abs()计算复数数组的幅度(强度)。np.log(... + 1)用于对幅度进行对数缩放,因为直流成分的幅度通常比其他频率成分大很多,对数缩放可以使其他部分的细节更清晰可见。加1是为了避免log(0)。
  • low_pass_filter = np.zeros((rows, cols), dtype=np.float64):创建一个与频域图像(频谱)同尺寸的NumPy数组,用于作为频域低通滤波器掩模。初始化为零。
  • center_row, center_col = rows // 2, cols // 2:计算频谱的中心坐标。
  • y, x = np.ogrid[:rows, :cols]:创建网格坐标数组,用于计算每个点到中心的距离。
  • distance_from_center = np.sqrt((x - center_col)**2 + (y - center_row)**2):计算每个频域点到中心的欧几里得距离。在频域中,距离中心越近代表频率越低,距离越远代表频率越高。
  • low_pass_filter[distance_from_center <= cutoff_radius] = 1.0:设计一个理想低通滤波器。在距离中心小于等于cutoff_radius的区域,将掩模值设置为1.0(允许这些频率通过);在其他区域(高频),掩模值保持为0(阻止这些频率通过)。
  • filtered_f_transform_shifted = f_transform_shifted * low_pass_filter:在频域中应用滤波器。将移频后的频谱与滤波器掩模进行逐元素乘法。
  • filtered_f_transform = ifftshift(filtered_f_transform_shifted):使用ifftshift()将零频率成分移回原始位置(左上角),为逆傅里叶变换做准备。
  • filtered_img_float = ifft2(filtered_f_transform).real:对滤波后的频域图像进行二维逆快速傅里叶变换。结果ifft2()返回复数,但对于从实数图像变换来的频谱,其逆变换的虚部理论上应该非常接近于零(由于浮点误差可能有微小非零值),因此取实部.real就是滤波后的空间域图像。
  • filtered_img_uint8 = np.clip(filtered_img_float, 0, 255).astype(np.uint8):将逆变换结果裁剪到0-255范围并转换为uint8类型,以便显示和保存。
  • high_pass_filter = 1.0 - low_pass_filter:设计一个理想高通滤波器。对于理想滤波器,高通滤波器掩模可以通过将低通滤波器掩模的值取反(1减去原值)得到。高通滤波器保留高频成分,抑制低频成分。
  • 频域高通滤波的过程与低通滤波类似,只是使用了不同的滤波器掩模。

频域滤波提供了另一种强大的视角来理解和操作图像。通过在频域中选择性地处理不同频率成分,我们可以实现空间域中难以或效率低下的滤波效果。