
01
引言
大家好,我是AI算法之道!
手工、重复性工作。唉。这是我最讨厌的工作之一,尤其是当我知道这些工作可以自动完成时。想象一下,你需要用同样的裁剪和调整大小操作来编辑一堆图片。对于几张图片,你可能只需打开图片编辑器,然后手工操作。但是,如果要对数千或数万张图片进行同样的操作呢?

让我们看看如何使用 Python 和 OpenCV 自动完成这样的图像处理任务,以及如何优化这一数据处理过程,以便在大型数据集上高效运行。
02
数据集
这与训练机器学习模型时可能需要的数据集预处理步骤大致相似。
03
安装依赖
pip install opencv-python tqdm
代码:https://github.com/trflorian/image-processing
04
数据加载
首先,让我们使用 OpenCV 逐个加载图片。所有图片都在一个子文件夹中,我们将使用 pathlib中的glob 方法查找该文件夹中的所有 png 文件。为了显示进度,我使用了 tqdm 库。通过使用 sorted 方法,我确保对路径进行了排序,并将 glob 调用返回的生成器转换为列表。这样,tqdm 就能知道迭代的长度,从而显示进度条。
from pathlib import Pathfrom tqdm import tqdmimg_paths = Path("images").glob("*.png")for img_path in tqdm(sorted(img_paths)):pass
现在我们还可以准备输出目录,并确保其存在。经过处理的图像将存放在这里。
output_path = Path("output")output_path.mkdir(exist_ok=True, parents=True)
05
图像处理
为了处理图像,让我们定义一个函数。它将输入和输出图像路径作为参数。
def process_image(input_path: Path, output_path: Path) -> None:"""Image processing pipeline:- Center crop to square aspect ratio- Resize to 224x224Args:input_path (Path): Path to input imageoutput_path (Path): Path to save processed image"""
要实现这一功能,我们首先需要用 OpenCV 加载图像。确保在文件开头导入 opencv 包。
import cv2def process_image(input_path: Path, output_path: Path) -> None:...# Read imageimg = cv2.imread(str(input_path))
要裁剪图像,我们可以直接在 x 轴上对图像数组进行切片。请记住,OpenCV 图像数组是以 YXC 形状存储的:X/Y 是图像的二维轴,从左上角开始,C 是颜色通道。因此,X 轴是图像的第二个索引。为简单起见,我假设图像为横向格式,宽度大于高度。
height, width, _ = img.shapeimg = img[:, (width - height) // 2 : (width + height) // 2, :]
要调整图像大小,我们只需使用 OpenCV 的 resize 函数即可。如果我们不指定插值方法,它将使用双线性插值,这对本项目来说没有问题。
target_size = 224img = cv2.resize(img, (target_size, target_size)
最后,必须使用 imwrite 功能将图像保存到输出文件中。
cv2.imwrite(str(output_path), img)
现在,我们只需在图像路径的循环中调用 process_image 函数即可。
for img_path in tqdm(sorted(img_paths)):process_image(input_path=img_path, output_path=output_path / img_path.name)
如果在我的机器上运行这个程序,处理 10,000 张图像需要一分多钟。
4%|█████▏ | 441/10000
06
多核处理
为了让我们的程序使用更多的可用内核,我们需要使用 python 中一个名为Multiprocessing的模块。由于全局解释器锁 (GIL),单个 python 进程无法真正并行运行任务(除非禁用 GIL,Python≥3.13 可以做到这一点)。我们需要做的是产生多个 python 进程(因此被称为多进程),由我们的主 python 程序管理。
为了实现这一点,我们可以使用 python 内置的多进程和并发模块。理论上,我们可以手动生成 python 进程,同时确保提交的进程数量不超过我们的内核数。由于我们的进程受 CPU 约束,因此进程数量越多,速度就越慢,因为它们只能等待。事实上,在进程间切换的开销一度会超过并行化的优势。
为了管理 python 进程,我们可以使用 ProcessPoolExecutor。它将保留一个 python 进程池,而不是为每个提交的任务完全销毁和重启每个进程。默认情况下,它会使用与可用逻辑 CPU 数量相同的进程池,CPU 数量可从 os.process_cpu_count() 中获取。因此,默认情况下,CPU 的每个内核都会产生一个进程,我的情况是 20 个。你也可以提供一个 max_workers 参数,指定在池中催生的进程数。
from concurrent.futures import ProcessPoolExecutor...output_paths = [output_path / img_path.name for img_path in img_paths]with ProcessPoolExecutor() as executor:all_processes = executor.map(process_image,img_paths,output_paths,)for _ in tqdm(all_processes, total=len(img_paths)):pass
我们使用上下文管理器(with 语句)创建一个进程池执行器,这将确保即使在执行过程中出现异常,进程也会被清理。然后,我们使用 map 函数为每个输入图像路径和输出路径创建一个进程。最后,通过使用 tqdm 封装 all_processes 的迭代,我们可以获得已完成进程的进度条。
18%|█████ | 1760/10000
现在,如果运行程序并再次检查 CPU 利用率,就会发现所有内核都已使用!进度条还显示了迭代速度的提高。
07
比较
为了快速检查是否合理,我绘制了使用不同数量的并行化处理 1000 张图像的时间图,从单个 Worker 方案开始,将 Worker 数量增加到机器内核数量的两倍。下图显示,最佳值接近 CPU 内核数。从 1 个 Worker 到多个 Worker,性能急剧上升,而当 Worker 数量超过 CPU 内核数量时,性能略有下降。
08
结论
在本文章中,您将了解到如何通过在所有可用内核上并行运行处理程序来高效处理图像数据集。通过这种方式,数据处理流水线的速度大大加快。希望你今天有所收获,祝你编码愉快,并保重身体!
点击上方小卡片关注我
添加个人微信,进专属粉丝群!


