3. 图像处理与滤镜:用Pillow玩转图像
图像处理,说白了就是让计算机帮我们「看」图片,然后按我们的想法去改它。Pillow这个库,是我做桌面应用时最常用的图像工具之一。它轻量、好用,而且API设计得很直观。
我记得刚入行那会儿,有个需求是要批量处理用户上传的头像——缩放、裁剪、加个模糊效果。当时我第一反应是用OpenCV,后来发现杀鸡用牛刀了。Pillow完全够用,而且代码写起来更清爽。
3.1 图像的加载与保存
先来最基础的操作。加载一张图片,用Image.open()就行。保存则用save()方法。
from PIL import Image
# 加载图像
img = Image.open('example.jpg')
print(f"图像格式: {img.format}")
print(f"图像尺寸: {img.size}")
print(f"图像模式: {img.mode}")
# 保存为不同格式
img.save('output.png') # 保存为PNG
img.save('output.bmp') # 保存为BMP
img.save('output.webp') # 保存为WebP
这里有个坑,我曾经踩过——保存时如果不指定格式,Pillow会根据文件扩展名自动判断。但如果你把.jpg的文件保存为.png,它不会报错,只是内部做了格式转换。嗯,这其实挺方便的,但要注意文件大小会变化。
img.info可以查看图像的元数据,比如EXIF信息。我做过一个相册应用,就是靠这个读取拍摄时间、相机型号的。
3.2 图像的缩放与旋转
缩放和旋转,是图像处理里最常用的两个操作。我个人习惯用resize()做缩放,用rotate()做旋转。
from PIL import Image
img = Image.open('example.jpg')
# 缩放 - 指定新尺寸
resized_img = img.resize((800, 600))
resized_img.save('resized.jpg')
# 缩放 - 按比例缩放
width, height = img.size
new_width = 400
ratio = new_width / width
new_height = int(height * ratio)
resized_ratio = img.resize((new_width, new_height))
# 旋转 - 顺时针旋转45度
rotated_img = img.rotate(45, expand=True)
rotated_img.save('rotated.jpg')
你想想看,resize()如果不指定比例,图像会变形。所以我在项目中一般先算比例,再传尺寸。rotate()有个expand=True参数,默认是False。如果不加这个参数,旋转后的图像会被裁剪,只保留原图范围内的部分。我刚开始用的时候就被坑过——旋转后的头像只剩半个脸了。
resize()默认使用最近邻插值,速度最快但质量一般。想要高质量缩放,可以用Image.LANCZOS(也就是Image.ANTIALIAS的升级版)。比如:img.resize((800, 600), Image.LANCZOS)。
3.3 图像的裁剪
裁剪其实很简单,就是指定一个矩形区域。这个区域用(left, upper, right, lower)四个坐标表示。
from PIL import Image
img = Image.open('example.jpg')
# 裁剪 - 取左上角300x300区域
cropped_img = img.crop((0, 0, 300, 300))
cropped_img.save('cropped.jpg')
# 裁剪 - 取中心区域
width, height = img.size
left = (width - 400) // 2
top = (height - 400) // 2
right = left + 400
bottom = top + 400
center_crop = img.crop((left, top, right, bottom))
center_crop.save('center_crop.jpg')
为什么会这样?因为Pillow的坐标系原点在左上角,x轴向右,y轴向下。这个和很多图像库是一致的。我做过一个截图工具,就是靠crop()加上鼠标拖拽实现的——用户选区域,我算坐标,然后裁剪。
3.4 滤镜效果:模糊与边缘检测
滤镜这块,Pillow提供了ImageFilter模块。常用的有模糊、锐化、边缘检测等。我个人觉得,做桌面应用时,模糊和边缘检测是最实用的两个。
from PIL import Image, ImageFilter
img = Image.open('example.jpg')
# 高斯模糊 - 半径越大越模糊
blurred_img = img.filter(ImageFilter.GaussianBlur(radius=5))
blurred_img.save('blurred.jpg')
# 普通模糊
blur_img = img.filter(ImageFilter.BLUR)
blur_img.save('blur.jpg')
# 边缘检测
edge_img = img.filter(ImageFilter.FIND_EDGES)
edge_img.save('edges.jpg')
# 轮廓增强
contour_img = img.filter(ImageFilter.CONTOUR)
contour_img.save('contour.jpg')
我曾经做过一个图片编辑器,用户上传照片后可以一键添加「素描效果」。其实就是先用FIND_EDGES提取边缘,再和原图做混合。效果还挺唬人的。
GaussianBlur(radius):radius越大,模糊越强。一般2-5就够用。FIND_EDGES:输出的是黑白边缘图,背景是黑色,边缘是白色。CONTOUR:类似边缘检测,但输出的是轮廓线,背景保留原色。
3.5 综合示例:批量处理工具
最后,我写一个综合示例。假设你要批量处理一批图片:先缩放到统一尺寸,再裁剪成正方形,最后加个模糊效果。嗯,这个需求我在做用户头像上传功能时遇到过。
from PIL import Image, ImageFilter
import os
def batch_process(input_dir, output_dir, size=256, blur_radius=2):
"""
批量处理图片:缩放 -> 中心裁剪 -> 模糊
"""
if not os.path.exists(output_dir):
os.makedirs(output_dir)
for filename in os.listdir(input_dir):
if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp')):
filepath = os.path.join(input_dir, filename)
# 加载
img = Image.open(filepath)
# 缩放 - 保持比例
img.thumbnail((size, size), Image.LANCZOS)
# 中心裁剪成正方形
width, height = img.size
min_side = min(width, height)
left = (width - min_side) // 2
top = (height - min_side) // 2
img = img.crop((left, top, left + min_side, top + min_side))
# 模糊
img = img.filter(ImageFilter.GaussianBlur(radius=blur_radius))
# 保存
output_path = os.path.join(output_dir, f"processed_{filename}")
img.save(output_path)
print(f"已处理: {filename}")
# 使用示例
batch_process('input_images', 'output_images', size=256, blur_radius=3)
这个函数里,我用thumbnail()而不是resize(),是因为thumbnail()会自动保持宽高比,不会变形。这是我在项目中踩坑后养成的习惯。
3.6 避坑指南
最后,分享几个我踩过的坑:
- 图像模式问题:有些滤镜不支持RGBA模式(带透明通道)。我遇到过
FIND_EDGES在RGBA图上报错。解决办法是先转成RGB:img.convert('RGB')。 - 大图处理:如果图片很大(比如4000x3000),直接
resize()会占用大量内存。我建议先thumbnail()缩小,再做其他操作。 - 格式兼容性:WebP格式在旧版Pillow中不支持。如果你要处理WebP,确保Pillow版本>=8.0。我有个项目就因为这个在客户机器上崩了。
好了,图像处理的基础操作就这些。说白了,Pillow就是让你用几行代码搞定图片的加载、保存、缩放、旋转、裁剪和滤镜。下一章我们会聊更高级的图像合成和绘制,到时候会用到ImageDraw和ImageEnhance,敬请期待。