ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用Python+Pillow实现稿件批量处理与自动化预览图生成

用Python+Pillow实现稿件批量处理与自动化预览图生成 做稿件类项目时最怕的不是画得慢而是文件散落、版本混乱、导出格式不统一。尤其是像“三张摸鱼大头更新”这种看似轻松的整活稿件一旦需要连续出多张预览、多轮修改和多端使用手工整理很快就会变成比画画更耗时的事。这篇文章不聊具体的审美和画法而是从工程效率出发整理一套可复用的稿件过程管理方案用文件夹规范管理原始文件用 Python Pillow 写批量处理脚本把尺寸调整、透明通道处理、预览图生成和版本归档串成一条自动化流水线。不管你是自由插画师、动画工作室的资产整理岗还是业余做周边授权这套思路都能直接照搬。读完你会掌握稿件目录如何按阶段拆分避免反复横跳。如何用 Pillow 批量统一画布尺寸并正确处理透明背景。如何一键生成九宫格预览图方便发群、发客户、发平台。常见图像批处理报错怎么排查以及生产环境下的安全注意事项。1. 背景与核心概念1.1 什么是“稿件过程”自动化“稿件过程”在绘画/设计圈里通常指从草图到成品的过程管理。对于个人创作者来说这个过程往往是手动的一张一张在绘画软件里改尺寸、导出、重命名、发预览。一旦稿件数量到了“三张”“五张”“十张”手工操作不仅慢而且很容易出现“导出到一半发现画布大小不一致”“透明底变成黑底”“文件名全是最终版2修订3”这类问题。把这套过程拆开看其实就是四个固定动作把原始绘制文件归档。按统一规则生成成品图。生成一张可供快速预览的拼图。保留历史版本方便回滚。这四个动作完全可以用脚本自动化。这也是本文想表达的核心观点稿件是作品但“稿件过程”是工程问题工程问题就应该用工程手段解决。1.2 为什么要用 Python 来做很多绘画工作流里已经内置了“批处理”功能比如 Photoshop 的批量导出、Clip Studio Paint 的素材管理。但这些工具解决不了跨软件、跨平台的协作问题。而 Python 作为胶水语言有下面几个明显优势跨平台Windows、macOS、Linux 都能跑。生态成熟Pillow 是 Python 图像处理的事实标准库。容易接入版本控制脚本和配置可以放进 Git 仓库和稿件一起管理。可重复执行同一套脚本不管未来是 3 张还是 30 张输入一次就能跑完。当然这里不是说脚本要替代绘画本身而是把绘画流程中那些重复、机械、容易出错的环节抽出来交给计算机处理。1.3 应用场景这套方案适用的场景很广给一套角色头像做统一的尺寸和格式导出。给多张插画生成缩略图用于作品集页面。给稿件加水印后整理发布版。配合 Git 或者云盘做版本回滚和归档。如果你家里有闲下来的旧电脑甚至可以把这个脚本挂到 NAS 或者服务器上形成一套最小可用的“稿件资产管理系统”。2. 环境准备与版本说明2.1 软件环境本文示例以 Python 3.10 环境为主Pillow 使用 10.x 版本。原因很简单Pillow 在 10.0 之后对Image.LANCZOS这类重采样常量的用法进行了规范化老项目中常用的Image.ANTIALIAS已经被移除如果照抄旧教程很容易报错。版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。如果你用的是更低的 Python 版本请优先考虑升级如果由于项目限制不能升级可以把示例中的Image.LANCZOS替换成旧版的Image.ANTIALIAS。软件/库版本说明Python3.93.10 最稳妥Pillow10.x图像处理核心库Windows / macOS / Linux任意脚本跨平台VS Code任意推荐便于调试2.2 安装依赖建议先创建一个虚拟环境避免污染系统级 Pythonpython -m venv venvWindows 下激活虚拟环境venv\Scripts\activatemacOS / Linux 下激活虚拟环境source venv/bin/activate然后安装 Pillowpip install Pillow10,11安装完成后可以用下面这行命令确认版本python -c import PIL; print(PIL.__version__)如果输出类似10.4.0这样的版本号说明环境已经就绪。如果你的环境中 Pillow 版本更高通常也可以在稍加测试后直接使用。3. 核心工作流与自动化思路3.1 素材目录设计很多游戏公司和设计团队会对素材目录做严格约定因为文件数量一旦上去找文件的时间成本会远超整理文件的时间成本。这里采用的目录结构如下project_root/ ├── input/ # 原始绘制文件 │ ├── 角色A_v1.png │ ├── 角色A_v2.png │ ├── 角色B_v1.png │ └── 角色C_v1.png ├── output/ # 脚本导出目录 │ ├── final/ # 成品图 │ ├── preview/ # 总览预览图 │ └── archive/ # 历史版本归档 ├── scripts/ │ ├── batch_process.py # 主处理脚本 │ └── check_assets.py # 辅助检查脚本 ├── config.json # 项目配置 └── requirements.txt # Python 依赖设计这套目录时遵循了最基本的“读写分离”思想input/是只读目录永远不要在里面直接改名或覆盖原始文件。output/是每次运行脚本后重新生成的目录可以被删除、重建。archive/存的是带时间戳的旧版本不参与日常处理。这样设计后即使中间哪一步操作失误也不会破坏原始稿件。3.2 统一画布尺寸插画软件里可以按比例导出任意尺寸但不同平台要求的尺寸可能不同。比如头像要求 1:1常见 512×512、1024×1024。宽幅封面要求 16:9。手机壁纸要求 9:16。如果每次都靠绘画软件手工导出太容易出错了。批处理脚本里推荐使用ImageOps.fit()做“裁剪式缩放”它能自动把图像裁成目标宽高比再统一缩放到目标大小比单纯的resize()更适合头像类素材。3.3 透明通道处理“透明底变黑底”是稿件批处理里最经典的问题。实际上JPEG 格式根本不支持透明通道如果直接把 RGBA 模式的 PNG 保存为 JPG透明区域会被填充成黑色。正确的做法分成两步先判断图像的模式是不是RGBA。如果带透明通道把它贴到指定的背景色上再统一保存。文中代码会展示怎么用RGB背景画布加alpha通道合成这比直接调用convert(RGB)更能控制最终效果。3.4 生成预览拼图发稿过程中预览图很重要。自己看是一张一张看但发群里、发甲方、发平台往往需要一张包含多张作品的“九宫格”或“横排总览”。手工在绘画软件里拼图不仅慢而且拼出来格式乱七八糟。脚本里可以做一个简单的拼图函数把所有处理好的成品图按固定行数和列数拼到一张大白底上每格 512×512生成一张 JPG 总览图。这个函数在后面的完整案例中会直接给出。3.5 试运行模式脚本再好也怕误操作。一个非常值得养成的习惯是在批量处理前用--dry-run参数先模拟跑一遍只看清单不执行。这也是我写脚本时的默认保留项如果你要用在真实项目中建议也把 dry-run 保留住。4. 完整实战案例三个“摸鱼大头”的批量处理流程下面我们把上面说的思路串起来用一套完整脚本处理“三张摸鱼大头”这类稿件。假设手上有三张尺寸不一、格式不一的大头原稿最终要求全部导出为 1024×1024 的 PNG并生成一张预览总览图。4.1 创建项目结构首先在任意工作目录下创建项目文件夹。可以在终端里直接执行mkdir -p project_root/{input,output/final,output/preview,output/archive,scripts}如果你的终端不支持这种花括号展开也可以手动创建目录。最后在input/下放入原始稿件例如input/ ├── 角色A_v1.png ├── 角色B_v1.jpg └── 角色C_v1.webp注意这里故意用了三种不同格式作为输入脚本需要兼容它们。4.2 添加依赖文件创建requirements.txtPillow10,11然后在虚拟环境中执行pip install -r requirements.txt4.3 编写配置文件 config.json创建config.json{ input_dir: input, output_dir: output, target_size: [1024, 1024], background: [255, 255, 255], scale_mode: fit }配置说明input_dir原始文件所在目录相对于项目根目录。output_dir导出目录脚本会自动创建。target_size目标尺寸顺序是[宽, 高]这里表示 1024×1024。background透明区域的填充背景色。[255, 255, 255]是纯白色。scale_mode缩放模式fit表示裁剪适配后续还可以扩展为thumbnail等模式。4.4 编写主处理脚本创建scripts/batch_process.pyimport argparse import json from pathlib import Path from PIL import Image, ImageOps, UnidentifiedImageError BASE_DIR Path(__file__).resolve().parent.parent def load_config(config_pathNone): cfg_path Path(config_path) if config_path else BASE_DIR / config.json with open(cfg_path, r, encodingutf-8) as f: return json.load(f) def find_images(source_dir, extensions(.png, .jpg, .jpeg, .webp)): source Path(source_dir) if not source.exists(): raise FileNotFoundError(f输入目录不存在: {source}) images [p for p in source.rglob(*) if p.suffix.lower() in extensions] return sorted(images) def convert_and_flatten(img, background(255, 255, 255)): 把图像统一为 RGB 模式并处理透明通道。 if img.mode in (RGBA, LA): alpha img.getchannel(A) rgb_img img.convert(RGB) canvas Image.new(RGB, img.size, background) canvas.paste(rgb_img, maskalpha) return canvas return img.convert(RGB) def fit_to_size(img, target_size(1024, 1024)): 裁剪并缩放图像到目标尺寸。 return ImageOps.fit( img, target_size, methodImage.LANCZOS, centering(0.5, 0.5), ) def save_image(img, output_path): output_path.parent.mkdir(parentsTrue, exist_okTrue) img.save(output_path, quality92) print(f处理完成: {output_path}) def build_contact_sheet(image_paths, output_path, cols3, thumb_size(512, 512)): 把多张图片拼成一张总览图。 if not image_paths: return rows (len(image_paths) cols - 1) // cols cell_w, cell_h thumb_size sheet_w cols * cell_w sheet_h rows * cell_h sheet Image.new(RGB, (sheet_w, sheet_h), (240, 240, 240)) for idx, img_path in enumerate(image_paths): img Image.open(img_path) img ImageOps.fit(img, thumb_size, methodImage.LANCZOS, centering(0.5, 0.5)) x (idx % cols) * cell_w y (idx // cols) * cell_h sheet.paste(img, (x, y)) sheet.save(output_path, quality92) print(f预览图生成: {output_path}) def process(config, dry_runFalse): input_dir BASE_DIR / config[input_dir] output_dir BASE_DIR / config[output_dir] target_size tuple(config[target_size]) background tuple(config[background]) images find_images(input_dir) if not images: print(未发现可处理的图片文件。) return if dry_run: print([Dry Run] 以下文件将被处理) for img in images: print(f {img}) print(f[Dry Run] 共 {len(images)} 张图片) return final_dir output_dir / final preview_dir output_dir / preview final_dir.mkdir(parentsTrue, exist_okTrue) preview_dir.mkdir(parentsTrue, exist_okTrue) for img_path in images: try: img Image.open(img_path) img convert_and_flatten(img, background) img fit_to_size(img, target_size) out_name f{img_path.stem}_{target_size[0]}x{target_size[1]}.png save_image(img, final_dir / out_name) except UnidentifiedImageError: print(f跳过无法识别的文件: {img_path}) except OSError as e: print(f处理失败: {img_path}, 错误: {e}) processed_images sorted(final_dir.glob(*.png)) contact_sheet_path output_dir / preview / contact_sheet.jpg build_contact_sheet(processed_images, contact_sheet_path) def main(): parser argparse.ArgumentParser(description稿件批量处理工具) parser.add_argument(--config, defaultNone, help自定义配置文件路径) parser.add_argument(--dry-run, actionstore_true, help只打印处理清单不实际执行) args parser.parse_args() config load_config(args.config) process(config, dry_runargs.dry_run) if __name__ __main__: main()这段脚本的核心逻辑分成四块find_images()递归查找输入目录下的所有图片。convert_and_flatten()处理透明通道把 RGBA 图贴到背景色上。fit_to_size()统一裁剪缩放尺寸。build_contact_sheet()把处理好的成品图拼成总览图。脚本里用到了Path类型所以即使文件夹路径包含中文在 Python 3.9 下也能正常工作。不过 Windows 终端里的编码有时会影响print()输出如果出现中文乱码可以在运行前设置set PYTHONUTF814.5 运行与验证先执行试运行python scripts/batch_process.py --dry-run预期输出类似于[Dry Run] 以下文件将被处理 project_root/input/角色A_v1.png project_root/input/角色B_v1.jpg project_root/input/角色C_v1.webp [Dry Run] 共 3 张图片确认文件清单无误后正式执行python scripts/batch_process.py执行完查看output/目录output/ ├── final/ │ ├── 角色A_v1_1024x1024.png │ ├── 角色B_v1_1024x1024.png │ └── 角色C_v1_1024x1024.png └── preview/ └── contact_sheet.jpg打开preview/contact_sheet.jpg可以看到三张图被整齐拼在了一张白底图上。如果只有三张默认用一行三列排列后续如果加入更多输入图片会自动按两行、三行往下排。4.6 结果说明到这里三张“摸鱼大头”的稿件过程就闭环了原始稿件还在input/里没有被动过。成品图统一变成了 1024×1024 的 PNG透明通道也按白底处理了。总览预览图自动生成可以直接发给同事或朋友预览。之后如果某一轮修改后重新导出只需要再次执行同一个脚本原来的输出会被覆盖全部成品图和预览图都会同步更新。如果不想覆盖还可以再配合一个归档脚本把上一轮结果先复制到archive/里。5. 常见问题与排查思路5.1 问题现象排查表问题现象常见原因解决思路提示FileNotFoundError: 输入目录不存在当前工作目录和脚本预期的目录不一致确认项目根目录下存在input/并且脚本从项目根目录运行提示UnidentifiedImageError文件不是有效图片或者是损坏的 PSD把 PSD 先用绘画软件导出成 PNG/PSB或者跳过该文件透明区域变成黑色直接把带 Alpha 的 PNG 保存成了 JPG使用convert_and_flatten()先贴到背景色上再保存生成的图片被拉伸变形直接用了resize()而不是ImageOps.fit()改用fit()做等比例裁剪缩放图片边缘有白边图片本身带有白色描边或者原图比例差异过大在绘画软件中先裁好安全边距或调整centering参数中文文件名显示乱码Windows 终端编码不是 UTF-8运行前设置PYTHONUTF81运行时报AttributeError: module PIL.Image has no attribute ANTIALIASPillow 10 移除了旧写法统一使用Image.LANCZOS5.2 怎么避免类似问题最容易踩的坑是“临时手动导出”和“脚本导出”混用。一旦决定用脚本就要所有导出都走脚本不要在中间插入手工操作。手工操作一次两次没问题但次数一多很容易产生“为什么这张图的尺寸不对”的疑问。其次给原始文件取一个好名字。文件名建议遵循语义_版本的结构例如角色A_v1.png角色A_v2.png角色B_v1.png版本号里的v表示 version后面的数字每次修改加一。不要让文件名出现最终版、新新最终版这类没法排序的命名。5.3 如果脚本处理到一半失败怎么办这套脚本的处理逻辑是单张图片处理失败时会捕获异常并继续处理下一张。因此即使输入目录里混进了一个损坏文件也不会让整个任务中断。但要注意如果失败发生在图片读取阶段那么输出目录里不会生成对应文件如果失败发生在保存阶段可能会留下一个半成品。最稳妥的做法是跑完脚本后检查日志确认所有需要处理的文件都出现在final/目录里。6. 最佳实践与工程建议6.1 目录规范是自动化前提自动化最怕的就是“没有规则”。如果你的原始文件、导出文件、参考素材全部堆在一个文件夹里那么脚本再厉害也帮不上忙。建议最少做到下面三条原图和导出图严格分目录。文件名必须带版本号。归档目录只按时间存放不手工改名。这三条规则看起来简单但只要坚持后面任何自动化工具都能轻松接入。6.2 引入 Git 做版本管理绘画过程中原始稿件可以使用 Git 做版本管理。虽然 Git 对二进制文件不太友好但通过 Git LFSLarge File Storage扩展可以处理大文件。建议在项目根目录执行git init然后维护一个合理的.gitignore把venv/和output/忽略掉venv/ __pycache__/ output/ .DS_Store原始稿件input/可以和代码一起入库。每次修改稿件提交一次之后就可以用git checkout回滚到任意历史版本。6.3 扩展成自动加水印批量处理里非常实用的一项扩展是加水印。你可以参考build_contact_sheet()里的贴图逻辑用ImageDraw在成品图角落绘制透明文字或 Logofrom PIL import ImageDraw def add_watermark(img, textDRAFT, opacity128): overlay img.convert(RGBA) draw ImageDraw.Draw(overlay) text_layer Image.new(RGBA, img.size, (0, 0, 0, 0)) draw_text ImageDraw.Draw(text_layer) draw_text.text((20, 20), text, fill(255, 255, 255, opacity)) result Image.alpha_composite(overlay, text_layer) return result这个函数可以接在主处理流程后面。如果遇到中文文本字体缺失记得在 Windows 下指定C:/Windows/Fonts/msyh.ttc或 macOS 下的苹方字体路径。6.4 安全与权限提醒如果这套稿件流程要放到团队共享目录或服务器上运行需要注意几点脚本里避免任何shutil.rmtree直接删除整个目录的操作。如果确实要做归档清理先使用 dry-run 模式看文件清单。不要在共享目录里用 root/admin 权限直接运行脚本建议创建一个只有读取input/和写入output/权限的专用账号。对output/目录做定时清理或备份防止磁盘写满。处理生产环境稿件时永远保留一份原始稿件在第三方位置网盘、NAS 或 Git 远端。6.5 从脚本进化到“资产管理”如果未来稿件规模持续扩大可以考虑把脚本升级为一个小工具用 PySide6 或 Tkinter 做 GUI拖拽图片即可执行。用 SQLite 记录每次导出的文件名、尺寸、时间和标签。把脚本发布为命令行工具接入持续集成流水线每次有文件变更自动构建预览。但也不要过度设计。个人稿件项目先做到“一条命令处理完”就已经超过绝大部分手工整理流程了。7. 总结与学习路线本文从“三张摸鱼大头更新”这个最平常的稿件场景出发梳理了一套完整的稿件过程自动化方案。核心收获有三点目录规范化优先于代码编写没有清晰的input/、output/约定任何脚本都容易失控。用 Pillow 的ImageOps.fit()和透明通道处理可以把最常见的尺寸和底图问题一次性解决。dry-run 试运行模式是批量操作的安全底线尤其是未来要扩展到团队场景时这一步能避免大量误操作。接下来你可以继续做三件事一是把脚本扩展成带水印的发布版工具二是用 Git 把整个项目纳入版本管理三是尝试把脚本接入到批处理流水线中让每次出图后自动生成预览图。我个人更推荐从第二件事开始做。版本管理是所有自动化流程的地基先把稿件的历史记录管好再谈批量处理顺序不要反。如果本文对你有帮助可以考虑收藏备用。下次再遇到“开稿一时爽整理火葬场”的稿件项目试着先写一个小脚本把重复劳动交给机器。
返回列表