图像处理
SciPy 的 scipy.ndimage 模块提供了强大的多维图像处理功能,包括滤波、形态学操作、几何变换、特征检测等。虽然对于复杂的计算机视觉任务,专门的库如 OpenCV 可能更合适,但 scipy.ndimage 为基础的图像处理任务提供了简洁而高效的解决方案。本章将详细介绍如何使用 SciPy 进行各种图像处理操作。
scipy.ndimage 模块概述
scipy.ndimage 模块包含以下主要功能:
- 图像滤波和卷积
- 形态学操作
- 几何变换和插值
- 特征检测和测量
- 分割和标记
- 距离变换
python
import numpy as np
from scipy import ndimage
import matplotlib.pyplot as plt
from scipy.ndimage import (
gaussian_filter, median_filter, uniform_filter,
sobel, laplace, binary_erosion, binary_dilation,
rotate, zoom, shift, affine_transform,
label, center_of_mass, distance_transform_edt
)
import warnings
warnings.filterwarnings('ignore')
# 设置绘图样式
plt.style.use('seaborn-v0_8')
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 查看 ndimage 模块的主要功能
print("scipy.ndimage 主要功能:")
functions = [attr for attr in dir(ndimage) if not attr.startswith('_')]
print(f"总共 {len(functions)} 个函数和类")
print("部分函数:", functions[:20])图像创建和基本操作
1. 创建测试图像
python
# 创建各种测试图像
def create_test_images():
"""创建用于测试的图像"""
# 1. 简单几何图形
size = 200
# 圆形
y, x = np.ogrid[:size, :size]
center_y, center_x = size // 2, size // 2
radius = 60
circle = (x - center_x)**2 + (y - center_y)**2 <= radius**2
# 矩形
rectangle = np.zeros((size, size), dtype=bool)
rectangle[60:140, 80:120] = True
# 棋盘图案
checkerboard = np.zeros((size, size))
square_size = 25
for i in range(0, size, square_size):
for j in range(0, size, square_size):
if (i // square_size + j // square_size) % 2 == 0:
checkerboard[i:i+square_size, j:j+square_size] = 1
# 2. 合成图像
# 添加噪声的圆形
noisy_circle = circle.astype(float)
noise = np.random.normal(0, 0.1, (size, size))
noisy_circle = noisy_circle + noise
noisy_circle = np.clip(noisy_circle, 0, 1)
# 渐变图像
gradient = np.linspace(0, 1, size)
gradient_image = np.tile(gradient, (size, 1))
# 3. 复杂图像
# 多个对象
multi_objects = np.zeros((size, size))
# 添加多个圆形
centers = [(50, 50), (150, 50), (100, 150), (50, 150), (150, 150)]
radii = [20, 25, 30, 15, 22]
for (cy, cx), r in zip(centers, radii):
y, x = np.ogrid[:size, :size]
mask = (x - cx)**2 + (y - cy)**2 <= r**2
multi_objects[mask] = 1
return {
'circle': circle.astype(float),
'rectangle': rectangle.astype(float),
'checkerboard': checkerboard,
'noisy_circle': noisy_circle,
'gradient': gradient_image,
'multi_objects': multi_objects
}
# 创建测试图像
test_images = create_test_images()
# 可视化测试图像
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.flatten()
image_names = list(test_images.keys())
for i, (name, image) in enumerate(test_images.items()):
axes[i].imshow(image, cmap='gray')
axes[i].set_title(f'{name.replace("_", " ").title()}')
axes[i].axis('off')
plt.tight_layout()
plt.show()
print("\n测试图像信息:")
for name, image in test_images.items():
print(f"{name:15s}: 形状={image.shape}, 数据类型={image.dtype}, 范围=[{image.min():.3f}, {image.max():.3f}]")2. 图像基本属性和操作
python
# 选择一个图像进行详细分析
test_image = test_images['noisy_circle']
# 基本属性
print("图像基本属性:")
print(f"形状: {test_image.shape}")
print(f"数据类型: {test_image.dtype}")
print(f"像素值范围: [{test_image.min():.3f}, {test_image.max():.3f}]")
print(f"平均值: {test_image.mean():.3f}")
print(f"标准差: {test_image.std():.3f}")
print(f"总像素数: {test_image.size}")
# 直方图分析
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# 原始图像
axes[0].imshow(test_image, cmap='gray')
axes[0].set_title('原始图像')
axes[0].axis('off')
# 直方图
axes[1].hist(test_image.flatten(), bins=50, alpha=0.7, edgecolor='black')
axes[1].set_title('像素值直方图')
axes[1].set_xlabel('像素值')
axes[1].set_ylabel('频次')
axes[1].grid(True, alpha=0.3)
# 累积分布
sorted_pixels = np.sort(test_image.flatten())
cumulative = np.arange(1, len(sorted_pixels) + 1) / len(sorted_pixels)
axes[2].plot(sorted_pixels, cumulative, 'b-', linewidth=2)
axes[2].set_title('累积分布函数')
axes[2].set_xlabel('像素值')
axes[2].set_ylabel('累积概率')
axes[2].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()图像滤波
1. 线性滤波
python
# 使用含噪声的图像进行滤波演示
noisy_image = test_images['noisy_circle']
# 不同类型的线性滤波器
filters = {
'高斯滤波': lambda img: gaussian_filter(img, sigma=2),
'均值滤波': lambda img: uniform_filter(img, size=5),
'高斯滤波(大核)': lambda img: gaussian_filter(img, sigma=4),
'均值滤波(大核)': lambda img: uniform_filter(img, size=9)
}
# 应用滤波器
filtered_results = {}
for name, filter_func in filters.items():
filtered_results[name] = filter_func(noisy_image)
# 可视化滤波结果
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.flatten()
# 原始图像
axes[0].imshow(noisy_image, cmap='gray')
axes[0].set_title('原始含噪图像')
axes[0].axis('off')
# 滤波结果
for i, (name, filtered_img) in enumerate(filtered_results.items(), 1):
axes[i].imshow(filtered_img, cmap='gray')
axes[i].set_title(name)
axes[i].axis('off')
# 误差分析
original_clean = test_images['circle']
axes[5].clear()
# 计算各滤波器的MSE
mse_values = []
filter_names = list(filtered_results.keys())
for name, filtered_img in filtered_results.items():
mse = np.mean((original_clean - filtered_img)**2)
mse_values.append(mse)
axes[5].bar(range(len(mse_values)), mse_values, color=['blue', 'green', 'red', 'orange'])
axes[5].set_title('滤波器性能比较 (MSE)')
axes[5].set_xlabel('滤波器')
axes[5].set_ylabel('均方误差')
axes[5].set_xticks(range(len(filter_names)))
axes[5].set_xticklabels(filter_names, rotation=45)
axes[5].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("\n滤波器性能分析:")
for name, mse in zip(filter_names, mse_values):
print(f"{name:15s}: MSE = {mse:.6f}")
# 最佳滤波器
best_filter = filter_names[np.argmin(mse_values)]
print(f"\n最佳滤波器: {best_filter} (最小MSE)")2. 非线性滤波
python
# 非线性滤波器
nonlinear_filters = {
'中值滤波': lambda img: median_filter(img, size=5),
'最大值滤波': lambda img: ndimage.maximum_filter(img, size=5),
'最小值滤波': lambda img: ndimage.minimum_filter(img, size=5),
'百分位滤波': lambda img: ndimage.percentile_filter(img, percentile=50, size=5)
}
# 创建包含椒盐噪声的图像
salt_pepper_image = test_images['circle'].copy()
# 添加椒盐噪声
noise_mask = np.random.random(salt_pepper_image.shape) < 0.05
salt_pepper_image[noise_mask] = np.random.choice([0, 1], size=np.sum(noise_mask))
# 应用非线性滤波器
nonlinear_results = {}
for name, filter_func in nonlinear_filters.items():
nonlinear_results[name] = filter_func(salt_pepper_image)
# 可视化非线性滤波结果
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.flatten()
# 含椒盐噪声的图像
axes[0].imshow(salt_pepper_image, cmap='gray')
axes[0].set_title('椒盐噪声图像')
axes[0].axis('off')
# 非线性滤波结果
for i, (name, filtered_img) in enumerate(nonlinear_results.items(), 1):
axes[i].imshow(filtered_img, cmap='gray')
axes[i].set_title(name)
axes[i].axis('off')
# 性能比较
axes[5].clear()
original_clean = test_images['circle']
# 计算PSNR
def calculate_psnr(original, processed):
mse = np.mean((original - processed)**2)
if mse == 0:
return float('inf')
max_pixel = 1.0
psnr = 20 * np.log10(max_pixel / np.sqrt(mse))
return psnr
psnr_values = []
nonlinear_names = list(nonlinear_results.keys())
for name, filtered_img in nonlinear_results.items():
psnr = calculate_psnr(original_clean, filtered_img)
psnr_values.append(psnr)
axes[5].bar(range(len(psnr_values)), psnr_values, color=['purple', 'brown', 'pink', 'gray'])
axes[5].set_title('非线性滤波器性能比较 (PSNR)')
axes[5].set_xlabel('滤波器')
axes[5].set_ylabel('PSNR (dB)')
axes[5].set_xticks(range(len(nonlinear_names)))
axes[5].set_xticklabels(nonlinear_names, rotation=45)
axes[5].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("\n非线性滤波器性能分析:")
for name, psnr in zip(nonlinear_names, psnr_values):
print(f"{name:15s}: PSNR = {psnr:.2f} dB")
# 最佳非线性滤波器
best_nonlinear = nonlinear_names[np.argmax(psnr_values)]
print(f"\n最佳非线性滤波器: {best_nonlinear} (最高PSNR)")边缘检测
1. 梯度算子
python
# 使用棋盘图像进行边缘检测
edge_test_image = test_images['checkerboard']
# 不同的边缘检测算子
edge_operators = {
'Sobel X': lambda img: sobel(img, axis=1),
'Sobel Y': lambda img: sobel(img, axis=0),
'Sobel 幅度': lambda img: np.sqrt(sobel(img, axis=0)**2 + sobel(img, axis=1)**2),
'Laplacian': lambda img: laplace(img),
'Prewitt X': lambda img: ndimage.prewitt(img, axis=1),
'Prewitt Y': lambda img: ndimage.prewitt(img, axis=0)
}
# 应用边缘检测算子
edge_results = {}
for name, operator in edge_operators.items():
edge_results[name] = operator(edge_test_image)
# 可视化边缘检测结果
fig, axes = plt.subplots(3, 3, figsize=(15, 15))
axes = axes.flatten()
# 原始图像
axes[0].imshow(edge_test_image, cmap='gray')
axes[0].set_title('原始图像')
axes[0].axis('off')
# 边缘检测结果
for i, (name, edge_img) in enumerate(edge_results.items(), 1):
axes[i].imshow(edge_img, cmap='gray')
axes[i].set_title(name)
axes[i].axis('off')
# 组合边缘检测
sobel_x = edge_results['Sobel X']
sobel_y = edge_results['Sobel Y']
sobel_magnitude = np.sqrt(sobel_x**2 + sobel_y**2)
sobel_direction = np.arctan2(sobel_y, sobel_x)
axes[7].imshow(sobel_magnitude, cmap='gray')
axes[7].set_title('Sobel 幅度')
axes[7].axis('off')
axes[8].imshow(sobel_direction, cmap='hsv')
axes[8].set_title('Sobel 方向')
axes[8].axis('off')
plt.tight_layout()
plt.show()
# 边缘强度统计
print("\n边缘检测统计:")
for name, edge_img in edge_results.items():
edge_strength = np.std(edge_img)
max_edge = np.max(np.abs(edge_img))
print(f"{name:15s}: 边缘强度标准差={edge_strength:.3f}, 最大边缘值={max_edge:.3f}")2. 高级边缘检测
python
# Canny边缘检测的简化实现
def simple_canny_edge_detection(image, low_threshold=0.1, high_threshold=0.2):
"""简化的Canny边缘检测"""
# 1. 高斯滤波降噪
smoothed = gaussian_filter(image, sigma=1)
# 2. 计算梯度
grad_x = sobel(smoothed, axis=1)
grad_y = sobel(smoothed, axis=0)
gradient_magnitude = np.sqrt(grad_x**2 + grad_y**2)
gradient_direction = np.arctan2(grad_y, grad_x)
# 3. 非最大值抑制(简化版)
suppressed = gradient_magnitude.copy()
# 4. 双阈值检测
strong_edges = gradient_magnitude > high_threshold
weak_edges = (gradient_magnitude > low_threshold) & (gradient_magnitude <= high_threshold)
# 5. 边缘连接(简化版)
edges = strong_edges.astype(float)
edges[weak_edges] = 0.5
return edges, gradient_magnitude, gradient_direction
# 应用Canny边缘检测
test_image_for_canny = test_images['multi_objects']
canny_edges, canny_magnitude, canny_direction = simple_canny_edge_detection(test_image_for_canny)
# 比较不同阈值的效果
thresholds = [(0.05, 0.1), (0.1, 0.2), (0.15, 0.3), (0.2, 0.4)]
canny_results = []
for low, high in thresholds:
edges, _, _ = simple_canny_edge_detection(test_image_for_canny, low, high)
canny_results.append((f'阈值({low}, {high})', edges))
# 可视化Canny边缘检测结果
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.flatten()
# 原始图像
axes[0].imshow(test_image_for_canny, cmap='gray')
axes[0].set_title('原始图像')
axes[0].axis('off')
# 梯度幅度
axes[1].imshow(canny_magnitude, cmap='gray')
axes[1].set_title('梯度幅度')
axes[1].axis('off')
# 不同阈值的结果
for i, (name, edges) in enumerate(canny_results[:4], 2):
axes[i].imshow(edges, cmap='gray')
axes[i].set_title(name)
axes[i].axis('off')
plt.tight_layout()
plt.show()
print("\nCanny边缘检测分析:")
for name, edges in canny_results:
edge_pixels = np.sum(edges > 0.5)
total_pixels = edges.size
edge_ratio = edge_pixels / total_pixels
print(f"{name:15s}: 边缘像素={edge_pixels}, 边缘比例={edge_ratio:.3f}")形态学操作
1. 基本形态学操作
python
# 创建二值图像进行形态学操作
binary_image = test_images['multi_objects'] > 0.5
# 定义结构元素
structuring_elements = {
'3x3方形': np.ones((3, 3)),
'5x5方形': np.ones((5, 5)),
'圆形(半径3)': ndimage.generate_binary_structure(2, 1),
'十字形': np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]])
}
# 基本形态学操作
morphological_ops = {
'腐蚀': binary_erosion,
'膨胀': binary_dilation,
'开运算': lambda img, struct: binary_erosion(binary_dilation(img, struct), struct),
'闭运算': lambda img, struct: binary_dilation(binary_erosion(img, struct), struct)
}
# 使用5x5方形结构元素进行操作
struct_elem = structuring_elements['5x5方形']
morphological_results = {}
for op_name, operation in morphological_ops.items():
if op_name in ['腐蚀', '膨胀']:
result = operation(binary_image, structure=struct_elem)
else:
result = operation(binary_image, struct_elem)
morphological_results[op_name] = result
# 可视化形态学操作结果
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.flatten()
# 原始二值图像
axes[0].imshow(binary_image, cmap='gray')
axes[0].set_title('原始二值图像')
axes[0].axis('off')
# 形态学操作结果
for i, (name, result) in enumerate(morphological_results.items(), 1):
axes[i].imshow(result, cmap='gray')
axes[i].set_title(name)
axes[i].axis('off')
# 结构元素可视化
axes[5].imshow(struct_elem, cmap='gray')
axes[5].set_title('5x5方形结构元素')
axes[5].axis('off')
plt.tight_layout()
plt.show()
# 形态学操作统计
print("\n形态学操作统计:")
original_area = np.sum(binary_image)
print(f"原始图像面积: {original_area} 像素")
for name, result in morphological_results.items():
result_area = np.sum(result)
area_change = (result_area - original_area) / original_area * 100
print(f"{name:8s}: 面积={result_area:5d} 像素, 变化={area_change:+6.1f}%")2. 高级形态学操作
python
# 高级形态学操作
def morphological_gradient(image, structure):
"""形态学梯度"""
dilated = binary_dilation(image, structure=structure)
eroded = binary_erosion(image, structure=structure)
return dilated.astype(int) - eroded.astype(int)
def top_hat_transform(image, structure):
"""顶帽变换"""
opened = binary_erosion(binary_dilation(image, structure), structure)
return image.astype(int) - opened.astype(int)
def black_hat_transform(image, structure):
"""黑帽变换"""
closed = binary_dilation(binary_erosion(image, structure), structure)
return closed.astype(int) - image.astype(int)
# 应用高级形态学操作
struct_elem = np.ones((7, 7)) # 使用较大的结构元素
advanced_morphological_results = {
'形态学梯度': morphological_gradient(binary_image, struct_elem),
'顶帽变换': top_hat_transform(binary_image, struct_elem),
'黑帽变换': black_hat_transform(binary_image, struct_elem)
}
# 骨架提取
skeleton = ndimage.binary_erosion(binary_image)
for i in range(10): # 迭代骨架化
eroded = ndimage.binary_erosion(skeleton)
if np.sum(eroded) == 0:
break
skeleton = eroded
advanced_morphological_results['骨架'] = skeleton
# 距离变换
distance_transform = distance_transform_edt(binary_image)
advanced_morphological_results['距离变换'] = distance_transform
# 可视化高级形态学操作
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.flatten()
# 原始图像
axes[0].imshow(binary_image, cmap='gray')
axes[0].set_title('原始二值图像')
axes[0].axis('off')
# 高级形态学操作结果
for i, (name, result) in enumerate(advanced_morphological_results.items(), 1):
if name == '距离变换':
im = axes[i].imshow(result, cmap='viridis')
plt.colorbar(im, ax=axes[i], fraction=0.046, pad=0.04)
else:
axes[i].imshow(result, cmap='gray')
axes[i].set_title(name)
axes[i].axis('off')
plt.tight_layout()
plt.show()
print("\n高级形态学操作分析:")
for name, result in advanced_morphological_results.items():
if name == '距离变换':
max_distance = np.max(result)
avg_distance = np.mean(result[result > 0])
print(f"{name:12s}: 最大距离={max_distance:.2f}, 平均距离={avg_distance:.2f}")
else:
feature_pixels = np.sum(result > 0)
print(f"{name:12s}: 特征像素={feature_pixels}")几何变换
1. 基本几何变换
python
# 使用圆形图像进行几何变换演示
original_image = test_images['circle']
# 基本几何变换
geometric_transforms = {
'旋转45度': lambda img: rotate(img, 45, reshape=False),
'旋转90度': lambda img: rotate(img, 90, reshape=False),
'缩放1.5倍': lambda img: zoom(img, 1.5),
'缩放0.7倍': lambda img: zoom(img, 0.7),
'水平平移': lambda img: shift(img, [0, 30]),
'垂直平移': lambda img: shift(img, [30, 0])
}
# 应用几何变换
transform_results = {}
for name, transform in geometric_transforms.items():
try:
result = transform(original_image)
# 确保结果尺寸一致(用于显示)
if result.shape != original_image.shape:
# 裁剪或填充到原始尺寸
if result.shape[0] > original_image.shape[0] or result.shape[1] > original_image.shape[1]:
# 裁剪
start_y = (result.shape[0] - original_image.shape[0]) // 2
start_x = (result.shape[1] - original_image.shape[1]) // 2
result = result[start_y:start_y+original_image.shape[0],
start_x:start_x+original_image.shape[1]]
else:
# 填充
padded = np.zeros(original_image.shape)
start_y = (original_image.shape[0] - result.shape[0]) // 2
start_x = (original_image.shape[1] - result.shape[1]) // 2
padded[start_y:start_y+result.shape[0],
start_x:start_x+result.shape[1]] = result
result = padded
transform_results[name] = result
except Exception as e:
print(f"变换 {name} 失败: {e}")
transform_results[name] = original_image
# 可视化几何变换结果
fig, axes = plt.subplots(3, 3, figsize=(15, 15))
axes = axes.flatten()
# 原始图像
axes[0].imshow(original_image, cmap='gray')
axes[0].set_title('原始图像')
axes[0].axis('off')
# 变换结果
for i, (name, result) in enumerate(transform_results.items(), 1):
axes[i].imshow(result, cmap='gray')
axes[i].set_title(name)
axes[i].axis('off')
# 组合变换示例
combined_transform = rotate(zoom(original_image, 0.8), 30, reshape=False)
axes[7].imshow(combined_transform, cmap='gray')
axes[7].set_title('组合变换(缩放+旋转)')
axes[7].axis('off')
# 插值方法比较
rotated_nearest = rotate(original_image, 45, order=0, reshape=False) # 最近邻
rotated_bilinear = rotate(original_image, 45, order=1, reshape=False) # 双线性
axes[8].imshow(np.abs(rotated_bilinear - rotated_nearest), cmap='hot')
axes[8].set_title('插值方法差异')
axes[8].axis('off')
plt.tight_layout()
plt.show()
print("\n几何变换分析:")
for name, result in transform_results.items():
# 计算与原图的相似性
correlation = np.corrcoef(original_image.flatten(), result.flatten())[0, 1]
mse = np.mean((original_image - result)**2)
print(f"{name:12s}: 相关系数={correlation:.3f}, MSE={mse:.6f}")2. 仿射变换
python
# 仿射变换矩阵
def create_affine_matrix(rotation=0, scale_x=1, scale_y=1, shear_x=0, shear_y=0,
translate_x=0, translate_y=0):
"""创建仿射变换矩阵"""
# 旋转矩阵
cos_r = np.cos(np.radians(rotation))
sin_r = np.sin(np.radians(rotation))
# 组合变换矩阵
matrix = np.array([
[scale_x * cos_r - shear_y * sin_r, -scale_x * sin_r - shear_y * cos_r, translate_x],
[shear_x * cos_r + scale_y * sin_r, -shear_x * sin_r + scale_y * cos_r, translate_y],
[0, 0, 1]
])
return matrix[:2, :] # 返回2x3矩阵
# 定义各种仿射变换
affine_transforms = {
'剪切变换': create_affine_matrix(shear_x=0.3),
'非均匀缩放': create_affine_matrix(scale_x=1.5, scale_y=0.7),
'复合变换1': create_affine_matrix(rotation=30, scale_x=0.8, scale_y=0.8, translate_x=20),
'复合变换2': create_affine_matrix(rotation=-15, shear_x=0.2, scale_x=1.2)
}
# 应用仿射变换
affine_results = {}
for name, matrix in affine_transforms.items():
# 计算输出形状的中心
center = np.array(original_image.shape) / 2
# 应用仿射变换
result = affine_transform(original_image, matrix,
offset=center - np.dot(matrix[:, :2], center),
output_shape=original_image.shape,
order=1) # 双线性插值
affine_results[name] = result
# 可视化仿射变换结果
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.flatten()
# 原始图像
axes[0].imshow(original_image, cmap='gray')
axes[0].set_title('原始图像')
axes[0].axis('off')
# 仿射变换结果
for i, (name, result) in enumerate(affine_results.items(), 1):
axes[i].imshow(result, cmap='gray')
axes[i].set_title(name)
axes[i].axis('off')
# 变换矩阵可视化
axes[5].clear()
matrix_names = list(affine_transforms.keys())
matrix_data = []
for name in matrix_names:
matrix = affine_transforms[name]
# 提取变换参数进行可视化
det = np.linalg.det(matrix[:, :2]) # 行列式(面积缩放因子)
matrix_data.append(det)
axes[5].bar(range(len(matrix_data)), matrix_data, color=['red', 'green', 'blue', 'orange'])
axes[5].set_title('变换矩阵行列式(面积缩放)')
axes[5].set_xlabel('变换类型')
axes[5].set_ylabel('行列式值')
axes[5].set_xticks(range(len(matrix_names)))
axes[5].set_xticklabels(matrix_names, rotation=45)
axes[5].grid(True, alpha=0.3)
axes[5].axhline(y=1, color='black', linestyle='--', alpha=0.7, label='无缩放')
axes[5].legend()
plt.tight_layout()
plt.show()
print("\n仿射变换分析:")
for name, result in affine_results.items():
matrix = affine_transforms[name]
det = np.linalg.det(matrix[:, :2])
area_change = (det - 1) * 100
# 计算图像质量
correlation = np.corrcoef(original_image.flatten(), result.flatten())[0, 1]
print(f"{name:12s}: 面积变化={area_change:+6.1f}%, 相关系数={correlation:.3f}")图像分割和标记
1. 连通组件分析
python
# 使用多对象图像进行分割
segmentation_image = test_images['multi_objects'] > 0.5
# 连通组件标记
labeled_image, num_features = label(segmentation_image)
print(f"检测到 {num_features} 个连通组件")
# 计算每个组件的属性
component_properties = []
for i in range(1, num_features + 1):
component_mask = labeled_image == i
# 基本属性
area = np.sum(component_mask)
centroid = center_of_mass(component_mask)
# 边界框
coords = np.where(component_mask)
min_row, max_row = np.min(coords[0]), np.max(coords[0])
min_col, max_col = np.min(coords[1]), np.max(coords[1])
bbox_area = (max_row - min_row + 1) * (max_col - min_col + 1)
# 形状特征
solidity = area / bbox_area if bbox_area > 0 else 0
# 周长(简化计算)
perimeter = np.sum(binary_erosion(component_mask) != component_mask)
# 圆形度
if perimeter > 0:
circularity = 4 * np.pi * area / (perimeter ** 2)
else:
circularity = 0
component_properties.append({
'id': i,
'area': area,
'centroid': centroid,
'bbox': (min_row, min_col, max_row, max_col),
'solidity': solidity,
'perimeter': perimeter,
'circularity': circularity
})
# 可视化分割结果
fig, axes = plt.subplots(2, 2, figsize=(12, 12))
# 原始图像
axes[0, 0].imshow(segmentation_image, cmap='gray')
axes[0, 0].set_title('原始二值图像')
axes[0, 0].axis('off')
# 标记图像
axes[0, 1].imshow(labeled_image, cmap='nipy_spectral')
axes[0, 1].set_title(f'连通组件标记 ({num_features}个组件)')
axes[0, 1].axis('off')
# 添加质心和边界框
axes[1, 0].imshow(segmentation_image, cmap='gray')
for prop in component_properties:
centroid = prop['centroid']
bbox = prop['bbox']
# 绘制质心
axes[1, 0].plot(centroid[1], centroid[0], 'ro', markersize=8)
# 绘制边界框
rect_height = bbox[2] - bbox[0]
rect_width = bbox[3] - bbox[1]
rect = plt.Rectangle((bbox[1], bbox[0]), rect_width, rect_height,
fill=False, edgecolor='red', linewidth=2)
axes[1, 0].add_patch(rect)
# 添加标签
axes[1, 0].text(centroid[1], centroid[0], str(prop['id']),
color='yellow', fontsize=12, ha='center', va='center')
axes[1, 0].set_title('质心和边界框')
axes[1, 0].axis('off')
# 组件属性统计
axes[1, 1].clear()
areas = [prop['area'] for prop in component_properties]
circularities = [prop['circularity'] for prop in component_properties]
axes[1, 1].scatter(areas, circularities, c=range(len(areas)), cmap='viridis', s=100)
axes[1, 1].set_xlabel('面积 (像素)')
axes[1, 1].set_ylabel('圆形度')
axes[1, 1].set_title('组件特征分布')
axes[1, 1].grid(True, alpha=0.3)
# 添加组件ID标注
for i, prop in enumerate(component_properties):
axes[1, 1].annotate(str(prop['id']), (prop['area'], prop['circularity']),
xytext=(5, 5), textcoords='offset points')
plt.tight_layout()
plt.show()
# 打印组件属性
print("\n连通组件属性:")
print(f"{'ID':>3} {'面积':>6} {'质心':>15} {'圆形度':>8} {'实心度':>8}")
print("-" * 50)
for prop in component_properties:
centroid_str = f"({prop['centroid'][0]:.1f}, {prop['centroid'][1]:.1f})"
print(f"{prop['id']:>3} {prop['area']:>6} {centroid_str:>15} {prop['circularity']:>8.3f} {prop['solidity']:>8.3f}")2. 区域生长分割
python
# 实现简单的区域生长算法
def region_growing(image, seed_points, threshold=0.1):
"""区域生长分割算法"""
segmented = np.zeros_like(image, dtype=int)
for region_id, seed in enumerate(seed_points, 1):
# 初始化
visited = np.zeros_like(image, dtype=bool)
region_pixels = [seed]
visited[seed] = True
segmented[seed] = region_id
# 区域生长
while region_pixels:
current_pixel = region_pixels.pop(0)
current_value = image[current_pixel]
# 检查8邻域
for dy in [-1, 0, 1]:
for dx in [-1, 0, 1]:
if dy == 0 and dx == 0:
continue
ny, nx = current_pixel[0] + dy, current_pixel[1] + dx
# 边界检查
if (0 <= ny < image.shape[0] and 0 <= nx < image.shape[1] and
not visited[ny, nx]):
neighbor_value = image[ny, nx]
# 相似性检查
if abs(neighbor_value - current_value) < threshold:
visited[ny, nx] = True
segmented[ny, nx] = region_id
region_pixels.append((ny, nx))
return segmented
# 使用梯度图像进行区域生长
gradient_image = test_images['gradient']
# 选择种子点
seed_points = [
(50, 50), # 左上角
(50, 150), # 右上角
(150, 50), # 左下角
(150, 150) # 右下角
]
# 应用区域生长
region_grown = region_growing(gradient_image, seed_points, threshold=0.15)
# 可视化区域生长结果
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# 原始图像
axes[0].imshow(gradient_image, cmap='gray')
for i, seed in enumerate(seed_points):
axes[0].plot(seed[1], seed[0], 'ro', markersize=10)
axes[0].text(seed[1], seed[0], str(i+1), color='yellow',
fontsize=12, ha='center', va='center')
axes[0].set_title('原始图像和种子点')
axes[0].axis('off')
# 分割结果
axes[1].imshow(region_grown, cmap='nipy_spectral')
axes[1].set_title('区域生长分割结果')
axes[1].axis('off')
# 分割边界
boundaries = np.zeros_like(region_grown)
for i in range(1, region_grown.shape[0]-1):
for j in range(1, region_grown.shape[1]-1):
if (region_grown[i, j] != region_grown[i-1, j] or
region_grown[i, j] != region_grown[i+1, j] or
region_grown[i, j] != region_grown[i, j-1] or
region_grown[i, j] != region_grown[i, j+1]):
boundaries[i, j] = 1
axes[2].imshow(gradient_image, cmap='gray')
axes[2].imshow(boundaries, cmap='Reds', alpha=0.7)
axes[2].set_title('分割边界叠加')
axes[2].axis('off')
plt.tight_layout()
plt.show()
# 分析分割结果
print("\n区域生长分割分析:")
for region_id in range(1, len(seed_points) + 1):
region_mask = region_grown == region_id
region_area = np.sum(region_mask)
region_mean = np.mean(gradient_image[region_mask])
region_std = np.std(gradient_image[region_mask])
print(f"区域 {region_id}: 面积={region_area:5d} 像素, 均值={region_mean:.3f}, 标准差={region_std:.3f}")实际应用案例
1. 细胞计数应用
python
# 模拟细胞图像
def create_cell_image(size=300, num_cells=15):
"""创建模拟的细胞图像"""
image = np.zeros((size, size))
np.random.seed(42)
for _ in range(num_cells):
# 随机位置和大小
center_y = np.random.randint(20, size-20)
center_x = np.random.randint(20, size-20)
radius = np.random.randint(8, 18)
# 创建细胞
y, x = np.ogrid[:size, :size]
mask = (x - center_x)**2 + (y - center_y)**2 <= radius**2
# 添加细胞强度
intensity = np.random.uniform(0.6, 1.0)
image[mask] = intensity
# 添加背景噪声
noise = np.random.normal(0, 0.05, (size, size))
image = image + noise
image = np.clip(image, 0, 1)
return image
# 创建细胞图像
cell_image = create_cell_image()
# 细胞检测和计数流程
def cell_counting_pipeline(image):
"""细胞计数流程"""
# 1. 预处理
# 高斯滤波降噪
smoothed = gaussian_filter(image, sigma=1)
# 2. 阈值分割
# 使用Otsu阈值
threshold = 0.3 # 简化的阈值
binary = smoothed > threshold
# 3. 形态学操作
# 去除小噪声
cleaned = binary_erosion(binary, structure=np.ones((3, 3)))
cleaned = binary_dilation(cleaned, structure=np.ones((3, 3)))
# 分离粘连细胞
distance = distance_transform_edt(cleaned)
local_maxima = distance > 0.7 * np.max(distance)
# 4. 连通组件分析
labeled, num_cells = label(cleaned)
return {
'original': image,
'smoothed': smoothed,
'binary': binary,
'cleaned': cleaned,
'distance': distance,
'labeled': labeled,
'num_cells': num_cells
}
# 执行细胞计数
cell_results = cell_counting_pipeline(cell_image)
# 可视化细胞计数结果
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.flatten()
# 处理步骤可视化
steps = [
('original', '原始图像', 'gray'),
('smoothed', '高斯滤波', 'gray'),
('binary', '阈值分割', 'gray'),
('cleaned', '形态学清理', 'gray'),
('distance', '距离变换', 'viridis'),
('labeled', '细胞标记', 'nipy_spectral')
]
for i, (key, title, cmap) in enumerate(steps):
im = axes[i].imshow(cell_results[key], cmap=cmap)
axes[i].set_title(f'{title}')
axes[i].axis('off')
if key == 'distance':
plt.colorbar(im, ax=axes[i], fraction=0.046, pad=0.04)
elif key == 'labeled':
axes[i].set_title(f'{title} ({cell_results["num_cells"]} 个细胞)')
plt.tight_layout()
plt.show()
# 细胞特征分析
cell_features = []
for cell_id in range(1, cell_results['num_cells'] + 1):
cell_mask = cell_results['labeled'] == cell_id
# 基本特征
area = np.sum(cell_mask)
centroid = center_of_mass(cell_mask)
# 强度特征
mean_intensity = np.mean(cell_image[cell_mask])
max_intensity = np.max(cell_image[cell_mask])
# 形状特征
coords = np.where(cell_mask)
min_row, max_row = np.min(coords[0]), np.max(coords[0])
min_col, max_col = np.min(coords[1]), np.max(coords[1])
width = max_col - min_col + 1
height = max_row - min_row + 1
aspect_ratio = width / height if height > 0 else 0
cell_features.append({
'id': cell_id,
'area': area,
'centroid': centroid,
'mean_intensity': mean_intensity,
'max_intensity': max_intensity,
'width': width,
'height': height,
'aspect_ratio': aspect_ratio
})
print(f"\n细胞计数结果: 检测到 {cell_results['num_cells']} 个细胞")
print("\n细胞特征统计:")
print(f"{'ID':>3} {'面积':>6} {'平均强度':>10} {'宽度':>6} {'高度':>6} {'长宽比':>8}")
print("-" * 55)
for feature in cell_features:
print(f"{feature['id']:>3} {feature['area']:>6} {feature['mean_intensity']:>10.3f} "
f"{feature['width']:>6} {feature['height']:>6} {feature['aspect_ratio']:>8.2f}")
# 统计分析
areas = [f['area'] for f in cell_features]
intensities = [f['mean_intensity'] for f in cell_features]
print(f"\n统计摘要:")
print(f"平均细胞面积: {np.mean(areas):.1f} ± {np.std(areas):.1f} 像素")
print(f"平均细胞强度: {np.mean(intensities):.3f} ± {np.std(intensities):.3f}")
print(f"面积范围: {np.min(areas)} - {np.max(areas)} 像素")2. 纹理分析应用
python
# 纹理特征提取
def extract_texture_features(image, window_size=16):
"""提取纹理特征"""
features = {}
# 1. 统计特征
features['mean'] = np.mean(image)
features['std'] = np.std(image)
features['skewness'] = np.mean(((image - features['mean']) / features['std'])**3)
features['kurtosis'] = np.mean(((image - features['mean']) / features['std'])**4)
# 2. 梯度特征
grad_x = sobel(image, axis=1)
grad_y = sobel(image, axis=0)
gradient_magnitude = np.sqrt(grad_x**2 + grad_y**2)
features['gradient_mean'] = np.mean(gradient_magnitude)
features['gradient_std'] = np.std(gradient_magnitude)
# 3. 局部二值模式 (简化版)
def local_binary_pattern_simple(img):
lbp = np.zeros_like(img)
for i in range(1, img.shape[0]-1):
for j in range(1, img.shape[1]-1):
center = img[i, j]
pattern = 0
pattern += (img[i-1, j-1] >= center) << 7
pattern += (img[i-1, j] >= center) << 6
pattern += (img[i-1, j+1] >= center) << 5
pattern += (img[i, j+1] >= center) << 4
pattern += (img[i+1, j+1] >= center) << 3
pattern += (img[i+1, j] >= center) << 2
pattern += (img[i+1, j-1] >= center) << 1
pattern += (img[i, j-1] >= center) << 0
lbp[i, j] = pattern
return lbp
lbp = local_binary_pattern_simple(image)
lbp_hist, _ = np.histogram(lbp.flatten(), bins=256, range=(0, 256))
lbp_hist = lbp_hist / np.sum(lbp_hist) # 归一化
features['lbp_uniformity'] = np.sum(lbp_hist**2)
features['lbp_entropy'] = -np.sum(lbp_hist * np.log2(lbp_hist + 1e-10))
return features
# 创建不同纹理的图像
def create_texture_samples():
"""创建不同纹理样本"""
size = 128
textures = {}
# 1. 随机纹理
np.random.seed(42)
textures['random'] = np.random.random((size, size))
# 2. 正弦波纹理
x, y = np.meshgrid(np.linspace(0, 4*np.pi, size), np.linspace(0, 4*np.pi, size))
textures['sine_wave'] = 0.5 * (np.sin(x) + np.sin(y)) + 0.5
# 3. 棋盘纹理
checkerboard = np.zeros((size, size))
square_size = 8
for i in range(0, size, square_size):
for j in range(0, size, square_size):
if (i // square_size + j // square_size) % 2 == 0:
checkerboard[i:i+square_size, j:j+square_size] = 1
textures['checkerboard'] = checkerboard
# 4. 分形纹理(简化版)
fractal = np.zeros((size, size))
for i in range(size):
for j in range(size):
fractal[i, j] = np.sin(0.1 * i) * np.cos(0.1 * j) + 0.2 * np.sin(0.3 * i) * np.cos(0.3 * j)
textures['fractal'] = (fractal - fractal.min()) / (fractal.max() - fractal.min())
return textures
# 创建纹理样本
texture_samples = create_texture_samples()
# 提取每个纹理的特征
texture_features = {}
for name, texture in texture_samples.items():
texture_features[name] = extract_texture_features(texture)
# 可视化纹理样本和特征
fig, axes = plt.subplots(2, 4, figsize=(16, 8))
# 纹理样本
for i, (name, texture) in enumerate(texture_samples.items()):
axes[0, i].imshow(texture, cmap='gray')
axes[0, i].set_title(f'{name.replace("_", " ").title()}')
axes[0, i].axis('off')
# 特征比较
feature_names = ['mean', 'std', 'gradient_mean', 'lbp_entropy']
colors = ['red', 'green', 'blue', 'orange']
for i, feature_name in enumerate(feature_names):
values = [texture_features[texture_name][feature_name] for texture_name in texture_samples.keys()]
axes[1, i].bar(range(len(values)), values, color=colors[i], alpha=0.7)
axes[1, i].set_title(f'{feature_name.replace("_", " ").title()}')
axes[1, i].set_xticks(range(len(texture_samples)))
axes[1, i].set_xticklabels(list(texture_samples.keys()), rotation=45)
axes[1, i].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("\n纹理特征分析:")
print(f"{'纹理类型':>12} {'均值':>8} {'标准差':>8} {'梯度均值':>10} {'LBP熵':>8}")
print("-" * 55)
for name, features in texture_features.items():
print(f"{name:>12} {features['mean']:>8.3f} {features['std']:>8.3f} "
f"{features['gradient_mean']:>10.3f} {features['lbp_entropy']:>8.3f}")
# 纹理分类示例
def classify_texture_simple(features):
"""简单的纹理分类器"""
if features['std'] < 0.2:
return '均匀纹理'
elif features['gradient_mean'] > 0.3:
return '边缘丰富纹理'
elif features['lbp_entropy'] > 6:
return '复杂纹理'
else:
return '简单纹理'
print("\n纹理分类结果:")
for name, features in texture_features.items():
classification = classify_texture_simple(features)
print(f"{name:>12}: {classification}")性能优化技巧
1. 算法选择和参数优化
python
import time
# 性能测试函数
def performance_test(func, *args, **kwargs):
"""测试函数性能"""
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
return result, end_time - start_time
# 创建大图像进行性能测试
large_image = np.random.random((1000, 1000))
# 比较不同滤波器的性能
filter_tests = {
'高斯滤波(sigma=1)': lambda img: gaussian_filter(img, sigma=1),
'高斯滤波(sigma=2)': lambda img: gaussian_filter(img, sigma=2),
'均值滤波(5x5)': lambda img: uniform_filter(img, size=5),
'均值滤波(9x9)': lambda img: uniform_filter(img, size=9),
'中值滤波(5x5)': lambda img: median_filter(img, size=5)
}
print("滤波器性能比较:")
print(f"{'滤波器':>20} {'时间(秒)':>10} {'相对速度':>10}")
print("-" * 45)
performance_results = []
for name, filter_func in filter_tests.items():
_, exec_time = performance_test(filter_func, large_image)
performance_results.append((name, exec_time))
# 按执行时间排序
performance_results.sort(key=lambda x: x[1])
fastest_time = performance_results[0][1]
for name, exec_time in performance_results:
relative_speed = exec_time / fastest_time
print(f"{name:>20} {exec_time:>10.4f} {relative_speed:>10.2f}x")
# 内存使用优化
def memory_efficient_processing(image, chunk_size=256):
"""内存高效的图像处理"""
result = np.zeros_like(image)
for i in range(0, image.shape[0], chunk_size):
for j in range(0, image.shape[1], chunk_size):
# 处理图像块
end_i = min(i + chunk_size, image.shape[0])
end_j = min(j + chunk_size, image.shape[1])
chunk = image[i:end_i, j:end_j]
processed_chunk = gaussian_filter(chunk, sigma=1)
result[i:end_i, j:end_j] = processed_chunk
return result
# 测试内存效率
print("\n内存使用优化测试:")
_, time_normal = performance_test(gaussian_filter, large_image, sigma=1)
_, time_chunked = performance_test(memory_efficient_processing, large_image)
print(f"常规处理时间: {time_normal:.4f} 秒")
print(f"分块处理时间: {time_chunked:.4f} 秒")
print(f"时间开销: {time_chunked/time_normal:.2f}x")2. 并行处理和缓存优化
python
from functools import lru_cache
# 缓存优化示例
@lru_cache(maxsize=128)
def cached_gaussian_kernel(sigma, size):
"""缓存高斯核"""
kernel = np.zeros((size, size))
center = size // 2
for i in range(size):
for j in range(size):
x, y = i - center, j - center
kernel[i, j] = np.exp(-(x**2 + y**2) / (2 * sigma**2))
return kernel / np.sum(kernel)
# 优化的卷积函数
def optimized_convolution(image, kernel):
"""优化的卷积实现"""
# 使用SciPy的优化卷积
return ndimage.convolve(image, kernel, mode='reflect')
# 批处理优化
def batch_process_images(images, operation):
"""批处理图像"""
results = []
# 预分配内存
if images:
sample_result = operation(images[0])
results = [np.zeros_like(sample_result) for _ in images]
results[0] = sample_result
# 处理剩余图像
for i in range(1, len(images)):
results[i] = operation(images[i])
return results
# 创建测试图像集
test_images_batch = [np.random.random((200, 200)) for _ in range(5)]
# 测试批处理性能
print("\n批处理性能测试:")
# 单独处理
start_time = time.time()
individual_results = []
for img in test_images_batch:
result = gaussian_filter(img, sigma=1)
individual_results.append(result)
individual_time = time.time() - start_time
# 批处理
start_time = time.time()
batch_results = batch_process_images(test_images_batch,
lambda img: gaussian_filter(img, sigma=1))
batch_time = time.time() - start_time
print(f"单独处理时间: {individual_time:.4f} 秒")
print(f"批处理时间: {batch_time:.4f} 秒")
print(f"性能提升: {individual_time/batch_time:.2f}x")
# 参数调优建议
print("\n性能优化建议:")
print("1. 选择合适的滤波器大小和参数")
print("2. 对于大图像,考虑分块处理")
print("3. 使用缓存避免重复计算")
print("4. 批处理多个图像以提高效率")
print("5. 选择合适的插值方法平衡质量和速度")小结
本章详细介绍了SciPy中的图像处理功能,主要内容包括:
核心概念
- scipy.ndimage模块: SciPy的多维图像处理核心
- 图像表示: 数组形式的图像数据结构
- 滤波操作: 线性和非线性滤波技术
- 形态学操作: 二值图像的结构化处理
主要功能
- 图像滤波: 高斯滤波、中值滤波、边缘检测
- 几何变换: 旋转、缩放、仿射变换
- 形态学处理: 腐蚀、膨胀、开闭运算
- 图像分割: 阈值分割、区域生长、连通组件分析
- 特征提取: 纹理分析、形状特征
实际应用
- 生物医学图像: 细胞计数、组织分析
- 工业检测: 缺陷检测、质量控制
- 科学研究: 显微镜图像分析
- 计算机视觉: 预处理和特征提取
性能优化
- 选择合适的算法和参数
- 内存高效的分块处理
- 缓存和批处理优化
- 并行处理技术
SciPy的图像处理功能为科学计算和研究提供了强大的工具,虽然不如专门的计算机视觉库功能丰富,但在基础图像处理任务中表现出色,特别适合科学数据的分析和处理。
练习题
基础滤波: 实现一个自定义的3x3均值滤波器,并与
uniform_filter的结果进行比较。边缘检测: 组合使用Sobel算子的X和Y方向结果,计算边缘的幅度和方向,并可视化结果。
形态学应用: 设计一个形态学操作序列来分离粘连的圆形对象。
几何变换: 实现一个图像配准函数,将一个旋转的图像恢复到原始方向。
分割算法: 改进区域生长算法,添加多个相似性准则(如颜色、纹理)。
特征提取: 实现灰度共生矩阵(GLCM)来提取纹理特征。
性能优化: 比较不同插值方法在图像缩放中的性能和质量差异。
综合应用: 开发一个完整的图像分析流程,包括预处理、分割、特征提取和分类。