一、库的简介:图像处理的瑞士军刀
在数字时代,图像处理已经渗透到我们日常生活的方方面面。从社交媒体上的照片滤镜、电商平台的产品图片处理,到办公场景的文档扫描、医疗影像的分析,图像处理技术无处不在。Pillow作为Python生态中最受欢迎的图像处理库,为开发者提供了一套强大而简洁的工具集,让图像处理变得像操作文本文件一样简单。
Pillow是PIL(Python Imaging Library)的活跃分支和继承者,它支持数十种图像格式的读写,提供了丰富的图像处理功能:图像的缩放、裁剪、旋转、颜色转换、滤镜效果、文字水印、格式转换等。在实际生活中,Pillow的应用场景极其广泛:
-
社交媒体自动化:批量处理照片尺寸、添加滤镜、生成缩略图
-
电商运营:自动生成商品主图、添加促销标签、统一图片规格
-
办公自动化:扫描件优化、PDF转图片、文档水印添加
-
数据可视化:生成图表、添加标注、制作数据报告配图
-
安全领域:图片隐写、验证码生成、图像加密
-
教育应用:制作教学素材、批改扫描作业、生成习题配图
更重要的是,Pillow作为Python库,可以轻松集成到Web应用、桌面软件、自动化脚本中,为各类项目提供专业的图像处理能力。
二、安装Pillow
Pillow的安装非常简单,通过pip即可完成:
bash
# 安装核心库
pip install Pillow
# 如果需要处理特定格式(WebP、JPEG2000等),可能需要额外依赖
# Ubuntu/Debian
sudo apt-get install libjpeg-dev zlib1g-dev libwebp-dev
# macOS
brew install libjpeg zlib webp
# Windows(通常不需要额外操作)
验证安装:
python
from PIL import Image, ImageFilter, ImageDraw, ImageFont
print(f"Pillow版本: {Image.__version__}")
三、基本用法:四步掌握Pillow
1. 打开和显示图像
python
from PIL import Image
# 打开图像文件
img = Image.open('example.jpg')
print(f"图像格式: {img.format}")
print(f"图像尺寸: {img.size}")
print(f"图像模式: {img.mode}") # RGB、RGBA、L(灰度)等
# 显示图像(调用系统默认图片查看器)
img.show()
# 获取图像信息
exif = img._getexif() # 获取EXIF信息
if exif:
print("EXIF信息:", exif)
# 创建新图像
new_img = Image.new('RGB', (800, 600), color='white')
new_img = Image.new('RGBA', (400, 300), color=(255, 0, 0, 128)) # 半透明红色
2. 图像基本操作
python
from PIL import Image, ImageOps
def basic_operations():
# 打开图像
img = Image.open('example.jpg')
# 1. 调整大小
resized = img.resize((800, 600))
# 保持比例的缩略图
thumbnail = img.copy()
thumbnail.thumbnail((400, 300))
# 2. 裁剪
# 裁剪区域:(左, 上, 右, 下)
cropped = img.crop((100, 100, 500, 500))
# 3. 旋转和翻转
rotated = img.rotate(45, expand=True, fillcolor='white') # 旋转45度
flipped_h = img.transpose(Image.FLIP_LEFT_RIGHT) # 水平翻转
flipped_v = img.transpose(Image.FLIP_TOP_BOTTOM) # 垂直翻转
# 4. 颜色转换
grayscale = img.convert('L') # 灰度图
rgba = img.convert('RGBA') # 带透明通道
# 5. 图像增强
from PIL import ImageEnhance
# 亮度增强
enhancer = ImageEnhance.Brightness(img)
brighter = enhancer.enhance(1.5) # 亮度提高50%
# 对比度增强
enhancer = ImageEnhance.Contrast(img)
higher_contrast = enhancer.enhance(1.5)
# 色彩饱和度
enhancer = ImageEnhance.Color(img)
more_colorful = enhancer.enhance(1.5)
# 锐度
enhancer = ImageEnhance.Sharpness(img)
sharper = enhancer.enhance(2.0)
return {
'resized': resized,
'cropped': cropped,
'rotated': rotated,
'grayscale': grayscale
}
3. 图像滤镜和效果
python
from PIL import Image, ImageFilter
def filter_demo():
img = Image.open('example.jpg')
# 内置滤镜
blurred = img.filter(ImageFilter.BLUR) # 模糊
gaussian_blur = img.filter(ImageFilter.GaussianBlur(radius=2)) # 高斯模糊
contour = img.filter(ImageFilter.CONTOUR) # 轮廓
emboss = img.filter(ImageFilter.EMBOSS) # 浮雕
edge_enhance = img.filter(ImageFilter.EDGE_ENHANCE) # 边缘增强
find_edges = img.filter(ImageFilter.FIND_EDGES) # 边缘检测
sharpen = img.filter(ImageFilter.SHARPEN) # 锐化
smooth = img.filter(ImageFilter.SMOOTH) # 平滑
# 自定义卷积核
kernel = [
-1, -1, -1,
-1, 8, -1,
-1, -1, -1
]
custom_filter = img.filter(ImageFilter.Kernel((3, 3), kernel, scale=1))
# 中值滤波(去噪)
median = img.filter(ImageFilter.MedianFilter(size=3))
# 模式滤波
mode = img.filter(ImageFilter.ModeFilter(size=3))
# 保存结果
results = {
'original': img,
'blur': blurred,
'edges': find_edges,
'emboss': emboss
}
return results
4. 绘图和文字处理
python
from PIL import Image, ImageDraw, ImageFont
import os
def draw_demo():
# 创建空白图像
img = Image.new('RGB', (800, 400), color='white')
draw = ImageDraw.Draw(img)
# 绘制基本形状
# 线条
draw.line((50, 50, 750, 50), fill='black', width=2)
# 矩形
draw.rectangle((100, 100, 300, 200), outline='blue', width=3)
draw.rectangle((350, 100, 550, 200), fill='red', outline='black')
# 椭圆
draw.ellipse((100, 250, 300, 350), outline='green', width=2)
draw.ellipse((350, 250, 550, 350), fill='yellow')
# 多边形(三角形)
draw.polygon([(600, 250), (700, 150), (800, 250)], fill='purple')
# 圆弧
draw.arc((600, 300, 750, 380), start=0, end=180, fill='blue', width=3)
# 点
for i in range(100):
x = 50 + i * 5
y = 380
draw.point((x, y), fill='black')
# 绘制文字
try:
# 尝试加载系统字体
font_path = None
if os.name == 'nt': # Windows
font_path = "C:/Windows/Fonts/msyh.ttc"
elif os.name == 'posix': # macOS/Linux
possible_paths = [
"/System/Library/Fonts/PingFang.ttc", # macOS
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", # Linux
]
for path in possible_paths:
if os.path.exists(path):
font_path = path
break
if font_path:
font_large = ImageFont.truetype(font_path, 36)
font_medium = ImageFont.truetype(font_path, 24)
else:
font_large = ImageFont.load_default()
font_medium = ImageFont.load_default()
except:
font_large = ImageFont.load_default()
font_medium = ImageFont.load_default()
# 绘制文字
draw.text((50, 30), "Pillow 绘图示例", fill='black', font=font_large)
draw.text((50, 400), "各种图形和文字的绘制效果", fill='gray', font=font_medium)
# 带轮廓的文字
text = "Hello Pillow"
x, y = 200, 80
# 绘制轮廓
for offset in [(x-1,y-1), (x-1,y+1), (x+1,y-1), (x+1,y+1)]:
draw.text(offset, text, fill='black', font=font_medium)
# 绘制主体
draw.text((x, y), text, fill='yellow', font=font_medium)
return img
四、高级用法
1. 批量图像处理
python
from PIL import Image
import os
from pathlib import Path
class BatchImageProcessor:
def __init__(self, input_dir, output_dir):
self.input_dir = Path(input_dir)
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
self.supported_formats = {'.jpg', '.jpeg', '.png', '.bmp', '.gif', '.webp'}
def process_all(self, operations):
"""批量处理所有图片
operations: 操作字典,例如:
{
'resize': (800, 600),
'rotate': 90,
'format': 'PNG',
'quality': 85,
'watermark': 'logo.png'
}
"""
results = {
'success': [],
'failed': []
}
for img_path in self.input_dir.glob('*'):
if img_path.suffix.lower() not in self.supported_formats:
continue
try:
output_path = self.process_single(img_path, operations)
results['success'].append((str(img_path), str(output_path)))
except Exception as e:
results['failed'].append((str(img_path), str(e)))
return results
def process_single(self, img_path, operations):
img = Image.open(img_path)
# 调整大小
if 'resize' in operations:
img = img.resize(operations['resize'], Image.Resampling.LANCZOS)
# 缩略图(保持比例)
if 'thumbnail' in operations:
img.thumbnail(operations['thumbnail'], Image.Resampling.LANCZOS)
# 裁剪
if 'crop' in operations:
img = img.crop(operations['crop'])
# 旋转
if 'rotate' in operations:
img = img.rotate(operations['rotate'], expand=True)
# 滤镜
if 'filter' in operations:
filter_map = {
'blur': ImageFilter.BLUR,
'contour': ImageFilter.CONTOUR,
'emboss': ImageFilter.EMBOSS,
'edges': ImageFilter.FIND_EDGES
}
if operations['filter'] in filter_map:
img = img.filter(filter_map[operations['filter']])
# 添加水印
if 'watermark' in operations:
img = self.add_watermark(img, operations['watermark'])
# 设置输出路径
output_filename = f"{img_path.stem}_processed{img_path.suffix}"
output_path = self.output_dir / output_filename
# 保存
save_kwargs = {}
if 'quality' in operations:
save_kwargs['quality'] = operations['quality']
if 'format' in operations:
save_kwargs['format'] = operations['format']
img.save(output_path, **save_kwargs)
return output_path
def add_watermark(self, img, watermark_path):
"""添加水印"""
watermark = Image.open(watermark_path)
# 调整水印大小
w_width = int(img.width * 0.2) # 水印宽度为原图20%
w_height = int(watermark.height * w_width / watermark.width)
watermark = watermark.resize((w_width, w_height), Image.Resampling.LANCZOS)
# 如果水印没有透明通道,转换
if watermark.mode != 'RGBA':
watermark = watermark.convert('RGBA')
# 创建新图层
layer = Image.new('RGBA', img.size, (0,0,0,0))
position = (img.width - w_width - 10, img.height - w_height - 10) # 右下角
layer.paste(watermark, position, watermark)
# 合并
result = Image.alpha_composite(img.convert('RGBA'), layer)
return result.convert('RGB')
# 使用示例
processor = BatchImageProcessor('./photos', './processed')
results = processor.process_all({
'thumbnail': (800, 600),
'quality': 90,
'watermark': './logo.png',
'filter': 'edges'
})
print(f"成功处理: {len(results['success'])} 张")
print(f"失败: {len(results['failed'])} 张")
2. 验证码生成器
python
from PIL import Image, ImageDraw, ImageFont
import random
import string
import os
class CaptchaGenerator:
def __init__(self, width=300, height=100):
self.width = width
self.height = height
self.characters = string.ascii_uppercase + string.digits
self.noise_chars = string.ascii_letters + string.digits
def generate(self, length=6, save_path=None):
"""生成验证码"""
# 生成随机字符
text = ''.join(random.choices(self.characters, k=length))
# 创建画布
img = Image.new('RGB', (self.width, self.height), self.random_color(240, 255))
draw = ImageDraw.Draw(img)
# 添加干扰线条
self.add_lines(draw)
# 添加干扰点
self.add_noise(draw)
# 添加干扰字符
self.add_noise_chars(draw)
# 绘制验证码文字
self.draw_text(draw, text)
# 添加扭曲效果
img = self.distort(img)
if save_path:
img.save(save_path)
return img, text
def random_color(self, start, end):
"""生成随机颜色"""
return tuple(random.randint(start, end) for _ in range(3))
def add_lines(self, draw):
"""添加干扰线"""
for _ in range(random.randint(3, 6)):
x1 = random.randint(0, self.width)
y1 = random.randint(0, self.height)
x2 = random.randint(0, self.width)
y2 = random.randint(0, self.height)
width = random.randint(1, 2)
draw.line([(x1, y1), (x2, y2)], fill=self.random_color(150, 200), width=width)
def add_noise(self, draw):
"""添加噪点"""
for _ in range(random.randint(100, 200)):
x = random.randint(0, self.width - 1)
y = random.randint(0, self.height - 1)
draw.point((x, y), fill=self.random_color(0, 100))
def add_noise_chars(self, draw):
"""添加干扰字符"""
try:
font = ImageFont.truetype("arial.ttf", random.randint(15, 25))
except:
font = ImageFont.load_default()
for _ in range(random.randint(5, 10)):
char = random.choice(self.noise_chars)
x = random.randint(0, self.width - 30)
y = random.randint(0, self.height - 30)
draw.text((x, y), char, fill=self.random_color(180, 220), font=font)
def draw_text(self, draw, text):
"""绘制验证码文字"""
# 尝试加载不同字体
font_paths = []
if os.name == 'nt': # Windows
font_paths = [
"C:/Windows/Fonts/arial.ttf",
"C:/Windows/Fonts/verdana.ttf",
"C:/Windows/Fonts/times.ttf"
]
elif os.name == 'posix': # macOS/Linux
font_paths = [
"/System/Library/Fonts/Arial.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
]
font = None
for font_path in font_paths:
if os.path.exists(font_path):
try:
font = ImageFont.truetype(font_path, 40)
break
except:
continue
if not font:
font = ImageFont.load_default()
# 计算文字位置
text_width = sum(draw.textlength(char, font=font) for char in text)
x = (self.width - text_width) / 2
y = (self.height - 50) / 2
# 逐个绘制字符(增加变化)
current_x = x
for char in text:
# 随机旋转和偏移
char_img = Image.new('RGBA', (50, 50), (0,0,0,0))
char_draw = ImageDraw.Draw(char_img)
char_draw.text((5, 5), char, fill=self.random_color(0, 100), font=font)
char_img = char_img.rotate(random.randint(-15, 15), expand=1)
# 粘贴到主图
paste_x = int(current_x)
paste_y = int(y + random.randint(-5, 5))
img.paste(char_img, (paste_x, paste_y), char_img)
current_x += draw.textlength(char, font=font) + random.randint(5, 10)
def distort(self, img):
"""图像扭曲效果"""
import math
# 创建扭曲映射
x_shift = 0
y_shift = 0
# 正弦扭曲
distorted = Image.new('RGB', img.size)
for x in range(img.width):
for y in range(img.height):
# 计算偏移
x_offset = int(10 * math.sin(2 * math.pi * y / 30))
y_offset = int(5 * math.sin(2 * math.pi * x / 25))
new_x = min(max(x + x_offset, 0), img.width - 1)
new_y = min(max(y + y_offset, 0), img.height - 1)
distorted.putpixel((x, y), img.getpixel((new_x, new_y)))
return distorted
# 使用示例
captcha = CaptchaGenerator(width=400, height=150)
img, text = captcha.generate(save_path='captcha.png')
print(f"验证码文字: {text}")
3. 图像元数据管理
python
from PIL import Image, ImageOps
from PIL.ExifTags import TAGS, GPSTAGS
import os
from datetime import datetime
class ImageMetadataManager:
def __init__(self, image_path):
self.image_path = image_path
self.img = Image.open(image_path)
self.exif_data = self.get_exif_data()
def get_exif_data(self):
"""提取EXIF数据"""
exif = {}
try:
raw_exif = self.img._getexif()
if raw_exif:
for tag_id, value in raw_exif.items():
tag_name = TAGS.get(tag_id, tag_id)
# 处理GPS信息
if tag_name == 'GPSInfo':
gps_data = {}
for gps_id in value:
gps_name = GPSTAGS.get(gps_id, gps_id)
gps_data[gps_name] = value[gps_id]
exif[tag_name] = gps_data
else:
exif[tag_name] = value
except Exception as e:
print(f"读取EXIF失败: {e}")
return exif
def get_gps_coordinates(self):
"""获取GPS坐标"""
if 'GPSInfo' not in self.exif_data:
return None
gps = self.exif_data['GPSInfo']
def convert_to_degrees(value):
"""转换GPS坐标为十进制度数"""
d = float(value[0])
m = float(value[1])
s = float(value[2])
return d + (m / 60.0) + (s / 3600.0)
try:
lat = convert_to_degrees(gps[2]) # GPSLatitude
lon = convert_to_degrees(gps[4]) # GPSLongitude
# 根据参考方向确定正负
if gps[1] == 'S': # GPSLatitudeRef
lat = -lat
if gps[3] == 'W': # GPSLongitudeRef
lon = -lon
return (lat, lon)
except:
return None
def get_timestamp(self):
"""获取拍摄时间"""
if 'DateTimeOriginal' in self.exif_data:
return self.exif_data['DateTimeOriginal']
elif 'DateTime' in self.exif_data:
return self.exif_data['DateTime']
return None
def get_camera_info(self):
"""获取相机信息"""
camera_info = {}
fields = ['Make', 'Model', 'LensModel', 'FNumber',
'ExposureTime', 'ISOSpeedRatings', 'FocalLength']
for field in fields:
if field in self.exif_data:
camera_info[field] = self.exif_data[field]
return camera_info
def strip_metadata(self, save_path=None):
"""清除所有元数据"""
# 创建无元数据的新图像
data = list(self.img.getdata())
clean_img = Image.new(self.img.mode, self.img.size)
clean_img.putdata(data)
if save_path:
clean_img.save(save_path)
return clean_img
def add_metadata(self, metadata_dict, save_path=None):
"""添加自定义元数据"""
# 创建包含元数据的新图像
from PIL.PngImagePlugin import PngInfo
if self.img.format == 'PNG':
metadata = PngInfo()
for key, value in metadata_dict.items():
metadata.add_text(key, str(value))
if save_path:
self.img.save(save_path, pnginfo=metadata)
else:
# JPEG等格式可以使用EXIF
# 简化的实现,实际需要构建EXIF数据
if save_path:
self.img.save(save_path)
def print_report(self):
"""打印元数据报告"""
print("=" * 50)
print(f"文件: {os.path.basename(self.image_path)}")
print(f"尺寸: {self.img.size}")
print(f"格式: {self.img.format}")
print(f"模式: {self.img.mode}")
print("-" * 50)
if self.exif_data:
print("EXIF信息:")
# 相机信息
camera = self.get_camera_info()
if camera:
print(" 相机:")
for key, value in camera.items():
print(f" {key}: {value}")
# 拍摄时间
timestamp = self.get_timestamp()
if timestamp:
print(f" 拍摄时间: {timestamp}")
# GPS信息
gps = self.get_gps_coordinates()
if gps:
print(f" GPS坐标: {gps[0]:.6f}, {gps[1]:.6f}")
else:
print("无EXIF信息")
print("=" * 50)
# 使用示例
manager = ImageMetadataManager('photo.jpg')
manager.print_report()
# 清除元数据
manager.strip_metadata('photo_clean.jpg')
五、实际应用场景
场景一:电商产品图批量处理器
python
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import os
from pathlib import Path
import json
class EcommerceImageProcessor:
def __init__(self, config_path=None):
self.config = self.load_config(config_path) if config_path else self.default_config()
self.watermark = None
if self.config.get('watermark_path'):
try:
self.watermark = Image.open(self.config['watermark_path']).convert('RGBA')
except:
pass
def default_config(self):
return {
'target_sizes': {
'main': (800, 800), # 主图
'thumbnail': (200, 200), # 缩略图
'detail': (1200, 1200), # 详情页大图
'list': (400, 400) # 列表图
},
'watermark_path': 'logo.png',
'watermark_opacity': 128,
'background_color': '#FFFFFF',
'quality': 85,
'output_format': 'JPEG'
}
def load_config(self, path):
with open(path, 'r') as f:
return json.load(f)
def process_product_images(self, input_dir, output_dir, product_sku):
"""处理单个产品的所有图片"""
input_path = Path(input_dir)
output_path = Path(output_dir) / product_sku
output_path.mkdir(parents=True, exist_ok=True)
# 收集所有原始图片
images = []
for ext in ['*.jpg', '*.jpeg', '*.png']:
images.extend(input_path.glob(ext))
if not images:
print(f"警告: 未找到图片 - {input_dir}")
return
results = {}
for idx, img_path in enumerate(images, 1):
try:
img = Image.open(img_path)
# 为每个尺寸生成图片
for size_name, dimensions in self.config['target_sizes'].items():
output_file = output_path / f"{product_sku}_{idx}_{size_name}.{self.config['output_format'].lower()}"
processed = self.process_single_image(
img,
dimensions,
self.config
)
processed.save(
output_file,
quality=self.config['quality'],
optimize=True
)
if size_name not in results:
results[size_name] = []
results[size_name].append(str(output_file))
except Exception as e:
print(f"处理图片失败 {img_path}: {e}")
return results
def process_single_image(self, img, target_size, config):
"""处理单张图片"""
# 转换颜色模式
if img.mode != 'RGB':
img = img.convert('RGB')
# 创建白色背景
background = Image.new('RGB', target_size, config['background_color'])
# 保持比例调整大小
img.thumbnail(target_size, Image.Resampling.LANCZOS)
# 计算居中位置
x = (target_size[0] - img.width) // 2
y = (target_size[1] - img.height) // 2
# 粘贴图片
if img.mode == 'RGBA':
background.paste(img, (x, y), img)
else:
background.paste(img, (x, y))
# 添加水印
if self.watermark:
background = self.add_watermark(background)
# 添加产品标签(可选的)
if config.get('add_price_tag'):
background = self.add_price_tag(background, config)
return background
def add_watermark(self, img):
"""添加水印"""
if not self.watermark:
return img
# 调整水印大小
w_width = int(img.width * 0.15) # 水印宽度为原图15%
w_height = int(self.watermark.height * w_width / self.watermark.width)
watermark = self.watermark.resize((w_width, w_height), Image.Resampling.LANCZOS)
# 调整透明度
if watermark.mode == 'RGBA':
alpha = watermark.split()[3]
alpha = alpha.point(lambda p: p * self.config['watermark_opacity'] / 255)
watermark.putalpha(alpha)
# 创建新图层
layer = Image.new('RGBA', img.size, (0,0,0,0))
position = (img.width - w_width - 20, img.height - w_height - 20) # 右下角
layer.paste(watermark, position, watermark)
# 合并
result = Image.alpha_composite(img.convert('RGBA'), layer)
return result.convert('RGB')
def add_price_tag(self, img, config):
"""添加价格标签"""
draw = ImageDraw.Draw(img)
# 尝试加载字体
try:
font = ImageFont.truetype("arial.ttf", 36)
except:
font = ImageFont.load_default()
# 绘制标签背景
price = config.get('price', '¥99.99')
draw.rectangle([20, 20, 200, 80], fill='#FF0000')
draw.text((30, 30), price, fill='white', font=font)
return img
def create_image_set(self, images_info):
"""创建商品主图组合"""
# 创建四宫格组合图
grid_size = (2, 2) if len(images_info) >= 4 else (1, len(images_info))
cell_size = (400, 400)
composite = Image.new('RGB',
(cell_size[0] * grid_size[0], cell_size[1] * grid_size[1]),
'#FFFFFF')
for idx, (size_name, img_paths) in enumerate(images_info.items()):
if idx >= grid_size[0] * grid_size[1]:
break
if img_paths:
img = Image.open(img_paths[0])
img.thumbnail(cell_size, Image.Resampling.LANCZOS)
x = (idx % grid_size[0]) * cell_size[0] + (cell_size[0] - img.width) // 2
y = (idx // grid_size[0]) * cell_size[1] + (cell_size[1] - img.height) // 2
composite.paste(img, (x, y))
return composite
# 使用示例
processor = EcommerceImageProcessor()
processor.process_product_images(
'./raw_images/product123',
'./processed_images',
'SKU12345'
)
场景二:智能证件照生成器
python
from PIL import Image, ImageDraw, ImageFilter
import numpy as np
class IDPhotoGenerator:
def __init__(self):
# 标准证件照尺寸(英寸转像素,假设300DPI)
self.standard_sizes = {
'1inch': {'size': (295, 413), 'name': '一寸'}, # 25x35mm
'2inch': {'size': (413, 579), 'name': '二寸'}, # 35x49mm
'passport': {'size': (390, 510), 'name': '护照'}, # 33x48mm
'visa': {'size': (510, 510), 'name': '签证'} # 51x51mm
}
# 常见背景色
self.background_colors = {
'white': (255, 255, 255),
'blue': (0, 112, 192),
'red': (237, 28, 36),
'gray': (128, 128, 128)
}
def remove_background(self, img, tolerance=30):
"""简单背景去除(基于颜色阈值)"""
if img.mode != 'RGBA':
img = img.convert('RGBA')
# 转换为numpy数组
data = np.array(img)
# 假设背景是图片边缘的颜色
bg_color = data[0, 0] # 左上角颜色
# 创建掩码
r, g, b, a = data[:,:,0], data[:,:,1], data[:,:,2], data[:,:,3]
# 颜色距离
distance = np.sqrt(
(r - bg_color[0])**2 +
(g - bg_color[1])**2 +
(b - bg_color[2])**2
)
# 标记背景区域
mask = distance < tolerance
# 设置透明度
data[mask, 3] = 0
return Image.fromarray(data)
def change_background(self, img, new_bg_color):
"""更换背景色"""
if img.mode != 'RGBA':
img = img.convert('RGBA')
# 创建新背景
background = Image.new('RGBA', img.size, new_bg_color)
# 合成
result = Image.alpha_composite(background, img)
return result.convert('RGB')
def detect_face(self, img):
"""简单人脸检测(基于比例)"""
# 实际项目中可以集成OpenCV的人脸检测
# 这里使用简化版:假设人脸在图片的1/3到2/3高度处
width, height = img.size
# 预估人脸区域(占图片高度的1/3)
face_height = height // 3
face_y = height // 3
# 预估人脸宽度(占图片宽度的1/2)
face_width = width // 2
face_x = (width - face_width) // 2
return (face_x, face_y, face_x + face_width, face_y + face_height)
def auto_crop_face(self, img):
"""自动裁剪到人脸区域"""
face_bbox = self.detect_face(img)
# 扩展裁剪区域
margin = 0.2 # 周围保留20%空间
width = face_bbox[2] - face_bbox[0]
height = face_bbox[3] - face_bbox[1]
new_left = max(0, face_bbox[0] - width * margin)
new_top = max(0, face_bbox[1] - height * margin)
new_right = min(img.width, face_bbox[2] + width * margin)
new_bottom = min(img.height, face_bbox[3] + height * margin)
return img.crop((new_left, new_top, new_right, new_bottom))
def standardize(self, img, size_key='1inch', bg_color='white'):
"""标准化证件照"""
# 获取目标尺寸
target_size = self.standard_sizes[size_key]['size']
bg_color_rgb = self.background_colors[bg_color]
# 移除背景
img_nobg = self.remove_background(img)
# 裁剪人脸
img_cropped = self.auto_crop_face(img_nobg)
# 调整大小(保持比例)
img_cropped.thumbnail(target_size, Image.Resampling.LANCZOS)
# 创建标准尺寸的背景
result = Image.new('RGB', target_size, bg_color_rgb)
# 居中放置
x = (target_size[0] - img_cropped.width) // 2
y = (target_size[1] - img_cropped.height) // 2
if img_cropped.mode == 'RGBA':
result.paste(img_cropped, (x, y), img_cropped)
else:
result.paste(img_cropped, (x, y))
return result
def create_layout(self, img, size_key='1inch', bg_color='white', cols=4, rows=5):
"""创建排版好的证件照排版"""
# 获取单张证件照
single = self.standardize(img, size_key, bg_color)
# 计算排版尺寸
single_width, single_height = single.size
layout_width = single_width * cols
layout_height = single_height * rows
# 创建排版画布
layout = Image.new('RGB', (layout_width, layout_height), 'white')
# 填充照片
for row in range(rows):
for col in range(cols):
x = col * single_width
y = row * single_height
layout.paste(single, (x, y))
# 添加裁剪线
draw = ImageDraw.Draw(layout)
line_color = (200, 200, 200)
# 垂直线
for col in range(1, cols):
x = col * single_width
draw.line([(x, 0), (x, layout_height)], fill=line_color, width=1)
# 水平线
for row in range(1, rows):
y = row * single_height
draw.line([(0, y), (layout_width, y)], fill=line_color, width=1)
return layout
# 使用示例
generator = IDPhotoGenerator()
img = Image.open('portrait.jpg')
id_photo = generator.standardize(img, '1inch', 'blue')
id_photo.save('standard_id_photo.jpg')
# 生成排版照片
layout = generator.create_layout(img, '1inch', 'white', cols=4, rows=5)
layout.save('id_photo_layout.jpg')
六、结尾与互动
Pillow作为Python图像处理的标准库,以其简洁的API和强大的功能,为无数开发者和项目提供了可靠的图像处理能力。从本文的介绍中,我们不仅学习了Pillow的基础操作和高级技巧,更重要的是看到了它在真实场景中的应用价值。无论是电商平台的批量图片处理、网站验证码的生成,还是个人证件照的制作,Pillow都能以优雅的方式解决复杂的图像处理需求。
图像处理是一门既古老又年轻的技艺,它在计算机视觉、人工智能等前沿领域中焕发着新的生机。而Pillow作为这一领域的基础工具,它的价值不仅在于能完成具体任务,更在于为我们打开了探索更广阔图像处理世界的大门。当你掌握了Pillow后,可以进一步学习OpenCV、scikit-image等更专业的库,甚至可以结合深度学习框架,构建智能图像识别系统。
现在,我想听听你的想法:你在工作中遇到过哪些有趣的图像处理需求?是用Pillow解决的,还是尝试了其他工具?有没有什么独到的技巧或经验想要分享?或者,你是否有一个创意,想要用图像处理来实现?欢迎在评论区留言交流,也许你的分享会启发其他开发者,碰撞出更多精彩的解决方案。如果你在使用Pillow时遇到任何问题,也欢迎提出来,我们一起探讨解决之道!