目录

  1. 引言:图像处理的基石——NumPy 与 SciPy
    • 1.1 什么是图像处理?
    • 1.2 Python 在图像处理中的优势
    • 1.3 NumPySciPy 的核心作用
    • 1.4 图像加载与保存的补充:Pillow (PIL Fork)
  2. 图像表示:NumPy 数组的魔法
    • 2.1 图像即数据:多维数组
    • 2.2 像素值与颜色通道
    • 2.3 内存布局与数据类型
  3. 使用 NumPy 进行基础图像操作
    • 3.1 图像的加载与保存 (使用 Pillow 替代 scipy.misc)
    • 3.2 像素访问与修改
    • 3.3 图像的灰度化 (Grayscaling)
    • 3.4 图像的裁剪 (Cropping)
    • 3.5 图像的尺寸调整 (Resizing)
    • 3.6 颜色通道分离与合并
    • 3.7 图像的几何变换 (翻转、旋转 90 度)
  4. SciPy.ndimage:N 维图像处理的利器
    • 4.1 scipy.ndimage 模块概述
    • 4.2 图像滤波 (Filtering)
      • 4.2.1 卷积 (Convolution) 原理简述
      • 4.2.2 线性滤波:高斯滤波 (gaussian_filter)
      • 4.2.3 非线性滤波:中值滤波 (median_filter)
      • 4.2.4 边缘检测:Sobel 滤波器 (sobel)、Laplace 滤波器 (laplace)
    • 4.3 形态学操作 (Morphological Operations)
      • 4.3.1 腐蚀 (Erosion)
      • 4.3.2 膨胀 (Dilation)
      • 4.3.3 开运算 (Opening) 与 闭运算 (Closing)
    • 4.4 几何变换 (Geometric Transformations)
      • 4.4.1 旋转 (rotate)
      • 4.4.2 移位 (shift)
      • 4.4.3 缩放 (zoom)
    • 4.5 图像测量与特征提取
      • 4.5.1 连通分量标记 (label)
      • 4.5.2 区域属性测量
  5. 性能考量与优化
    • 5.1 NumPy 的向量化操作
    • 5.2 SciPy 底层的 C/Fortran 优化
  6. 局限性与高级库介绍
    • 6.1 NumPy 和 SciPy 的局限
    • 6.2 Pillow (PIL Fork):基础图像操作与格式支持
    • 6.3 OpenCV (Open Computer Vision):高级计算机视觉与实时处理
    • 6.4 scikit-image:科学图像处理的综合库
  7. 知识扩展:图像处理在实际应用中的场景
    • 7.1 医疗影像分析
    • 7.2 卫星遥感与地理信息系统 (GIS)
    • 7.3 工业质检与自动化
    • 7.4 计算机视觉与机器学习
  8. 代码示例
    • 8.1 图像加载、灰度化、裁剪
    • 8.2 高斯模糊与边缘检测
    • 8.3 形态学操作
    • 8.4 几何旋转
  9. 总结

1. 引言:图像处理的基石——NumPy 与 SciPy

1.1 什么是图像处理?

图像处理是对图像进行操作,以改善图像质量、提取有意义的信息或为后续的计算机视觉任务做准备的技术。它涵盖了从简单的亮度/对比度调整到复杂的特征提取和模式识别等一系列操作。

1.2 Python 在图像处理中的优势

Python 凭借其简洁的语法、丰富的库生态系统以及强大的科学计算能力,已成为图像处理和计算机视觉领域的热门选择。它的优势在于:

  • 易学易用:快速原型开发和迭代。
  • 强大的库支持NumPy, SciPy, Pillow, OpenCV, scikit-image 等。
  • 跨平台:在不同操作系统上均可运行。

1.3 NumPySciPy 的核心作用

  • NumPy (Numerical Python)
    • 提供高性能的多维数组对象 (ndarray),是所有图像处理操作的基础数据结构。
    • 支持大量广播函数和向量化操作,极大提升了数组运算的效率。
    • 图像在 Python 中通常被表示为 NumPy 数组。
  • SciPy (Scientific Python)
    • 构建在 NumPy 之上,提供了一系列高级科学计算功能。
    • scipy.ndimage 模块专门用于 N 维图像处理,提供了多种滤波、形态学、几何变换和测量功能,且底层通常由优化过的 C/Fortran 代码实现,性能优异。

