Skip to content
// 0x
0x04 // 工具设计

图片比例自动归类与裁剪系统设计

图片处理是很多项目中绕不开的痛点。本质上是一个”分类+裁剪”的 ETL 流程,但真正做好需要考虑安全性、隔离性、一致性。本文记录完整的设计思路和代码实现。

设计目标

建立一个稳定、可靠且具备等幂性的图片处理系统:

核心组件

系统包含三个核心组件:

1. normalize_image_names.py - 扩展名规范化

处理图片扩展名的大小写问题,.JPEG.jpg

def normalize_dir(directory, verbose=False):
    if not os.path.isdir(directory):
        return False
    
    for filename in files:
        new_ext = ext.lower()
        if new_ext == '.jpeg': new_ext = '.jpg'
        
        if ext != new_ext:
            # 备份 → 重命名 → 删除备份
            backup_path = old_path + '.bak'
            shutil.copy2(old_path, backup_path)
            os.rename(old_path, new_path)
            os.remove(backup_path)

使用备份机制保证重命名失败时可回滚。

2. classify_images_by_ratio.py - 分类主控

负责整体调度、扫描与物理移动:

3. resize_image__by_ratio.py - 裁剪工具

作为底层工具模块,不维护比例字典,仅根据传入参数执行裁剪:

def crop_and_save(src_path, dest_path, tw, th):
    with Image.open(src_path) as img:
        w, h = img.size
        
        if w * th > h * tw:
            new_w = round(h * tw / th)
            left = (w - new_w) // 2
            img = img.crop((left, 0, left + new_w, h))
        else:
            new_h = round(w * th / tw)
            top = (h - new_h) // 2
            img = img.crop((0, top, w, top + new_h))
        
        img.save(dest_path, quality=95, subsampling=0)

关键数学逻辑

为避免浮点数除法产生的精度误差,系统统一采用交叉乘法判定:

# 分类判定:找到 diff 最小的比例
diff = abs(w * th - h * tw)
if diff < min_diff:
    best_key = key

# 验证判定:允许 10 像素内的舍入误差
return abs(w * th - h * tw) <= 10

ETL 工作流程

1. Start: 清空 ~tmp/
2. Extract: 获取图片清单(白名单过滤扩展名)
3. Transform & Load:
   IF 比例 == 预设比例:
       MOVE 原图 → 目标目录/
   ELSE:
       COPY 原图 → ~tmp/副本
       调用裁剪工具
       IF 裁剪成功:
           MOVE ~tmp/副本 → 目标目录/
           REMOVE 原图
       ELSE:
           记录 Error
4. End: 清理 ~tmp/

核心原则:原图仅在操作成功后删除

异常处理

场景系统反应安全保障
处理中断原图未被删除重启后原图完整
同名冲突自动重命名防止覆盖
裁剪失败保留原图坏图不会进入分类目录
重复执行已无图片可处理等幂性保证

实现细节

白名单过滤

使用白名单而非黑名单,避免误处理非图片文件:

IMAGE_EXTS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.tiff'}

for filename in files:
    ext = os.path.splitext(filename)[1].lower()
    if ext not in IMAGE_EXTS:
        continue

事务性文件操作

保证操作原子性:

def safe_move(src, dest):
    """先复制再删除,确保事务安全"""
    shutil.copy2(src, dest)
    os.remove(src)

脚本相对路径

要求从工具目录运行,使用相对路径调用子脚本:

RESIZE_SCRIPT = './resize_image__by_ratio.py'

输出结构

images/
├── 0101/      # 1:1 比例
├── 1609/      # 16:9 比例
├── classify_*.log
└── ~tmp/      # 运行后清空

总结

这类工具的核心挑战不在于”能做什么”,而在于”如何安全地做”:

  1. 等幂性需要每一步都可验证、可重试
  2. 事务安全需要先备份再操作,失败可回滚
  3. 隔离处理需要沙盒环境,原数据不受影响

作为工具脚本,可维护性同样重要:日志记录、白名单过滤、详细错误信息都是为了让调试更轻松。


Share this post on:

Previous Post
本地开发 SSL 证书踩坑实录 — 通配符、SAN 与 Chrome 信任链
Next Post
构建自动化的 Build in Public 工作流