pillow,一个实用的 Python 库!

一、库的简介:图像处理的瑞士军刀

在数字时代,图像处理已经渗透到我们日常生活的方方面面。从社交媒体上的照片滤镜、电商平台的产品图片处理,到办公场景的文档扫描、医疗影像的分析,图像处理技术无处不在。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时遇到任何问题,也欢迎提出来,我们一起探讨解决之道!

相关推荐
A懿轩A1 小时前
【Java 基础编程】Java 异常处理保姆级教程:try-catch-finally、throw/throws、自定义异常
java·开发语言·python
追求源于热爱!2 小时前
记10,Gradio介绍
python
黎雁·泠崖2 小时前
Java 包装类:基本类型与引用类型的桥梁详解
java·开发语言
破晓之翼2 小时前
Skill原理及国内大模型实践
人工智能·python
IT管理圈2 小时前
Cursor Rules 实战指南—让AI按你的规矩写代码
python
Java后端的Ai之路2 小时前
微调模型成本太高,用RAG技术,低成本实现AI升级
开发语言·人工智能·python·rag·ai升级
2401_876907522 小时前
TYPE-C插拔力过大原因与解决方法
c语言·开发语言
喵手2 小时前
Python爬虫实战:从零构建书籍价格情报数据库(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·零基础python爬虫教学·csv导出·构建书籍价格情报·书籍价格采集
勾股导航2 小时前
蚁群优化算法
人工智能·pytorch·python