← 返回列表
图片批量压缩与裁剪
利用 Pillow (PIL) 图像处理库,对文件夹中的高分辨率图片进行批量压缩和尺寸调整,以便于网页加载或存储优化,保持画质与体积的平衡。
快速预览:
from PIL import Image
img = Image.open('photo.jpg')
# 调整尺寸为 800x600
img = img.resize((800, 600))
# 保存并设置压缩质量
img.save('photo_optimized.jpg', quality=85)
应用场景与价值
在数字化时代,图片处理是一项常见的需求。批量图片压缩与裁剪工具在以下场景中极具价值:
- 网站优化: 压缩网页图片,加快加载速度,提升用户体验和 SEO 排名
- 存储管理: 减小照片文件大小,节省云存储空间和本地硬盘
- 社交媒体: 批量调整图片尺寸以符合平台要求(Instagram、微信朋友圈等)
- 电商运营: 统一商品图片尺寸,生成缩略图和详情图
- 摄影工作流: 批量处理拍摄的照片,生成预览图和打印版
- 移动应用: 压缩图片资源,减小 APP 安装包体积
手动处理大量图片费时费力,使用 Python 的 Pillow 库可以在几分钟内自动处理成百上千张图片,保持高质量的同时显著减小文件体积。
基础知识
Pillow 库
Pillow 是 Python Imaging Library (PIL) 的现代分支,是 Python 最流行的图像处理库。
图片基础概念
- 分辨率: 图片的宽度和高度(像素数),如 1920x1080
- 文件格式: JPEG、PNG、GIF、BMP、WebP 等
- 色彩模式: RGB(彩色)、RGBA(带透明度)、L(灰度)等
- 压缩质量: JPEG 的压缩程度,通常 1-100,值越大质量越高
常用图片格式对比
| 格式 | 特点 | 适用场景 |
|---|---|---|
| JPEG | 有损压缩,体积小 | 照片、复杂图像 |
| PNG | 无损压缩,支持透明 | Logo、图标、需要透明度的图片 |
| WebP | Google 开发,体积更小 | 现代网站优化 |
| GIF | 支持动画 | 简单动画 |
Pillow 核心操作
from PIL import Image
# 打开图片
img = Image.open('photo.jpg')
# 获取图片信息
print(img.size) # (宽度, 高度)
print(img.format) # 'JPEG'
print(img.mode) # 'RGB'
# 调整尺寸
img_resized = img.resize((800, 600))
# 裁剪
img_cropped = img.crop((100, 100, 500, 500)) # (left, top, right, bottom)
# 旋转
img_rotated = img.rotate(90)
# 保存
img.save('output.jpg', quality=85)
代码详解
让我们深入理解图片压缩的核心代码:
from PIL import Image
导入 Pillow 库的 Image 模块。如果未安装,使用 uv add pillow 安装。
img = Image.open('photo.jpg')
打开并加载图片到内存。Pillow 会自动识别图片格式。
img = img.resize((800, 600))
调整图片尺寸为 800x600 像素。默认使用高质量的重采样算法(LANCZOS)。注意这会改变图片的宽高比,可能导致变形。
img.save('photo_optimized.jpg', quality=85)
保存压缩后的图片。quality 参数控制 JPEG 压缩质量(1-100):
- 95-100: 极高质量,文件较大
- 85-94: 高质量,适合大多数场景(推荐)
- 75-84: 中等质量,文件较小
- 60-74: 低质量,明显失真
完整代码
基础版本:批量压缩图片
from PIL import Image
import os
def batch_compress_images(input_folder, output_folder, quality=85, max_size=None):
"""
批量压缩图片
参数:
input_folder: 输入文件夹路径
output_folder: 输出文件夹路径
quality: JPEG 压缩质量(1-100)
max_size: 最大尺寸 (宽度, 高度),None 表示保持原尺寸
"""
# 创建输出文件夹
os.makedirs(output_folder, exist_ok=True)
# 支持的图片格式
supported_formats = ('.jpg', '.jpeg', '.png', '.bmp', '.gif')
processed_count = 0
total_original_size = 0
total_compressed_size = 0
for filename in os.listdir(input_folder):
if not filename.lower().endswith(supported_formats):
continue
input_path = os.path.join(input_folder, filename)
output_path = os.path.join(output_folder, filename)
try:
# 获取原始文件大小
original_size = os.path.getsize(input_path)
total_original_size += original_size
# 打开图片
with Image.open(input_path) as img:
# 转换 RGBA 或 P 模式到 RGB(JPEG 不支持透明度)
if img.mode in ('RGBA', 'P'):
img = img.convert('RGB')
# 调整尺寸(如果指定)
if max_size:
img.thumbnail(max_size, Image.Resampling.LANCZOS)
# 保存压缩图片
img.save(output_path, 'JPEG', quality=quality, optimize=True)
# 获取压缩后文件大小
compressed_size = os.path.getsize(output_path)
total_compressed_size += compressed_size
reduction = (1 - compressed_size / original_size) * 100
print(f"处理: {filename} | 原始: {original_size / 1024:.1f}KB -> 压缩: {compressed_size / 1024:.1f}KB | 减少: {reduction:.1f}%")
processed_count += 1
except Exception as e:
print(f"处理 {filename} 失败: {e}")
# 输出总结
print("\n" + "=" * 60)
print(f"批量压缩完成!")
print(f"处理图片数: {processed_count}")
print(f"总原始大小: {total_original_size / 1024 / 1024:.2f} MB")
print(f"总压缩大小: {total_compressed_size / 1024 / 1024:.2f} MB")
print(f"总体积减少: {(1 - total_compressed_size / total_original_size) * 100:.1f}%")
print("=" * 60)
# 使用示例
if __name__ == "__main__":
batch_compress_images(
input_folder='./original_photos',
output_folder='./compressed_photos',
quality=85,
max_size=(1920, 1080) # 限制最大尺寸
)
进阶版本:智能裁剪与缩略图生成
from PIL import Image
import os
def create_thumbnails(input_folder, output_folder, sizes):
"""
生成多种尺寸的缩略图
参数:
sizes: 尺寸列表,如 [(200, 200), (400, 400), (800, 800)]
"""
os.makedirs(output_folder, exist_ok=True)
for filename in os.listdir(input_folder):
if not filename.lower().endswith(('.jpg', '.jpeg', '.png')):
continue
input_path = os.path.join(input_folder, filename)
name, ext = os.path.splitext(filename)
try:
with Image.open(input_path) as img:
if img.mode in ('RGBA', 'P'):
img = img.convert('RGB')
for width, height in sizes:
# 智能裁剪:保持宽高比的中心裁剪
img_copy = img.copy()
img_copy.thumbnail((width * 2, height * 2), Image.Resampling.LANCZOS)
# 计算裁剪位置(居中)
left = (img_copy.width - width) // 2
top = (img_copy.height - height) // 2
right = left + width
bottom = top + height
img_cropped = img_copy.crop((left, top, right, bottom))
# 保存缩略图
output_filename = f"{name}_{width}x{height}{ext}"
output_path = os.path.join(output_folder, output_filename)
img_cropped.save(output_path, quality=85, optimize=True)
print(f"生成缩略图: {output_filename}")
except Exception as e:
print(f"处理 {filename} 失败: {e}")
# 使用示例
create_thumbnails(
input_folder='./photos',
output_folder='./thumbnails',
sizes=[(200, 200), (400, 400), (800, 800)]
)
高级版本:保持宽高比的智能压缩
from PIL import Image
import os
def smart_resize(img, max_width, max_height):
"""
智能调整尺寸:保持宽高比,不超过最大尺寸
返回:
调整后的图片对象
"""
original_width, original_height = img.size
# 计算缩放比例
width_ratio = max_width / original_width
height_ratio = max_height / original_height
scale = min(width_ratio, height_ratio, 1.0) # 不放大图片
if scale < 1.0:
new_width = int(original_width * scale)
new_height = int(original_height * scale)
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
return img
def advanced_batch_process(input_folder, output_folder, max_width=1920,
max_height=1080, quality=85, format_convert='JPEG'):
"""
高级批量图片处理
参数:
format_convert: 目标格式('JPEG', 'PNG', 'WebP')
"""
os.makedirs(output_folder, exist_ok=True)
for filename in os.listdir(input_folder):
if not filename.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp')):
continue
input_path = os.path.join(input_folder, filename)
name, _ = os.path.splitext(filename)
# 根据目标格式设置扩展名
ext_map = {'JPEG': '.jpg', 'PNG': '.png', 'WebP': '.webp'}
new_ext = ext_map.get(format_convert, '.jpg')
output_filename = name + new_ext
output_path = os.path.join(output_folder, output_filename)
try:
with Image.open(input_path) as img:
# 处理透明度
if format_convert == 'JPEG' and img.mode in ('RGBA', 'P'):
# JPEG 不支持透明,创建白色背景
background = Image.new('RGB', img.size, (255, 255, 255))
background.paste(img, mask=img.split()[-1] if img.mode == 'RGBA' else None)
img = background
elif img.mode not in ('RGB', 'RGBA'):
img = img.convert('RGB')
# 智能调整尺寸
img = smart_resize(img, max_width, max_height)
# 保存
save_kwargs = {'optimize': True}
if format_convert in ('JPEG', 'WebP'):
save_kwargs['quality'] = quality
img.save(output_path, format_convert, **save_kwargs)
original_size = os.path.getsize(input_path) / 1024
compressed_size = os.path.getsize(output_path) / 1024
print(f"{filename} -> {output_filename} | {original_size:.1f}KB -> {compressed_size:.1f}KB")
except Exception as e:
print(f"处理 {filename} 失败: {e}")
# 使用示例
advanced_batch_process(
input_folder='./original',
output_folder='./optimized',
max_width=1920,
max_height=1080,
quality=85,
format_convert='JPEG'
)
添加水印功能
from PIL import Image, ImageDraw, ImageFont
def add_watermark(img, text, position='bottom-right', opacity=128):
"""
为图片添加文字水印
参数:
text: 水印文字
position: 位置 ('top-left', 'top-right', 'bottom-left', 'bottom-right', 'center')
opacity: 不透明度 (0-255)
"""
# 创建透明图层
watermark_layer = Image.new('RGBA', img.size, (0, 0, 0, 0))
draw = ImageDraw.Draw(watermark_layer)
# 设置字体(如果系统字体不可用,会使用默认字体)
try:
font = ImageFont.truetype('Arial.ttf', 36)
except:
font = ImageFont.load_default()
# 获取文本大小
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
# 计算位置
margin = 20
positions = {
'top-left': (margin, margin),
'top-right': (img.width - text_width - margin, margin),
'bottom-left': (margin, img.height - text_height - margin),
'bottom-right': (img.width - text_width - margin, img.height - text_height - margin),
'center': ((img.width - text_width) // 2, (img.height - text_height) // 2)
}
xy = positions.get(position, positions['bottom-right'])
# 绘制水印
draw.text(xy, text, fill=(255, 255, 255, opacity), font=font)
# 合并图层
img_with_watermark = Image.alpha_composite(img.convert('RGBA'), watermark_layer)
return img_with_watermark.convert('RGB')
# 使用示例
img = Image.open('photo.jpg')
img_watermarked = add_watermark(img, '© 2026 Your Name', position='bottom-right')
img_watermarked.save('photo_watermarked.jpg', quality=90)
运行示例
安装依赖
uv add pillow
准备测试图片
将需要处理的图片放入 original_photos 文件夹。
执行压缩
uv run main.py
预期输出
处理: photo1.jpg | 原始: 3520.5KB -> 压缩: 452.3KB | 减少: 87.1%
处理: photo2.jpg | 原始: 2890.2KB -> 压缩: 380.7KB | 减少: 86.8%
处理: photo3.png | 原始: 5120.8KB -> 压缩: 620.5KB | 减少: 87.9%
...
============================================================
批量压缩完成!
处理图片数: 20
总原始大小: 68.45 MB
总压缩大小: 8.92 MB
总体积减少: 87.0%
============================================================
扩展思路
1. 图片格式转换
def convert_to_webp(input_folder, output_folder, quality=80):
"""批量转换为 WebP 格式"""
for filename in os.listdir(input_folder):
if filename.lower().endswith(('.jpg', '.jpeg', '.png')):
input_path = os.path.join(input_folder, filename)
output_path = os.path.join(output_folder, os.path.splitext(filename)[0] + '.webp')
with Image.open(input_path) as img:
img.save(output_path, 'WebP', quality=quality)
2. 图片滤镜效果
from PIL import ImageFilter, ImageEnhance
# 锐化
img_sharp = img.filter(ImageFilter.SHARPEN)
# 模糊
img_blur = img.filter(ImageFilter.GaussianBlur(radius=2))
# 调整亮度
enhancer = ImageEnhance.Brightness(img)
img_bright = enhancer.enhance(1.5) # 1.5倍亮度
# 调整对比度
enhancer = ImageEnhance.Contrast(img)
img_contrast = enhancer.enhance(1.3)
# 转灰度
img_gray = img.convert('L')
3. 智能内容识别裁剪
使用人脸识别库进行智能裁剪:
# 需要安装: uv add opencv-python
import cv2
def smart_crop_with_face_detection(image_path, output_size):
"""基于人脸检测的智能裁剪"""
img = cv2.imread(image_path)
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
faces = face_cascade.detectMultiScale(img, 1.1, 4)
if len(faces) > 0:
# 以检测到的人脸为中心裁剪
x, y, w, h = faces[0]
center_x, center_y = x + w // 2, y + h // 2
# 裁剪逻辑...
4. 批量添加边框
from PIL import ImageOps
def add_border(img, border_size=10, color='white'):
"""为图片添加边框"""
return ImageOps.expand(img, border=border_size, fill=color)
5. 图片质量评估
def estimate_quality(image_path):
"""估算图片质量(基于文件大小和分辨率)"""
img = Image.open(image_path)
file_size = os.path.getsize(image_path)
pixels = img.width * img.height
bytes_per_pixel = file_size / pixels
if bytes_per_pixel > 3:
return "高质量"
elif bytes_per_pixel > 1.5:
return "中等质量"
else:
return "低质量"
6. 生成图片网格
def create_image_grid(image_paths, grid_size=(3, 3), thumbnail_size=(300, 300)):
"""将多张图片组合成网格"""
cols, rows = grid_size
width, height = thumbnail_size
grid_img = Image.new('RGB', (cols * width, rows * height), 'white')
for i, path in enumerate(image_paths[:cols * rows]):
img = Image.open(path)
img.thumbnail(thumbnail_size, Image.Resampling.LANCZOS)
col = i % cols
row = i // cols
grid_img.paste(img, (col * width, row * height))
return grid_img
7. EXIF 数据处理
from PIL.ExifTags import TAGS
def read_exif(image_path):
"""读取图片 EXIF 信息"""
img = Image.open(image_path)
exif_data = img._getexif()
if exif_data:
for tag_id, value in exif_data.items():
tag = TAGS.get(tag_id, tag_id)
print(f"{tag}: {value}")
def remove_exif(image_path, output_path):
"""移除图片 EXIF 信息(保护隐私)"""
img = Image.open(image_path)
data = list(img.getdata())
image_without_exif = Image.new(img.mode, img.size)
image_without_exif.putdata(data)
image_without_exif.save(output_path)
注意事项
图片质量
- 质量参数选择: 85-90 是平衡质量和体积的最佳值
- 避免重复压缩: 已压缩的图片不应再次压缩,会导致质量严重下降
- 原始备份: 保留原始高质量图片,避免不可逆损失
性能优化
- 批量处理: 一次处理多张图片时考虑使用多进程
- 内存管理: 处理大图时及时关闭图片对象释放内存
- 缓存机制: 避免重复处理已处理的图片
格式选择
- JPEG: 照片、复杂图像(有损压缩)
- PNG: 需要透明度、简单图形(无损压缩)
- WebP: 现代网站(更小体积,需检查浏览器兼容性)
版权与隐私
- 水印保护: 为原创图片添加水印防止盗用
- EXIF 数据: 移除敏感位置信息保护隐私
- 版权声明: 确保有权处理和使用这些图片
通过掌握 Pillow 图片处理技术,你可以高效地进行图片批量优化、创建自动化的图片处理流程,为网站性能优化、内容管理和创意设计提供强大支持。