1.4 图像加载与保存的补充:Pillow (PIL Fork)

原 GeeksforGeeks 文章可能提到 scipy.misc.imread 等函数,但这些函数在 SciPy 的新版本中已被弃用并移除。因此,在实际应用中,我们通常会使用 Pillow (Python Imaging Library 的一个分支) 来进行图像的加载、保存和一些基本的处理。Pillow 提供了 Image 对象,可以方便地与 NumPy 数组进行转换。

2. 图像表示:NumPy 数组的魔法

2.1 图像即数据:多维数组

在计算机中,图像被表示为像素的网格。在 NumPy 中,这自然地对应于多维数组:

  • 灰度图像:通常表示为二维数组 (height, width),每个元素代表一个像素的亮度值(通常在 0-255 之间)。
  • 彩色图像:通常表示为三维数组 (height, width, channels)
    • height:图像的高度(像素行数)。
    • width:图像的宽度(像素列数)。
    • channels:颜色通道数。对于常见的 RGB 图像,channels 为 3 (红、绿、蓝)。有时还会包含 Alpha 通道 (透明度),则为 4 (RGBA)。

2.2 像素值与颜色通道

  • 像素值:每个数组元素的值代表对应像素的颜色或亮度。对于 8 位图像,值通常在 0 到 255 之间。
    • 0 表示最暗(黑色)。
    • 255 表示最亮(白色)。
  • 颜色通道:RGB 图像的每个像素由红、绿、蓝三个分量组成。例如,一个红色像素可能是 [255, 0, 0],一个白色像素是 [255, 255, 255]

2.3 内存布局与数据类型

  • 数据类型 (dtype):图像像素值通常使用 uint8 (无符号 8 位整数) 来表示 0-255 的范围。在进行一些数学运算时,可能需要转换为 float 类型(如 float32float64),以避免溢出或保持精度,运算后再转换回 uint8
  • 内存布局NumPy 数组的连续内存布局使得对图像数据的批量操作非常高效。

3. 使用 NumPy 进行基础图像操作

NumPy 提供了直接操作多维数组的能力,这使得进行各种基础图像处理变得直观和高效。

3.1 图像的加载与保存 (使用 Pillow 替代 scipy.misc)

为了确保代码的兼容性和可用性,我们将使用 Pillow 库来处理图像的 I/O。

from PIL import Image
import numpy as np

# 假设有一个名为 'example.jpg' 的图像文件
# 加载图像
try:
    img = Image.open('example.jpg')
    # 将 Pillow Image 对象转换为 NumPy 数组
    img_np = np.array(img)
    print(f"加载图像的形状: {img_np.shape}, 数据类型: {img_np.dtype}")

    # 保存图像 (将 NumPy 数组转换回 Pillow Image 对象)
    Image.fromarray(img_np).save('output_example.jpg')
    print("图像保存成功为 output_example.jpg")

except FileNotFoundError:
    print("example.jpg 文件未找到。请确保在当前目录下有图像文件。")
    # 如果没有文件,创建一个虚拟图像数组用于演示
    img_np = np.random.randint(0, 256, size=(100, 150, 3), dtype=np.uint8)
    print(f"使用虚拟图像进行演示,形状: {img_np.shape}, 数据类型: {img_np.dtype}")

3.2 像素访问与修改

NumPy 的切片和索引操作可用于访问和修改图像的单个像素或区域。

# 访问单个像素 (例如,图像左上角的像素)
pixel = img_np[0, 0]
print(f"左上角像素值: {pixel}") # 对于 RGB 图像,会是 [R, G, B] 数组

# 修改单个像素 (例如,将左上角像素设为红色)
if img_np.ndim == 3: # 仅对彩色图像操作
    img_np[0, 0] = [255, 0, 0]
    print(f"修改后的左上角像素值: {img_np[0, 0]}")

# 访问图像区域 (例如,裁剪左上角 10x10 区域)
region = img_np[0:10, 0:10]
print(f"10x10 区域的形状: {region.shape}")

# 将图像的某个区域设置为黑色
img_np[50:60, 50:60] = 0 # 0 对于 uint8 数组表示黑色

