Python计算机视觉 第1章-基本的图像操作和处理
本章讲解操作和处理图像的基础知识,将通过大量示例介绍处理图像所需的Python工具包,并介绍用于读取图像、图像转换和缩放、计算导数、画图和保存结果等的基本工具。
1.1 PIL:Python图像处理类库
利用PIL中的函数,我们可以从大多数图像格式的文件中读取数据,然后写入最常见的图像格式文件中。PIL中最重要的模块为Image。要读取一幅图像,可以使用:
from PIL import Image
pil_im = Image.open('empire.jpg')
上述代码的返回值pil_im是一个PIL图像对象。
from PIL import Image
import matplotlib.pyplot as plt
# 打开并转换图像为灰度模式
pil_im = Image.open('img.png').convert('L')
# 显示图像
plt.imshow(pil_im, cmap='gray')
plt.axis('off') # 隐藏坐标轴
plt.show()
上述代码是将图片转为灰度图像并显示出来
接下来对该图生成缩略图,裁剪图片中间部分,并将图片旋转45°
from PIL import Image
import matplotlib.pyplot as plt
from matplotlib import rcParams
# 设置中文字体
rcParams['font.sans-serif'] = ['SimHei'] # 使用黑体
rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# 打开并转换图像为灰度模式
pil_im = Image.open('img.png').convert('L')
# 创建一个图形窗口,准备展示多张图片
fig, axs = plt.subplots(1, 3, figsize=(15, 5))
# 1. 显示缩略图
thumbnail_im = pil_im.copy() # 复制图像以保持原图
thumbnail_im.thumbnail((100, 100)) # 创建缩略图,大小设置为 (100, 100)
axs[0].imshow(thumbnail_im, cmap='gray')
axs[0].set_title('缩略图')
axs[0].axis('off')
# 2. 裁剪图像的中间部分
width, height = pil_im.size
left = (width - 100) // 2
top = (height - 100) // 2
right = (width + 100) // 2
bottom = (height + 100) // 2
cropped_im = pil_im.crop((left, top, right, bottom)) # 裁剪中间 100x100 的区域
axs[1].imshow(cropped_im, cmap='gray')
axs[1].set_title('裁剪中间部分')
axs[1].axis('off')
# 3. 将图像旋转45°
rotated_im = pil_im.rotate(45) # 旋转图像45°
axs[2].imshow(rotated_im, cmap='gray')
axs[2].set_title('旋转 45°')
axs[2].axis('off')
# 显示所有图像
plt.show()
1.2 Matplotlib
接下来对该图像绘制图像轮廓,并生成图像的灰度直方图
from PIL import Image, ImageDraw, ImageFilter
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import rcParams
# 设置中文字体
rcParams['font.sans-serif'] = ['SimHei'] # 使用黑体(SimHei)字体
rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# 打开图像并转换为灰度模式
img = Image.open('img.png').convert('L')
# 创建一个绘图对象
draw = ImageDraw.Draw(img)
# 绘制点
draw.point((50, 50), fill=255) # 在 (50, 50) 位置绘制一个白色点
# 绘制线条
draw.line((20, 20, 80, 80), fill=255, width=2) # 从 (20, 20) 到 (80, 80) 画一条白色线
# 生成图像轮廓
contour_img = img.filter(ImageFilter.CONTOUR)
# 绘制直方图
histogram = np.array(img).flatten() # 将图像转为数组并展平
fig, axs = plt.subplots(1, 3, figsize=(18, 5))
# 原图显示
axs[0].imshow(img, cmap='gray')
axs[0].set_title('原图')
axs[0].axis('off')
# 显示轮廓图像
axs[1].imshow(contour_img, cmap='gray')
axs[1].set_title('图像轮廓')
axs[1].axis('off')
# 显示直方图
axs[2].hist(histogram, bins=256, range=(0, 255), density=True, color='gray')
axs[2].set_title('灰度直方图')
# 交互式标注功能
def onclick(event):
if event.inaxes is not None:
x, y = int(event.xdata), int(event.ydata)
color = img.getpixel((x, y))
axs[0].annotate(f'({x}, {y}) - {color}', (x, y), color='red')
fig.canvas.draw()
fig.canvas.mpl_connect('button_press_event', onclick)
# 显示所有图像和直方图
plt.show()
1.3 NumPy
1.3.1 图像数组表示
以下是实验代码
import numpy as np
from PIL import Image
# 1. 从文件读取图像
img_path = 'img.png'
img = Image.open(img_path)
# 2. 将图像转换为灰度图像
gray_img = img.convert('L')
gray_img_array = np.array(gray_img)
# 3. 图像缩放
scaled_img = gray_img.resize((128, 128))
scaled_img_array = np.array(scaled_img)
# 4. 直方图均衡化
hist, bins = np.histogram(scaled_img_array.flatten(), 256, [0, 256])
cdf = hist.cumsum() # 计算累计分布函数(CDF)
cdf_normalized = cdf * hist.max() / cdf.max() # 归一化
# 使用 CDF 进行直方图均衡化
cdf_m = np.ma.masked_equal(cdf, 0) # 忽略 CDF 中的 0 值
cdf_m = (cdf_m - cdf_m.min()) * 255 / (cdf_m.max() - cdf_m.min()) # 归一化 CDF
cdf = np.ma.filled(cdf_m, 0).astype('uint8') # 填充被屏蔽的值并转换为 uint8
equalized_img_array = cdf[scaled_img_array] # 应用均衡化后的 CDF 到图像
# 5. 确保所有图像具有相同的尺寸
gray_img_resized = gray_img.resize((128, 128))
gray_img_resized_array = np.array(gray_img_resized)
# 6. 保存图像数组到 NumPy 文件
np.savez('image_arrays.npz',
gray_img=gray_img_resized_array,
scaled_img=scaled_img_array,
equalized_img=equalized_img_array)
print("图像数组已保存到 'image_arrays.npz'")
# 7. 加载保存的图像数组
data = np.load('image_arrays.npz')
# 获取所有数组的名称
array_names = data.files
# 显示每个数组的信息
for name in array_names:
array = data[name]
print(f"数组名称: {name}")
print(f"形状: {array.shape}")
print(f"数据类型: {array.dtype}")
print(f"前几项数据: {array.flat[:10]}") # 打印前 10 个数据项(扁平化)
print() # 添加空行以便于阅读
# 关闭文件
data.close()
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
# 1. 读取图像并转换为灰度图像
image_path = r'img.png'
original_img = Image.open(image_path).convert('L')
original_img_array = np.array(original_img)
# 2. 图像缩放
# 将图像缩放到 128x128 像素
resized_img = original_img.resize((128, 128))
resized_img_array = np.array(resized_img)
# 3. 灰度变换
# 反向处理图像
inverted_img_array = 255 - resized_img_array
# 4. 显示图像
plt.rcParams['font.sans-serif'] = ['SimHei'] # 支持中文字体
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# 原始图像
axes[0].imshow(original_img_array, cmap='gray')
axes[0].set_title('原始图像')
axes[0].axis('off')
# 缩放后的图像
axes[1].imshow(resized_img_array, cmap='gray')
axes[1].set_title('缩放图像')
axes[1].axis('off')
# 反向处理图像
axes[2].imshow(inverted_img_array, cmap='gray')
axes[2].set_title('反向处理')
axes[2].axis('off')
plt.tight_layout()
plt.show()
接下来对图像直方图均衡化,以下为实验代码:
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
def histogram_equalization(image, num_bins=256):
# 计算图像的直方图
image_hist, bin_edges = np.histogram(image.flatten(), num_bins, density=True)
# 计算累积分布函数 (CDF)
cdf = image_hist.cumsum()
cdf = 255 * cdf / cdf[-1]
# 应用累积分布函数进行图像均衡化
equalized_image = np.interp(image.flatten(), bin_edges[:-1], cdf)
return equalized_image.reshape(image.shape), cdf
# 读取图像并转换为灰度图像
image_path = r'img.png'
original_image = Image.open(image_path).convert('L')
original_image_array = np.array(original_image)
# 对图像进行直方图均衡化
equalized_image_array, cdf = histogram_equalization(original_image_array)
# 显示结果
plt.rcParams['font.sans-serif'] = ['SimHei'] # 支持中文字体
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 原始图像
axes[0, 0].imshow(original_image_array, cmap='gray')
axes[0, 0].set_title('原始灰度图像')
axes[0, 0].axis('off')
# 均衡化后的图像
axes[0, 1].imshow(equalized_image_array, cmap='gray')
axes[0, 1].set_title('均衡化图像')
axes[0, 1].axis('off')
# 原始图像直方图
axes[1, 0].hist(original_image_array.flatten(), bins=128, range=(0, 256))
axes[1, 0].set_title('原始图像直方图')
axes[1, 0].set_xlabel('像素强度')
axes[1, 0].set_ylabel('频率')
# 均衡化图像直方图
axes[1, 1].hist(equalized_image_array.flatten(), bins=128, range=(0, 256))
axes[1, 1].set_title('均衡化图像直方图')
axes[1, 1].set_xlabel('像素强度')
axes[1, 1].set_ylabel('频率')
plt.tight_layout()
plt.show()
1.4 SciPy
SciPy是建立在NumPy 基础上,用于数值运算的开源工具包。SciPy 提供很多高效的操作,可以实现数值积分、优化、统计、信号处理,以及对我们来说最重要的图像处理功能。
1.4.1 图像模糊
以下为实验代码:
from PIL import Image
import numpy as np
from scipy.ndimage import gaussian_filter
import matplotlib.pyplot as plt
# 读取图像并转换为灰度图像
image_path = r'img.png'
original_image = Image.open(image_path).convert('L')
original_image_array = np.array(original_image)
# 应用高斯滤波
sigma = 5
smoothed_image_array = gaussian_filter(original_image_array, sigma=sigma)
# 可视化结果
plt.rcParams['font.sans-serif'] = ['SimHei'] # 支持中文字体
fig, axes = plt.subplots(1, 2, figsize=(12, 6))
# 显示原始图像
axes[0].imshow(original_image_array, cmap='gray')
axes[0].set_title('原始灰度图像')
axes[0].axis('off')
# 显示平滑后的图像
axes[1].imshow(smoothed_image_array, cmap='gray')
axes[1].set_title('高斯滤波结果')
axes[1].axis('off')
plt.tight_layout()
plt.show()
1.4.2 图像导数
为了生成图像的导数,可以使用梯度算子(如 Sobel 算子)来计算图像的梯度。
以下为实验代码:
from PIL import Image
import numpy as np
from scipy.ndimage import sobel
import matplotlib.pyplot as plt
# 读取图像并转换为灰度图像
image_path = r'img.png'
original_image = Image.open(image_path).convert('L')
image_array = np.array(original_image)
# 计算图像的梯度
gradient_x = sobel(image_array, axis=0) # x 方向的梯度
gradient_y = sobel(image_array, axis=1) # y 方向的梯度
# 计算梯度幅度
gradient_magnitude = np.sqrt(gradient_x**2 + gradient_y**2)
# 可视化结果
plt.rcParams['font.sans-serif'] = ['SimHei'] # 支持中文字体
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
# 显示原始图像
axes[0].imshow(image_array, cmap='gray')
axes[0].set_title('原始灰度图像')
axes[0].axis('off')
# 显示 x 方向梯度
axes[1].imshow(gradient_x, cmap='gray')
axes[1].set_title('X 方向梯度')
axes[1].axis('off')
# 显示 y 方向梯度
axes[2].imshow(gradient_y, cmap='gray')
axes[2].set_title('Y 方向梯度')
axes[2].axis('off')
plt.tight_layout()
plt.show()
# 可选:显示梯度幅度
plt.figure(figsize=(6, 6))
plt.imshow(gradient_magnitude, cmap='gray')
plt.title('梯度幅度')
plt.axis('off')
plt.show()
1.4.3 形态学:对象计数
形态学(或数学形态学)是度量和分析基本形状的图像处理方法的基本框架与集合。形态学通常用于处理二值图像,但是也能够用于灰度图像。二值图像是指图像的每个像素只能取两个值,通常是0和1。二值图像通常是,在计算物体的数目,或者度量其大小时,对一幅图像进行阈值化后的结果。
以下为示例代码:
from PIL import Image
import numpy as np
from scipy.ndimage import measurements, morphology
import matplotlib.pyplot as plt
# 读取图像并转换为灰度图像
image_path = r'img.png'
original_image = Image.open(image_path).convert('L')
image_array = np.array(original_image)
# 二值化处理
binary_image = (image_array < 128).astype(int)
# 标记二值化图像中的连通区域
labels, num_objects = measurements.label(binary_image)
# 执行开操作(形态学开操作)
structure_element = np.ones((9, 5))
opened_image = morphology.binary_opening(binary_image, structure_element, iterations=2)
opened_labels, num_opened_objects = measurements.label(opened_image)
# 可视化结果
plt.rcParams['font.sans-serif'] = ['SimHei'] # 支持中文字体
fig, axes = plt.subplots(2, 2, figsize=(12, 12))
# 显示二值化图像
axes[0, 0].imshow(binary_image, cmap='gray')
axes[0, 0].set_title('二值化图像')
axes[0, 0].axis('off')
# 显示标签图像
axes[0, 1].imshow(labels, cmap='nipy_spectral')
axes[0, 1].set_title('标签数组图像')
axes[0, 1].axis('off')
# 显示开操作后的图像
axes[1, 0].imshow(opened_image, cmap='gray')
axes[1, 0].set_title('开操作后图像')
axes[1, 0].axis('off')
# 显示开操作后的标签图像
axes[1, 1].imshow(opened_labels, cmap='nipy_spectral')
axes[1, 1].set_title('开操作后标签数组图像')
axes[1, 1].axis('off')
plt.tight_layout()
plt.show()
# 打印对象个数
print(f"开操作前对象个数为:{num_objects}")
print(f"开操作后对象个数为:{num_opened_objects}")
1.5 高级去噪
图像去噪是在去除图像噪声的同时,尽可能地保留图像细节和结构的处理技术。我们这里使用ROF(Rudin-Osher-Fatemi)去噪模型。该模型最早出现在文献[28]中。图像去噪对于很多应用来说都非常重要;这些应用范围很广,小到让你的假期照片看起来更漂亮,大到提高卫星图像的质量。ROF模型具有很好的性质:使处理后的图像更平滑,同时保持图像边缘和结构信息。
以下为实验代码,先对图像进行高斯模糊,然后再去噪:
from matplotlib import pyplot as plt
from PIL import Image
import numpy as np
from scipy.ndimage import gaussian_filter
def denoise(image, initial_guess, tolerance=0.1, tau=0.125, K=100):
m, n = image.shape
U = initial_guess
px = image.copy()
py = image.copy()
error = 1
while error > tolerance:
U_old = U.copy()
grad_x = np.roll(U, -1, axis=1) - U
grad_y = np.roll(U, -1, axis=0) - U
px_new = px + (tau / K) * grad_x
py_new = py + (tau / K) * grad_y
norm_new = np.maximum(1, np.sqrt(px_new**2 + py_new**2))
px = px_new / norm_new
py = py_new / norm_new
rx_px = np.roll(px, 1, axis=1)
ry_py = np.roll(py, 1, axis=0)
div_p = (px - rx_px) + (py - ry_py)
U = image + K * div_p
error = np.linalg.norm(U - U_old) / np.sqrt(m * n)
return U, image - U
# 读取图像并转换为灰度图像
image_path = r'img.png'
original_image = Image.open(image_path).convert('L')
image_array = np.array(original_image)
# 去噪处理
denoised_image, _ = denoise(image_array, image_array)
# 应用高斯模糊
blurred_image = gaussian_filter(image_array, sigma=10)
# 可视化结果
plt.rcParams['font.sans-serif'] = ['SimHei'] # 支持中文字体
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
# 显示原始图像
axes[0].imshow(image_array, cmap='gray')
axes[0].set_title('原始图像')
axes[0].axis('off')
# 显示经过高斯模糊后的图像
axes[1].imshow(blurred_image, cmap='gray')
axes[1].set_title('高斯模糊后')
axes[1].axis('off')
# 显示去噪后的图像
axes[2].imshow(denoised_image, cmap='gray')
axes[2].set_title('去噪后')
axes[2].axis('off')
plt.tight_layout()
plt.show()