🐍 第21课:图像处理

—— Pillow:Python 图像处理的瑞士军刀

🏆 图片缩放/裁剪/滤镜/水印全掌握
✅ Python验证通过

📌 本课目标

1️⃣ Pillow 基础操作

from PIL import Image

# 打开图片
img = Image.open("photo.jpg")
print(f"尺寸: {img.size}")       # (宽, 高)
print(f"格式: {img.format}")     # JPEG
print(f"模式: {img.mode}")       # RGB

# 保存为其他格式
img.save("photo.png")           # JPEG → PNG
img.save("photo.webp", quality=85) # WebP 压缩

# 缩放图片
# resize() 返回新图片,不修改原图
img_resized = img.resize((800, 600), Image.LANCZOS)
img_resized.save("photo_800x600.jpg")

# thumbnail() 原地缩放(不超限)
img_thumb = img.copy()
img_thumb.thumbnail((200, 200))  # 等比缩放到 200x200 内
img_thumb.save("thumbnail.jpg")

# 裁剪图片 (left, upper, right, lower)
cropped = img.crop((100, 50, 500, 400))
cropped.save("cropped.jpg")

# 旋转图片
rotated = img.rotate(90)            # 逆时针 90 度
rotated = img.rotate(45, expand=True) # 扩展画布容纳旋转

# 翻转
flipped_h = img.transpose(Image.FLIP_LEFT_RIGHT)
flipped_v = img.transpose(Image.FLIP_TOP_BOTTOM)
💡 resize vs thumbnail:resize() 返回新图,尺寸精确;thumbnail() 原地修改,等比缩放不超限。缩略图用 thumbnail,精确尺寸用 resize。

2️⃣ 滤镜效果

from PIL import Image, ImageFilter

img = Image.open("photo.jpg")

# 高斯模糊
blurred = img.filter(ImageFilter.GaussianBlur(radius=5))

# 盒模糊(更快)
box_blurred = img.filter(ImageFilter.BoxBlur(radius=3))

# 锐化
sharpened = img.filter(ImageFilter.SHARPEN)

# 边缘检测
edges = img.filter(ImageFilter.FIND_EDGES)

# 浮雕效果
embossed = img.filter(ImageFilter.EMBOSS)

# 轮廓提取
contoured = img.filter(ImageFilter.CONTOUR)

# 自定义卷积核
# 3x3 锐化核
kernel = ImageFilter.Kernel(
    size=(3, 3),
    kernel=[0, -1, 0,
           -1, 5, -1,
            0, -1, 0],
    scale=1,
    offset=0
)
custom_sharp = img.filter(kernel)

# USM 锐化(Unsharp Mask)
usm = img.filter(ImageFilter.UnsharpMask(
    radius=2, percent=150, threshold=3
))

3️⃣ ImageDraw 绘图与水印

from PIL import Image, ImageDraw, ImageFont

# 创建空白画布
canvas = Image.new("RGB", (800, 600), "#1a1a2e")
draw = ImageDraw.Draw(canvas)

# 绘制矩形
draw.rectangle([50, 50, 200, 150], fill="#34d399", outline="#ffffff", width=2)

# 绘制圆形
draw.ellipse([300, 50, 450, 200], fill="#7dd3fc")

# 绘制线条
draw.line([50, 300, 750, 300], fill="#fbbf24", width=3)

# 绘制文字
draw.text((100, 400), "Hello Pillow!", fill="#ffffff")

# 使用 TrueType 字体
try:
    font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 36)
    draw.text((100, 450), "Big Bold Text", fill="#34d399", font=font)
except:
    draw.text((100, 450), "Big Bold Text", fill="#34d399")

canvas.save("drawing.png")

# 图片水印
def add_watermark(image_path, text, output_path, opacity=128):
    """给图片添加文字水印"""
    img = Image.open(image_path).convert("RGBA")
    watermark = Image.new("RGBA", img.size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(watermark)
    
    try:
        font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 40)
    except:
        font = ImageFont.load_default()
    
    # 多行对角水印
    for y in range(0, img.height, 150):
        for x in range(0, img.width, 300):
            draw.text((x, y), text, fill=(255, 255, 255, opacity), font=font)
    
    result = Image.alpha_composite(img, watermark)
    result.convert("RGB").save(output_path)

4️⃣ 颜色与通道操作

from PIL import Image, ImageEnhance

img = Image.open("photo.jpg")

# 调整亮度
enhancer = ImageEnhance.Brightness(img)
brighter = enhancer.enhance(1.5)   # 1.5倍亮度
darker = enhancer.enhance(0.5)     # 0.5倍亮度

# 调整对比度
enhancer = ImageEnhance.Contrast(img)
high_contrast = enhancer.enhance(2.0)

# 调整饱和度
enhancer = ImageEnhance.Color(img)
vivid = enhancer.enhance(1.5)
grayscale = enhancer.enhance(0.0)  # 完全去色

# 调整锐度
enhancer = ImageEnhance.Sharpness(img)
sharper = enhancer.enhance(2.0)

# 灰度转换
gray = img.convert("L")    # 8位灰度

# 通道分离
r, g, b = img.split()
# 只保留红色通道
red_only = Image.merge("RGB", (r, g.point(lambda x: 0), b.point(lambda x: 0)))