3.3 图像的灰度化 (Grayscaling)

将彩色图像转换为灰度图像的常见方法是计算每个像素 R、G、B 分量的加权平均值。

# 方法一:简单的平均值
if img_np.ndim == 3:
    gray_img_avg = np.mean(img_np, axis=2).astype(np.uint8)
    print(f"灰度图像 (平均值) 形状: {gray_img_avg.shape}")
    Image.fromarray(gray_img_avg).save('output_gray_avg.jpg')

# 方法二:加权平均值 (更符合人眼感知)
if img_np.ndim == 3:
    R, G, B = img_np[:, :, 0], img_np[:, :, 1], img_np[:, :, 2]
    gray_img_weighted = (0.2989 * R + 0.5870 * G + 0.1140 * B).astype(np.uint8)
    print(f"灰度图像 (加权平均) 形状: {gray_img_weighted.shape}")
    Image.fromarray(gray_img_weighted).save('output_gray_weighted.jpg')

# 如果是灰度图像,直接使用
if img_np.ndim == 2:
    print("图像已经是灰度图像。")
    gray_img_weighted = img_np

3.4 图像的裁剪 (Cropping)

通过 NumPy 的切片功能,可以轻松裁剪图像。

height, width = gray_img_weighted.shape
# 裁剪中心区域 50x50 像素
start_row, end_row = height // 2 - 25, height // 2 + 25
start_col, end_col = width // 2 - 25, width // 2 + 25
cropped_img = gray_img_weighted[start_row:end_row, start_col:end_col]
print(f"裁剪图像形状: {cropped_img.shape}")
Image.fromarray(cropped_img).save('output_cropped.jpg')

3.5 图像的尺寸调整 (Resizing)

NumPy 自身没有直接的图像缩放功能,但我们可以使用 Pillowscipy.ndimage.zoom。这里先用 Pillow 演示。

# 将图像缩放到 50x50 像素
resized_img_pil = Image.fromarray(img_np).resize((50, 50))
resized_img_np = np.array(resized_img_pil)
print(f"缩放图像形状: {resized_img_np.shape}")
Image.fromarray(resized_img_np).save('output_resized.jpg')

3.6 颜色通道分离与合并

对于彩色图像,可以分离和独立操作各个颜色通道。

if img_np.ndim == 3:
    R = img_np[:, :, 0]
    G = img_np[:, :, 1]
    B = img_np[:, :, 2]

    print(f"红色通道形状: {R.shape}")

    # 只显示红色通道 (将G和B通道设为0)
    red_channel_only = np.zeros_like(img_np)
    red_channel_only[:, :, 0] = R
    Image.fromarray(red_channel_only).save('output_red_channel.jpg')

    # 合并通道 (例如,将R和B通道互换)
    swapped_channels = np.stack([B, G, R], axis=2)
    Image.fromarray(swapped_channels).save('output_swapped_channels.jpg')

3.7 图像的几何变换 (翻转、旋转 90 度)

NumPy 提供 np.fliplr (左右翻转), np.flipud (上下翻转) 和 np.rot90 (旋转 90 度) 等函数。

# 左右翻转
flipped_lr = np.fliplr(img_np)
Image.fromarray(flipped_lr).save('output_flipped_lr.jpg')

# 上下翻转
flipped_ud = np.flipud(img_np)
Image.fromarray(flipped_ud).save('output_flipped_ud.jpg')

# 旋转 90 度 (逆时针)
rotated_90 = np.rot90(img_np)
Image.fromarray(rotated_90).save('output_rotated_90.jpg')
print(f"旋转 90 度后图像形状: {rotated_90.shape}") # 注意形状会互换

4. SciPy.ndimage:N 维图像处理的利器

scipy.ndimage 模块是 SciPy 中专门用于 N 维图像处理的子包。它提供了许多高性能的函数,适用于图像滤波、形态学操作、几何变换和测量等。

4.1 scipy.ndimage 模块概述

  • 所有函数都直接作用于 NumPy 数组。
  • 底层实现由 C 或 Fortran 编写,因此非常高效。
  • 提供了广泛的图像处理算法,是进行科学图像分析的重要工具。

4.2 图像滤波 (Filtering)

