ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python批量图片裁剪:Pillow库实战与自动化处理技巧

Python批量图片裁剪:Pillow库实战与自动化处理技巧 1. 项目概述与核心需求解析最近在整理一个图像数据集手头有上千张图片但每张图片四周都有一圈多余的黑边或者不需要的空白区域。一张张用PS或者看图软件手动裁剪那得干到猴年马月去。这种重复性高、规则性强的批量图片处理任务正是Python大显身手的地方。这个项目的核心就是利用Python脚本自动化地遍历一个源文件夹里的所有图片对它们进行统一的裁剪操作然后将处理好的图片保存到另一个指定的目标文件夹下。听起来简单但里面涉及到的路径处理、图像库选择、批量操作逻辑以及异常处理都是实际工作中经常会遇到的“坎儿”。无论是做机器学习的同学需要预处理训练集还是设计师需要批量处理素材或者是普通用户想整理手机相册掌握这个技能都能极大提升效率。2. 技术方案选型与工具准备2.1 为什么选择PIL/Pillow在Python的图像处理领域有几个常见的库OpenCV、PIL/Pillow、scikit-image。对于“裁剪并保存”这个核心需求我首推PIL/Pillow。原因很简单它轻量、接口直观、对于基本的图像IO和操作如裁剪、缩放、格式转换支持得非常好而且安装简单。OpenCV功能更强大尤其在计算机视觉领域但它处理图像时默认使用BGR通道顺序对于纯图像处理任务有时会引入不必要的麻烦比如显示颜色问题。Pillow作为PILPython Imaging Library的友好分支完全兼容且持续维护是我们这次任务的“瑞士军刀”。安装命令非常简单使用pip即可pip install Pillow如果网络环境特殊可以考虑使用国内镜像源加速例如pip install Pillow -i https://pypi.tuna.tsinghua.edu.cn/simple2.2 项目目录结构设计在写代码之前规划好目录结构能让逻辑更清晰。我建议采用如下结构your_project/ ├── source_images/ # 源文件夹存放待处理的原始图片 │ ├── img1.jpg │ ├── img2.png │ └── ... ├── output_images/ # 目标文件夹用于保存裁剪后的图片脚本会自动创建 └── batch_crop.py # 我们的Python脚本这种结构将输入、输出和逻辑分离符合“单一职责”原则后续维护和排查问题都更方便。output_images文件夹最好由脚本自动创建避免手动操作遗漏。3. 核心代码实现与逐行解析接下来我们一步步构建核心脚本。我会先给出完整代码然后拆解每一个关键部分。3.1 完整脚本代码import os from PIL import Image def batch_crop_images(source_dir, output_dir, crop_box): 批量裁剪图片并保存到新文件夹。 参数: source_dir (str): 源图片文件夹路径。 output_dir (str): 输出图片文件夹路径。 crop_box (tuple): 裁剪区域格式为 (left, upper, right, lower)。 # 1. 检查源文件夹是否存在 if not os.path.exists(source_dir): print(f错误源文件夹 {source_dir} 不存在。) return # 2. 创建输出文件夹如果不存在 os.makedirs(output_dir, exist_okTrue) # 3. 获取源文件夹中所有支持的文件 supported_extensions (.jpg, .jpeg, .png, .bmp, .gif, .tiff) image_files [f for f in os.listdir(source_dir) if f.lower().endswith(supported_extensions)] if not image_files: print(f警告在 {source_dir} 中未找到支持的图片文件。) return print(f开始在 {source_dir} 中处理 {len(image_files)} 张图片...) # 4. 遍历并处理每张图片 processed_count 0 for filename in image_files: try: # 构建完整的文件路径 source_path os.path.join(source_dir, filename) # 打开图片 with Image.open(source_path) as img: # 将图片转换为RGB模式处理RGBA或P模式等 if img.mode ! RGB: img img.convert(RGB) # 执行裁剪操作 cropped_img img.crop(crop_box) # 构建输出文件路径保持原文件名 # 可以在这里修改输出格式例如强制保存为JPG name, ext os.path.splitext(filename) output_filename f{name}_cropped.jpg # 统一输出为jpg output_path os.path.join(output_dir, output_filename) # 保存图片优化JPG质量 cropped_img.save(output_path, JPEG, quality95, optimizeTrue) processed_count 1 print(f 已处理: {filename} - {output_filename}) except Exception as e: print(f 处理文件 {filename} 时出错: {e}) continue print(f处理完成成功处理 {processed_count}/{len(image_files)} 张图片。) print(f输出文件位于: {os.path.abspath(output_dir)}) if __name__ __main__: # 用户配置区域 SOURCE_FOLDER ./source_images # 替换为你的源文件夹路径 OUTPUT_FOLDER ./output_images # 替换为你的输出文件夹路径 # 定义裁剪区域 (left, upper, right, lower) # 例如从左上角(100, 50)到右下角(900, 650)的矩形区域 CROP_BOX (100, 50, 900, 650) # 执行批量裁剪 batch_crop_images(SOURCE_FOLDER, OUTPUT_FOLDER, CROP_BOX)3.2 关键函数与参数详解batch_crop_images函数是整个脚本的核心。它接收三个参数source_dir: 源文件夹路径。这里需要传入一个字符串指向存放原始图片的目录。output_dir: 输出文件夹路径。脚本会将处理后的图片保存到这里。crop_box: 裁剪区域。这是一个包含四个整数的元组(left, upper, right, lower)。这是Pillow裁剪操作的关键必须理解清楚left: 裁剪区域左边界的x坐标距离图片左边缘的像素数。upper: 裁剪区域上边界的y坐标距离图片上边缘的像素数。right: 裁剪区域右边界的x坐标距离图片左边缘的像素数。注意right必须大于left。lower: 裁剪区域下边界的y坐标距离图片上边缘的像素数。注意lower必须大于upper。坐标系统Pillow的坐标原点(0, 0)在图片的左上角x轴向右延伸y轴向下延伸。这与常见的数学坐标系不同务必注意。如何确定crop_box的值这是实操中最关键的一步。如果你不知道具体要裁剪哪里有几种方法手动测量用系统自带的画图工具或任何看图软件打开一张样例图片将鼠标悬停在目标区域的角落状态栏通常会显示坐标。编写预览脚本可以先写一个简单的脚本用Pillow打开图片获取其尺寸img.size返回(width, height)然后根据比例估算。例如想居中裁剪一个300x300的区域图片尺寸是1000x800那么crop_box可以计算为((1000-300)//2, (800-300)//2, (1000-300)//2300, (800-300)//2300)即(350, 250, 650, 550)。3.3 文件遍历与格式处理逻辑脚本中os.listdir结合列表推导式用于筛选出常见的图片格式文件。supported_extensions元组定义了脚本支持处理的格式。这里有一个重要细节我们使用了f.lower().endswith(...)来确保不区分文件名的大小写例如.JPG和.jpg都能被识别。在打开图片后有一行代码if img.mode ! RGB: img img.convert(RGB)。这是为了处理带有透明度通道的PNGRGBA模式或者调色板模式的GIFP模式等图片。统一转换为RGB模式可以避免后续保存为JPG时出现意外错误或警告。如果你的输出需要保留透明度则不能简单转换为RGB而需要考虑输出为PNG格式并做相应处理。3.4 保存图片的优化技巧cropped_img.save(output_path, JPEG, quality95, optimizeTrue)这行代码有几个可调节的参数quality95: 指定JPG的保存质量范围1-10095是一个在质量和文件大小之间取得很好平衡的值。如果需要更小的文件可以降低到85或75。optimizeTrue: 启用额外的优化可能会稍微增加保存时间但能生成更小的文件。如果你想保持原始格式可以判断原始后缀并使用img.format来保存。但为了统一我示例中强制保存为JPG并修改了后缀名。如果需要保留原始格式代码可以修改为# 保留原始格式的保存方式 cropped_img.save(output_path, formatimg.format or JPEG)4. 高级功能与自定义扩展基础的批量裁剪功能已经实现但在实际项目中需求往往更复杂。下面介绍几个常见的扩展方向。4.1 动态确定裁剪区域固定crop_box适用于所有图片尺寸和内容一致的情况。但如果图片大小不一或者需要裁剪的内容位置不固定例如总是裁剪掉图片底部50像素的水印就需要动态计算。示例1等比例居中裁剪假设我们想把所有图片都裁剪成中心的正方形。def crop_center_square(img): 将图片裁剪为居中的正方形 width, height img.size new_size min(width, height) # 取短边作为正方形的边长 left (width - new_size) // 2 upper (height - new_size) // 2 right left new_size lower upper new_size return img.crop((left, upper, right, lower))然后在主循环中将img.crop(crop_box)替换为crop_center_square(img)。示例2裁剪固定边缘比如所有图片顶部有80像素的横幅底部有120像素的水印需要去掉。def crop_fixed_margins(img, top_margin80, bottom_margin120): 裁剪掉图片顶部和底部固定高度的区域 width, height img.size new_top top_margin new_bottom height - bottom_margin # 确保裁剪区域有效 if new_bottom new_top: return img.crop((0, new_top, width, new_bottom)) else: print(f警告图片高度{height}不足以裁剪顶部{top_margin}和底部{bottom_margin}。) return img # 返回原图4.2 支持更多图片格式与元数据保留Pillow支持读取的格式远多于我们列出的几种。你可以通过Image.registered_extensions()查看所有支持的解码格式。对于保存常见的格式如WebP、JPEG 2000等也支持。如果需要保留图片的EXIF信息如拍摄时间、相机参数在裁剪后可能会丢失。Pillow的Image对象有一个info属性可能包含一些元数据。更专业的处理可以使用piexif库来提取和重新注入EXIF数据。# 示例使用piexif保留EXIF需先安装 pip install piexif import piexif from PIL import Image with Image.open(source_path) as img: # 提取原图的exif数据 try: exif_dict piexif.load(img.info[exif]) except: exif_dict None # ... 进行裁剪操作 ... # 保存时如果有exif数据则注入 if exif_dict: # 注意裁剪后图片尺寸变化可能需要更新部分EXIF标签如像素尺寸 exif_bytes piexif.dump(exif_dict) cropped_img.save(output_path, JPEG, exifexif_bytes, quality95) else: cropped_img.save(output_path, JPEG, quality95)4.3 添加进度显示与并行处理当处理成千上万张图片时一个进度条和并行加速会大大改善体验。使用tqdm添加进度条pip install tqdmfrom tqdm import tqdm # 在主循环处替换 for filename in tqdm(image_files, desc处理图片): # ... 处理逻辑 ...使用多进程加速适用于CPU密集型任务Python的concurrent.futures模块可以方便地实现并行。from concurrent.futures import ProcessPoolExecutor, as_completed def process_single_file(args): 包装单文件处理逻辑使其可并行化 filename, source_dir, output_dir, crop_box args # ... 这里放入之前对单个文件处理的try-catch逻辑 ... # 注意函数内需要自己处理路径拼接和保存 return filename, success # 返回处理结果 # 在主函数中替换遍历循环 file_args [(f, source_dir, output_dir, crop_box) for f in image_files] with ProcessPoolExecutor(max_workers4) as executor: # max_workers根据CPU核心数调整 futures {executor.submit(process_single_file, arg): arg for arg in file_args} for future in as_completed(futures): filename, success future.result() # 更新进度或日志注意并行处理时要确保每个进程的任务是独立的避免共享状态或资源竞争。文件IO可能成为瓶颈如果图片非常小多进程带来的开销可能抵消其收益。5. 实战演练处理一个真实图片集假设我们有一个product_photos文件夹里面是电商产品图但每张图四周都有10%的灰色背景边我们需要去掉这个边只保留中间的产品主体。步骤1分析单张图片我们先写一个快速查看脚本了解图片尺寸和背景色。from PIL import Image import os sample_path ./product_photos/sample.jpg with Image.open(sample_path) as img: print(f尺寸: {img.size}) # 例如 (1200, 800) print(f模式: {img.mode}) # 获取四个角像素的颜色判断是否为纯色背景 corners [ img.getpixel((0, 0)), img.getpixel((img.width-1, 0)), img.getpixel((0, img.height-1)), img.getpixel((img.width-1, img.height-1)) ] print(f四角像素: {corners})假设输出发现四角都是 (240, 240, 240)即浅灰色。步骤2设计动态裁剪逻辑我们不能简单固定像素值裁剪因为图片大小可能不同。我们的目标是裁剪掉四周10%的区域。def crop_percentage_margins(img, margin_percent0.1): 裁剪掉图片四周指定百分比的外边 width, height img.size margin_x int(width * margin_percent) margin_y int(height * margin_percent) left margin_x upper margin_y right width - margin_x lower height - margin_y # 确保裁剪区域有效 if right left and lower upper: return img.crop((left, upper, right, lower)) else: raise ValueError(f裁剪参数无效计算出的区域为({left}, {upper}, {right}, {lower}))步骤3集成到主脚本并运行修改主脚本中的CROP_BOX逻辑在循环内调用这个动态函数。# 在主循环的try块内替换固定的crop操作 # cropped_img img.crop(crop_box) # 注释掉这行 cropped_img crop_percentage_margins(img, margin_percent0.1) # 使用新函数运行脚本检查output_images文件夹所有产品图应该都被“瘦身”了去掉了灰色的边框。6. 常见问题排查与性能优化在实际操作中你可能会遇到以下问题。这里提供一个速查表问题现象可能原因解决方案FileNotFoundError源文件夹路径错误或文件名包含特殊字符。使用os.path.abspath(source_dir)打印绝对路径检查。用try-except包裹文件打开操作。KeyError: exif尝试读取不存在的EXIF信息。在访问img.info[exif]前用exif in img.info判断。裁剪后的图片是全黑或全白的crop_box坐标定义错误导致区域无效如rightleft。打印出crop_box和图片img.size进行对比调试。确保坐标在图片范围内。处理PNG后背景变黑PNG是RGBA带透明度模式直接转RGB时透明区域用黑色填充。如果背景应是白色可先创建一个白色背景图再将RGBA图片粘贴上去background Image.new(RGB, img.size, (255,255,255)); background.paste(img, maskimg.split()[3])。处理速度非常慢1. 图片分辨率极高。 2. 单线程处理大量图片。1. 如果不需要原尺寸可先缩略再裁剪。 2. 考虑使用上文的多进程并行处理。 3. 检查磁盘IO速度确保不是硬盘瓶颈。内存占用过高同时处理大量超大图片。使用循环逐张处理确保with Image.open() as img:上下文管理器正确关闭文件。避免在内存中同时持有大量Image对象。输出图片名重复覆盖输出文件名规则导致重名。在输出文件名中加入更多标识如原文件名裁剪尺寸时间戳f{name}_{crop_box[2]-crop_box[0]}x{crop_box[3]-crop_box[1]}.jpg。性能优化心得先评估后处理在处理超大批量如10万张图片前先用一小部分如100张测试脚本的稳定性和速度。IO与CPU平衡如果图片很小几十KB多进程的收益可能不明显因为进程创建和通信的开销占比大。此时使用单线程配合tqdm可能更简单稳定。分辨率是性能杀手一张2000万像素的图片处理起来比500万像素的慢得多也占用更多内存。如果最终用途不需要如此高的分辨率例如只是用于网页展示先缩放Image.resize到一个合理尺寸再进行裁剪或其他操作能极大提升速度。错误处理要细致在批量处理中一张图片出错不应导致整个任务中断。务必用try-except包裹核心处理逻辑记录下出错的文件名方便事后单独处理。这个项目虽然起点是一个简单的“裁剪保存”需求但通过逐步深入我们覆盖了路径处理、图像库核心操作、批量逻辑、异常处理、动态参数计算、性能优化等多个实用知识点。掌握它你就能举一反三处理更复杂的批量图像任务比如批量加水印、批量调整色调、批量格式转换等等。工具和脚本是死的但解决问题的思路是相通的。
返回列表