5️⃣ 批量图片处理

import os
from pathlib import Path
from PIL import Image

def batch_resize(input_dir, output_dir, size=(800, 600)):
    """批量缩放图片"""
    input_path = Path(input_dir)
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)
    
    stats = {"success": 0, "failed": 0}
    
    for f in input_path.iterdir():
        if f.suffix.lower() not in (".jpg", ".jpeg", ".png", ".webp"):
            continue
        try:
            img = Image.open(f)
            img_resized = img.resize(size, Image.LANCZOS)
            img_resized.save(output_path / f.name, quality=90)
            stats["success"] += 1
            print(f"  ✅ {f.name}: {img.size} → {img_resized.size}")
        except Exception as e:
            stats["failed"] += 1
            print(f"  ❌ {f.name}: {e}")
    
    print(f"\\n批量缩放完成: 成功{stats['success']} 失败{stats['failed']}")

def batch_convert(input_dir, output_dir, target_format="WEBP"):
    """批量格式转换"""
    input_path = Path(input_dir)
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)
    
    for f in input_path.iterdir():
        if f.suffix.lower() not in (".jpg", ".jpeg", ".png"):
            continue
        try:
            img = Image.open(f)
            out_name = f.stem + ".webp"
            img.save(output_path / out_name, target_format, quality=85)
            original_size = f.stat().st_size
            new_size = (output_path / out_name).stat().st_size
            saved = (1 - new_size / original_size) * 100
            print(f"  ✅ {f.name} → {out_name} (节省{saved:.1f}%)")
        except Exception as e:
            print(f"  ❌ {f.name}: {e}")

def batch_watermark(input_dir, output_dir, text="COPYRIGHT"):
    """批量添加水印"""
    input_path = Path(input_dir)
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)
    
    for f in input_path.iterdir():
        if f.suffix.lower() not in (".jpg", ".jpeg", ".png"):
            continue
        try:
            add_watermark(str(f), text, str(output_path / f.name))
            print(f"  ✅ {f.name} 水印已添加")
        except Exception as e:
            print(f"  ❌ {f.name}: {e}")

6️⃣ 验证脚本

#!/usr/bin/env python3
"""第21课 图像处理验证"""
from PIL import Image, ImageDraw, ImageFilter, ImageEnhance
import tempfile
import os

def test_basic_operations():
    """基础操作测试"""
    with tempfile.TemporaryDirectory() as tmpdir:
        # 创建测试图片
        img = Image.new("RGB", (400, 300), "#ff0000")
        img.save(os.path.join(tmpdir, "test.jpg"))
        
        # 打开验证
        opened = Image.open(os.path.join(tmpdir, "test.jpg"))
        assert opened.size == (400, 300)
        
        # 缩放
        resized = opened.resize((200, 150), Image.LANCZOS)
        assert resized.size == (200, 150)
        
        # 裁剪
        cropped = opened.crop((50, 50, 150, 100))
        assert cropped.size == (100, 50)
        
        # 旋转
        rotated = opened.rotate(90)
        assert rotated.size == (400, 300)  # 默认不扩展
    print("✅ 基础操作测试通过")

def test_filters():
    """滤镜测试"""
    img = Image.new("RGB", (100, 100), "#ff0000")
    
    blurred = img.filter(ImageFilter.GaussianBlur(radius=2))
    assert blurred.size == (100, 100)
    
    sharpened = img.filter(ImageFilter.SHARPEN)
    assert sharpened.size == (100, 100)
    
    edges = img.filter(ImageFilter.FIND_EDGES)
    assert edges.size == (100, 100)
    print("✅ 滤镜测试通过")

def test_draw():
    """绘图测试"""
    img = Image.new("RGB", (200, 200), "#1a1a2e")
    draw = ImageDraw.Draw(img)
    draw.rectangle([10, 10, 100, 100], fill="#34d399")
    draw.ellipse([50, 50, 150, 150], fill="#7dd3fc")
    draw.text((10, 160), "Test", fill="#ffffff")
    
    # 验证绘制确实改变了像素
    pixel = img.getpixel((50, 50))  # 矩形区域内
    assert pixel != (26, 26, 46)  # 不是背景色
    print("✅ 绘图测试通过")

def test_enhance():
    """增强操作测试"""
    img = Image.new("RGB", (100, 100), "#ff0000")
    
    brighter = ImageEnhance.Brightness(img).enhance(1.5)
    assert brighter.size == (100, 100)
    
    gray = img.convert("L")
    assert gray.mode == "L"
    
    r, g, b = img.split()
    assert r.size == (100, 100)
    print("✅ 增强操作测试通过")

if __name__ == "__main__":
    test_basic_operations()
    test_filters()
    test_draw()
    test_enhance()
    print("\n🎉 第21课全部验证通过!")

🔑 本课要点

  1. Pillow——Python 图像处理的事实标准,支持打开/保存 30+ 格式
  2. Image.resize()——LANCZOS 高质量缩放,thumbnail() 原地缩放
  3. Image.filter()——模糊/锐化/边缘检测,自定义卷积核
  4. ImageDraw——在图像上绘制文字/形状/水印
  5. 批量处理——os.walk + Pillow 实现批量缩放/转换/水印