滤波是图像处理中最常见的操作之一,用于去除噪声、平滑图像或突出特定特征。

4.2.1 卷积 (Convolution) 原理简述

图像滤波的核心是卷积。它涉及到用一个小的矩阵(称为卷积核 Kernel滤波器 Filter)扫描图像的每个像素。在每个位置,核的元素与图像对应区域的像素值相乘,然后将结果相加,作为新图像中该位置的像素值。不同的核可以实现不同的滤波效果(如模糊、锐化、边缘检测)。

4.2.2 线性滤波:高斯滤波 (gaussian_filter)

高斯滤波是一种常用的平滑滤波器,用于减少图像噪声和模糊图像。它使用一个高斯函数作为核,对图像进行加权平均。

from scipy import ndimage
import matplotlib.pyplot as plt

# 假设 gray_img_weighted 是上面生成的灰度图像
if 'gray_img_weighted' not in locals():
    gray_img_weighted = np.random.randint(0, 256, size=(100, 150), dtype=np.uint8)

# 高斯滤波,sigma 值越大,模糊程度越高
blurred_img_gaussian = ndimage.gaussian_filter(gray_img_weighted, sigma=3)
Image.fromarray(blurred_img_gaussian).save('output_gaussian_blur.jpg')
4.2.3 非线性滤波:中值滤波 (median_filter)

中值滤波是一种非线性滤波器,特别适用于去除椒盐噪声 (salt-and-pepper noise)。它将每个像素替换为其邻域像素的中值,而不是平均值。

# 添加一些椒盐噪声用于演示
noisy_img = np.copy(gray_img_weighted)
num_salt = int(0.02 * gray_img_weighted.size)
coords = [np.random.randint(0, i - 1, num_salt) for i in noisy_img.shape]
noisy_img[coords[0], coords[1]] = 255 # 白色噪声
num_pepper = int(0.02 * gray_img_weighted.size)
coords = [np.random.randint(0, i - 1, num_pepper) for i in noisy_img.shape]
noisy_img[coords[0], coords[1]] = 0 # 黑色噪声
Image.fromarray(noisy_img).save('output_noisy.jpg')

# 中值滤波
median_filtered_img = ndimage.median_filter(noisy_img, size=3) # size是核的大小
Image.fromarray(median_filtered_img).save('output_median_filtered.jpg')
4.2.4 边缘检测:Sobel 滤波器 (sobel)、Laplace 滤波器 (laplace)

边缘检测用于识别图像中亮度发生显著变化的区域,这通常对应于物体的轮廓。

  • Sobel 滤波器:用于计算图像在水平和垂直方向上的梯度近似值,从而突出边缘。
sobel_x = ndimage.sobel(gray_img_weighted, axis=0) # 垂直方向边缘
sobel_y = ndimage.sobel(gray_img_weighted, axis=1) # 水平方向边缘
edges_sobel = np.hypot(sobel_x, sobel_y) # 梯度的模长
# 需要归一化到 0-255 才能保存为图像
edges_sobel = (edges_sobel / edges_sobel.max() * 255).astype(np.uint8)
Image.fromarray(edges_sobel).save('output_edges_sobel.jpg')
  • Laplace 滤波器:一个二阶微分算子,用于检测图像中的孤立点、线和边缘。对噪声敏感。
edges_laplace = ndimage.laplace(gray_img_weighted)
edges_laplace = (np.abs(edges_laplace) / np.abs(edges_laplace).max() * 255).astype(np.uint8)
Image.fromarray(edges_laplace).save('output_edges_laplace.jpg')

4.3 形态学操作 (Morphological Operations)

形态学操作主要用于处理二值图像(黑白图像),基于形状(结构元素)来分析和处理图像。

4.3.1 腐蚀 (Erosion)

腐蚀操作会“收缩”图像中的前景对象(白色区域),可以用来消除小的噪声点或分离相邻对象。

# 创建一个二值图像用于演示
binary_image = np.array([[0,0,0,0,0],
                         [0,1,1,1,0],
                         [0,1,1,1,0],
                         [0,1,1,1,0],
                         [0,0,0,0,0]], dtype=np.uint8) * 255 # 255表示白色

eroded_image = ndimage.binary_erosion(binary_image, structure=np.ones((3,3))).astype(np.uint8) * 255
Image.fromarray(eroded_image).save('output_eroded.jpg')
4.3.2 膨胀 (Dilation)

膨胀操作会“扩张”图像中的前景对象,可以用来填充图像中的孔洞或连接不连续的区域。

dilated_image = ndimage.binary_dilation(binary_image, structure=np.ones((3,3))).astype(np.uint8) * 255
Image.fromarray(dilated_image).save('output_dilated.jpg')
4.3.3 开运算 (Opening) 与 闭运算 (Closing)
  • 开运算:先腐蚀后膨胀。主要用于消除小对象、平滑对象轮廓和断开细小的连接。
  • 闭运算:先膨胀后腐蚀。主要用于填充对象内部的小孔洞、连接临近的对象和平滑对象轮廓。
opened_image = ndimage.binary_opening(binary_image, structure=np.ones((3,3))).astype(np.uint8) * 255
closed_image = ndimage.binary_closing(binary_image, structure=np.ones((3,3))).astype(np.uint8) * 255
Image.fromarray(opened_image).save('output_opened.jpg')
Image.fromarray(closed_image).save('output_closed.jpg')

4.4 几何变换 (Geometric Transformations)

改变图像的几何形状和位置,如旋转、缩放、平移等。

4.4.1 旋转 (rotate)

可以将图像旋转任意角度。

rotated_img_scipy = ndimage.rotate(img_np, angle=45, reshape=True, mode='constant', cval=0) # reshape=True 会调整图像大小以容纳旋转后的图像
Image.fromarray(rotated_img_scipy).save('output_rotated_scipy.jpg')
4.4.2 移位 (shift)

将图像沿 X 和 Y 轴移动指定像素数。

shifted_img = ndimage.shift(gray_img_weighted, shift=(10, 20), mode='constant', cval=0) # 向下移 10 像素,向右移 20 像素
Image.fromarray(shifted_img).save('output_shifted.jpg')
4.4.3 缩放 (zoom)

按给定因子缩放图像。

zoomed_img = ndimage.zoom(img_np, zoom=(0.5, 0.5, 1) if img_np.ndim == 3 else 0.5) # 缩小一半,彩色图像需保持通道数不变
zoomed_img = np.clip(zoomed_img, 0, 255).astype(np.uint8) # zoom可能会产生浮点数,需要clip和转换回uint8
Image.fromarray(zoomed_img).save('output_zoomed.jpg')

4.5 图像测量与特征提取

ndimage 也提供了一些函数用于测量图像中对象的属性。

4.5.1 连通分量标记 (label)

对图像中的连通区域进行标记,为每个独立的区域分配一个唯一的标签。

# 创建一个包含多个对象的二值图像
labeled_test_image = np.array([[0,0,0,0,0,0,0],
                               [0,1,1,0,1,0,0],
                               [0,1,1,0,0,0,0],
                               [0,0,0,0,1,1,0],
                               [0,0,0,0,1,1,0],
                               [0,0,0,0,0,0,0]], dtype=np.uint8)

labels, num_features = ndimage.label(labeled_test_image)
print(f"检测到 {num_features} 个连通分量。")
# labels 数组会显示每个像素所属的连通分量标签
print("连通分量标签:\n", labels)
# 可以用不同颜色显示标记后的图像
# 例如,通过乘以一个常数来区分不同的标签,然后保存
display_labels = (labels * (255 // (num_features + 1))).astype(np.uint8)
Image.fromarray(display_labels).save('output_labeled_components.jpg')
4.5.2 区域属性测量

一旦图像被标记,就可以使用 ndimage.sum, ndimage.measurements.center_of_mass 等函数来测量每个区域的属性。

# 计算每个连通分量的像素总和
sums = ndimage.sum(labeled_test_image, labels, index=np.arange(1, num_features + 1))
print(f"每个连通分量的像素总和: {sums}")

# 计算每个连通分量的质心
centers = ndimage.center_of_mass(labeled_test_image, labels, index=np.arange(1, num_features + 1))
print(f"每个连通分量的质心: {centers}")

5. 性能考量与优化

  • NumPy 的向量化操作NumPy 的核心优势在于其对数组的向量化操作。尽量避免使用显式的 Python 循环来遍历像素,而是利用 NumPy 内置的函数和操作符,它们在底层是用 C 语言实现的,效率极高。
  • SciPy 底层的 C/Fortran 优化scipy.ndimage 中的函数大多都是对底层 C/Fortran 库的封装。这意味着即使是复杂的滤波和变换,也能以接近原生代码的速度执行,对于处理大型图像和实时应用至关重要。

6. 局限性与高级库介绍

尽管 NumPySciPy 提供了强大的图像处理基础,但它们并非万能。

6.1 NumPy 和 SciPy 的局限

  • 图像 I/O 支持有限SciPy 原生提供的图像加载/保存功能已被移除。
  • 缺少高级计算机视觉算法:不包含特征匹配、目标识别、图像拼接等复杂算法。
  • 可视化功能有限:虽然 matplotlib 可以用于显示图像,但并非 SciPy 的核心功能。

6.2 Pillow (PIL Fork):基础图像操作与格式支持

  • 功能:图像的加载、保存、格式转换、简单变换(裁剪、缩放、旋转)、滤镜、像素操作等。
  • 定位:图像处理的瑞士军刀,是处理图像文件和进行基本操作的首选。

6.3 OpenCV (Open Computer Vision):高级计算机视觉与实时处理

  • 功能:包含数千种算法,涵盖了计算机视觉的几乎所有领域,如特征检测、目标跟踪、图像分割、三维重建、机器学习、深度学习集成等。
  • 定位:工业级计算机视觉应用和实时图像处理的首选。底层由 C++ 编写,性能极高。

6.4 scikit-image:科学图像处理的综合库

  • 功能:提供了一系列图像处理算法,包括分割、几何变换、特征提取、滤镜、数学形态学等,许多功能与 scipy.ndimage 有重叠,但提供了更高级的 API 和更多的算法。
  • 定位:专注于科学研究和开发,与 scikit-learn 等生态系统紧密结合。

在实际项目中,这些库通常会协同使用:Pillow 处理图像 I/O,NumPy 存储图像数据,SciPy.ndimagescikit-image 进行科学图像处理,OpenCV 处理更复杂的计算机视觉任务。

7. 知识扩展:图像处理在实际应用中的场景

图像处理技术在现代社会中无处不在:

  • 医疗影像分析:CT、MRI 图像的增强、分割、三维重建,辅助诊断疾病。
  • 卫星遥感与地理信息系统 (GIS):对卫星图像进行预处理、特征提取(如植被覆盖、土地利用分类)、变化检测。
  • 工业质检与自动化:产品缺陷检测、尺寸测量、流水线上的物体识别和分拣。
  • 计算机视觉与机器学习:图像识别、人脸识别、自动驾驶(环境感知)、增强现实 (AR)、视频监控。
  • 消费电子:手机相机中的图像优化、滤镜、图像稳定。

8. 代码示例

为了确保上述代码示例能顺利运行,您需要安装 Pillowscipy 库:
pip install Pillow scipy matplotlib

然后,请确保在运行代码的目录下有一个名为 example.jpg 的图像文件。如果文件不存在,代码会创建一个虚拟图像进行演示。

9. 总结

为您深度解析了如何在 Python 中利用 NumPySciPy 进行图像处理。

核心要点回顾

  • 图像表示:图像在 Python 中被高效地表示为 NumPy 多维数组,这是所有操作的基础。
  • NumPy 的作用:提供了基础的数组操作、像素访问、颜色通道处理和简单的几何变换。
  • SciPy.ndimage 的作用:专注于 N 维图像处理,提供了丰富且高性能的滤波(高斯、中值、边缘检测)、形态学操作(腐蚀、膨胀、开闭运算)和几何变换(旋转、移位、缩放)功能。
  • 图像 I/O:推荐使用 Pillow 库进行图像的加载和保存。
  • 性能NumPy 的向量化和 SciPy 底层的 C/Fortran 优化确保了高效的计算。
  • 生态系统NumPySciPy 是基础,结合 Pillow, OpenCV, scikit-image 可以构建完整的图像处理和计算机视觉解决方案。

通过掌握 NumPySciPy,您将能够有效地处理和分析图像数据,为更高级的计算机视觉和机器学习任务打下坚实的基础。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