Python + Pillow 实战:开发一个图片批量格式转换工具

Python + Pillow 实战:开发一个图片批量格式转换工具

在日常办公、数据处理、网站开发和图像处理学习中,经常会遇到图片格式转换需求,例如:

  • 将 JPG 图片批量转换为 PNG
  • 将 PNG 图片批量转换为 JPG
  • 压缩图片体积,方便上传或传输
  • 批量调整图片尺寸
  • 处理整个目录下的图片文件

如果图片数量很少,可以使用图像编辑软件手动转换。但当图片数量达到几十张、几百张甚至更多时,手动操作就非常低效。Python 的 Pillow 库可以很好地解决这个问题。

本文面向 Python 图像处理入门者,使用 Pillow 实现一个简易的图片批量格式转换工具。

一、Pillow 是什么

Pillow 是 Python 中最常用的图像处理库之一,它是 PIL 的现代维护版本。

PIL 的全称是 Python Imaging Library,但原始 PIL 已经停止维护。现在实际项目中通常安装和使用的是 Pillow,不过导入时仍然使用:

python 复制代码
from PIL import Image

Pillow 支持常见图像处理操作,例如:

  • 打开图片
  • 保存图片
  • 图片格式转换
  • 图片压缩
  • 图片缩放
  • 图片裁剪
  • 图片旋转
  • 添加水印
  • 获取图片尺寸和模式

对于 Python 图像处理入门者来说,Pillow 是非常适合练手的库。

二、Pillow 安装

使用 pip 安装:

bash 复制代码
pip install pillow

如果使用的是国内网络环境,也可以指定镜像源:

bash 复制代码
pip install pillow -i https://pypi.tuna.tsinghua.edu.cn/simple

验证是否安装成功:

python 复制代码
from PIL import Image

print(Image.__version__)

如果可以正常输出版本号,说明 Pillow 安装成功。

三、打开和查看图片信息

在做格式转换之前,先看一下如何读取图片基本信息。

python 复制代码
from PIL import Image

image_path = "demo.jpg"

with Image.open(image_path) as img:
    print(f"图片格式:{img.format}")
    print(f"图片尺寸:{img.size}")
    print(f"图片模式:{img.mode}")

常见的图片模式包括:

  • RGB:常见彩色图片模式
  • RGBA:带透明通道的图片模式
  • L:灰度图模式
  • P:调色板模式

理解图片模式很重要,尤其是在 PNG 转 JPG 时。因为 JPG 不支持透明通道,如果直接保存 RGBA 图片为 JPG,可能会报错。

四、JPG 转 PNG

JPG 转 PNG 的核心逻辑非常简单:打开 JPG 文件,然后保存为 PNG 文件。

python 复制代码
from PIL import Image

input_path = "demo.jpg"
output_path = "demo.png"

with Image.open(input_path) as img:
    img.save(output_path, "PNG")

print("JPG 转 PNG 完成")

如果希望统一转换为 RGB 模式,可以写成:

python 复制代码
from PIL import Image

with Image.open("demo.jpg") as img:
    img = img.convert("RGB")
    img.save("demo.png", "PNG")

PNG 是无损格式,适合保存截图、图标、透明背景图片等。但 PNG 文件体积通常比 JPG 更大。

五、PNG 转 JPG

PNG 转 JPG 时,需要特别注意透明通道。

JPG 不支持透明背景,如果 PNG 是 RGBA 模式,需要先转换为 RGB

python 复制代码
from PIL import Image

input_path = "demo.png"
output_path = "demo.jpg"

with Image.open(input_path) as img:
    img = img.convert("RGB")
    img.save(output_path, "JPEG")

print("PNG 转 JPG 完成")

如果 PNG 图片带透明背景,直接 convert("RGB") 可能会把透明区域变成黑色。更稳妥的方式是先创建一个白色背景,再把 PNG 贴上去。

python 复制代码
from PIL import Image

input_path = "logo.png"
output_path = "logo.jpg"

with Image.open(input_path) as img:
    if img.mode in ("RGBA", "LA"):
        background = Image.new("RGB", img.size, (255, 255, 255))
        background.paste(img, mask=img.getchannel("A"))
        background.save(output_path, "JPEG")
    else:
        img.convert("RGB").save(output_path, "JPEG")

print("PNG 转 JPG 完成")

这种处理方式更适合批量转换,因为它能兼容透明 PNG。

六、图片压缩

图片压缩通常用于减小文件体积。对于 JPG 图片,可以通过 quality 参数控制质量。

python 复制代码
from PIL import Image

input_path = "demo.jpg"
output_path = "demo_compressed.jpg"

with Image.open(input_path) as img:
    img = img.convert("RGB")
    img.save(output_path, "JPEG", quality=70, optimize=True)

print("图片压缩完成")

参数说明:

  • quality:图片质量,取值一般为 1 到 95,数值越大质量越高,文件越大
  • optimize=True:让 Pillow 尽量优化保存结果

常用建议:

  • 文章配图可以使用 quality=75
  • 缩略图可以使用 quality=60
  • 高清存档可以使用 quality=90

PNG 也可以压缩:

python 复制代码
from PIL import Image

with Image.open("demo.png") as img:
    img.save("demo_compressed.png", "PNG", optimize=True)

需要注意的是,PNG 是无损格式,单纯使用 Pillow 压缩时,体积下降不一定明显。如果对 PNG 压缩要求很高,可以结合专门的压缩工具。

七、图片尺寸调整

调整图片尺寸可以使用 resize()

python 复制代码
from PIL import Image

input_path = "demo.jpg"
output_path = "demo_resized.jpg"

with Image.open(input_path) as img:
    resized = img.resize((800, 600))
    resized.save(output_path)

print("图片尺寸调整完成")

不过直接指定固定宽高可能导致图片变形。实际项目中更常见的是按比例缩放。

python 复制代码
from PIL import Image

def resize_keep_ratio(img, max_width, max_height):
    img_copy = img.copy()
    img_copy.thumbnail((max_width, max_height))
    return img_copy

with Image.open("demo.jpg") as img:
    resized = resize_keep_ratio(img, 800, 800)
    resized.save("demo_resized.jpg")

thumbnail() 会保持原图比例,并确保图片不会超过指定的最大宽高。

例如,原图是 3000 x 2000,最大尺寸设置为 800 x 800,缩放后会变成 800 x 533,不会被拉伸变形。

八、批量处理整个目录

批量处理目录的核心思路:

  1. 遍历输入目录下的文件
  2. 判断文件后缀是否为图片格式
  3. 使用 Pillow 打开图片
  4. 根据目标格式转换并保存
  5. 遇到异常时跳过并打印错误信息

可以先写一个简单版本:

python 复制代码
from pathlib import Path
from PIL import Image

input_dir = Path("input_images")
output_dir = Path("output_images")
output_dir.mkdir(parents=True, exist_ok=True)

for image_path in input_dir.iterdir():
    if image_path.suffix.lower() not in [".jpg", ".jpeg", ".png", ".bmp", ".webp"]:
        continue

    output_path = output_dir / f"{image_path.stem}.png"

    with Image.open(image_path) as img:
        img.save(output_path, "PNG")

    print(f"已转换:{image_path.name} -> {output_path.name}")

这个版本可以完成基本批量转换,但还不够灵活。下面我们会封装成一个完整工具。

九、完整代码案例:图片批量格式转换工具

下面的完整代码支持:

  • JPG、PNG、BMP、WEBP 等常见图片格式输入
  • 输出为 JPG、PNG、WEBP
  • 可选图片压缩质量
  • 可选最大宽高,保持比例缩放
  • 批量处理整个目录
  • 自动创建输出目录
  • 兼容 PNG 透明通道转 JPG 的情况

保存为 batch_image_converter.py

python 复制代码
import argparse
from pathlib import Path

from PIL import Image


SUPPORTED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".bmp", ".webp", ".tif", ".tiff"}
SUPPORTED_OUTPUT_FORMATS = {"jpg", "jpeg", "png", "webp"}


def normalize_format(output_format):
    output_format = output_format.lower().strip(".")
    if output_format not in SUPPORTED_OUTPUT_FORMATS:
        raise ValueError(f"不支持的输出格式:{output_format}")
    return "jpg" if output_format == "jpeg" else output_format


def get_output_suffix(output_format):
    return ".jpg" if output_format in ("jpg", "jpeg") else f".{output_format}"


def prepare_image_for_jpg(img, background_color=(255, 255, 255)):
    if img.mode in ("RGBA", "LA"):
        background = Image.new("RGB", img.size, background_color)
        alpha = img.getchannel("A")
        background.paste(img, mask=alpha)
        return background

    if img.mode == "P":
        img = img.convert("RGBA")
        if "A" in img.getbands():
            background = Image.new("RGB", img.size, background_color)
            background.paste(img, mask=img.getchannel("A"))
            return background

    return img.convert("RGB")


def resize_keep_ratio(img, max_width=None, max_height=None):
    if not max_width and not max_height:
        return img

    width, height = img.size
    target_width = max_width or width
    target_height = max_height or height

    resized = img.copy()
    resized.thumbnail((target_width, target_height))
    return resized


def save_image(img, output_path, output_format, quality):
    if output_format == "jpg":
        img = prepare_image_for_jpg(img)
        img.save(output_path, "JPEG", quality=quality, optimize=True)
        return

    if output_format == "png":
        img.save(output_path, "PNG", optimize=True)
        return

    if output_format == "webp":
        img.save(output_path, "WEBP", quality=quality, method=6)
        return

    raise ValueError(f"不支持的输出格式:{output_format}")


def convert_one_image(image_path, output_dir, output_format, quality, max_width, max_height):
    output_suffix = get_output_suffix(output_format)
    output_path = output_dir / f"{image_path.stem}{output_suffix}"

    with Image.open(image_path) as img:
        img = resize_keep_ratio(img, max_width=max_width, max_height=max_height)
        save_image(img, output_path, output_format, quality)

    return output_path


def batch_convert(input_dir, output_dir, output_format, quality=80, max_width=None, max_height=None):
    input_dir = Path(input_dir)
    output_dir = Path(output_dir)
    output_format = normalize_format(output_format)

    if not input_dir.exists():
        raise FileNotFoundError(f"输入目录不存在:{input_dir}")

    if not input_dir.is_dir():
        raise NotADirectoryError(f"输入路径不是目录:{input_dir}")

    output_dir.mkdir(parents=True, exist_ok=True)

    total = 0
    success = 0
    failed = 0

    for image_path in input_dir.rglob("*"):
        if not image_path.is_file():
            continue

        if image_path.suffix.lower() not in SUPPORTED_EXTENSIONS:
            continue

        total += 1

        try:
            output_path = convert_one_image(
                image_path=image_path,
                output_dir=output_dir,
                output_format=output_format,
                quality=quality,
                max_width=max_width,
                max_height=max_height,
            )
            success += 1
            print(f"[成功] {image_path} -> {output_path}")
        except Exception as exc:
            failed += 1
            print(f"[失败] {image_path},原因:{exc}")

    print("-" * 60)
    print(f"处理完成:总数={total},成功={success},失败={failed}")


def parse_args():
    parser = argparse.ArgumentParser(description="Python Pillow 图片批量格式转换工具")
    parser.add_argument("-i", "--input", required=True, help="输入图片目录")
    parser.add_argument("-o", "--output", required=True, help="输出图片目录")
    parser.add_argument(
        "-f",
        "--format",
        default="jpg",
        choices=["jpg", "jpeg", "png", "webp"],
        help="输出图片格式,默认 jpg",
    )
    parser.add_argument(
        "-q",
        "--quality",
        type=int,
        default=80,
        help="图片质量,主要用于 jpg 和 webp,默认 80",
    )
    parser.add_argument("--max-width", type=int, default=None, help="最大宽度,保持比例缩放")
    parser.add_argument("--max-height", type=int, default=None, help="最大高度,保持比例缩放")
    return parser.parse_args()


def main():
    args = parse_args()

    batch_convert(
        input_dir=args.input,
        output_dir=args.output,
        output_format=args.format,
        quality=args.quality,
        max_width=args.max_width,
        max_height=args.max_height,
    )


if __name__ == "__main__":
    main()

十、运行示例

input_images 目录下的图片批量转换为 JPG:

bash 复制代码
python batch_image_converter.py -i input_images -o output_images -f jpg

转换为 PNG:

bash 复制代码
python batch_image_converter.py -i input_images -o output_images -f png

转换为 WEBP,并设置压缩质量:

bash 复制代码
python batch_image_converter.py -i input_images -o output_images -f webp -q 75

批量压缩 JPG:

bash 复制代码
python batch_image_converter.py -i input_images -o compressed_images -f jpg -q 60

批量调整尺寸,最大宽度和高度不超过 800:

bash 复制代码
python batch_image_converter.py -i input_images -o resized_images -f jpg --max-width 800 --max-height 800

批量转换并压缩:

bash 复制代码
python batch_image_converter.py -i input_images -o result_images -f jpg -q 70 --max-width 1200 --max-height 1200

十一、代码设计说明

这个工具把功能拆成了几个函数:

  • normalize_format():统一处理输出格式
  • prepare_image_for_jpg():解决透明 PNG 转 JPG 的问题
  • resize_keep_ratio():保持比例调整尺寸
  • save_image():根据目标格式保存图片
  • convert_one_image():处理单张图片
  • batch_convert():批量处理整个目录
  • parse_args():解析命令行参数

这种拆分方式比把所有逻辑写在一个循环里更容易维护。后续要增加水印、重命名、跳过已存在文件等功能,也可以继续扩展。

十二、GUI 简单扩展思路

当前工具是命令行版本,适合脚本自动化和批处理。如果要做成图形界面,可以使用 Tkinter、PyQt 或 PySide。

一个简单 GUI 可以包含这些控件:

  • 输入目录选择按钮
  • 输出目录选择按钮
  • 输出格式下拉框
  • 图片质量滑块
  • 最大宽度和最大高度输入框
  • 开始转换按钮
  • 转换日志显示区域
  • 进度条

如果使用 Tkinter,基本结构可以这样设计:

python 复制代码
import tkinter as tk
from tkinter import filedialog, messagebox


def choose_input_dir():
    path = filedialog.askdirectory()
    input_var.set(path)


def choose_output_dir():
    path = filedialog.askdirectory()
    output_var.set(path)


def start_convert():
    input_dir = input_var.get()
    output_dir = output_var.get()
    output_format = format_var.get()

    if not input_dir or not output_dir:
        messagebox.showwarning("提示", "请选择输入目录和输出目录")
        return

    # 这里可以调用前面的 batch_convert() 函数
    messagebox.showinfo("完成", f"开始转换为 {output_format}")


root = tk.Tk()
root.title("图片批量格式转换工具")
root.geometry("500x260")

input_var = tk.StringVar()
output_var = tk.StringVar()
format_var = tk.StringVar(value="jpg")

tk.Label(root, text="输入目录").pack(anchor="w", padx=20, pady=5)
tk.Entry(root, textvariable=input_var, width=50).pack(anchor="w", padx=20)
tk.Button(root, text="选择输入目录", command=choose_input_dir).pack(anchor="w", padx=20, pady=5)

tk.Label(root, text="输出目录").pack(anchor="w", padx=20, pady=5)
tk.Entry(root, textvariable=output_var, width=50).pack(anchor="w", padx=20)
tk.Button(root, text="选择输出目录", command=choose_output_dir).pack(anchor="w", padx=20, pady=5)

tk.Label(root, text="输出格式").pack(anchor="w", padx=20, pady=5)
tk.OptionMenu(root, format_var, "jpg", "png", "webp").pack(anchor="w", padx=20)

tk.Button(root, text="开始转换", command=start_convert).pack(pady=15)

root.mainloop()

实际开发时,可以直接复用命令行版本中的 batch_convert() 函数。这样核心处理逻辑不用重写,只需要把 GUI 控件中的参数传给函数即可。

如果图片数量很多,GUI 中还需要注意一点:不要在主线程里直接执行批量转换,否则界面可能卡住。可以使用 threading.Thread 把转换任务放到子线程中执行。

十三、常见问题

1. 为什么 PNG 转 JPG 报错

常见原因是 PNG 图片包含透明通道,而 JPG 不支持透明通道。解决方式是先转换为 RGB,或者先合成白色背景。

2. 为什么压缩后图片还是很大

可能原因包括:

  • 原图尺寸太大
  • quality 设置过高
  • PNG 本身不适合用 Pillow 大幅压缩
  • 图片内容复杂,压缩空间有限

可以同时使用降低质量和调整尺寸两种方式。

3. 为什么批量处理时有些图片失败

可能原因包括:

  • 文件扩展名是图片,但文件内容已经损坏
  • 文件权限不足
  • 路径中存在特殊权限问题
  • 输出目录不可写

所以批量脚本中要使用 try except,避免一张图片失败导致整个任务中断。

十四、总结

本文使用 Python + Pillow 实现了一个图片批量格式转换工具,覆盖了图像处理入门中非常常见的几个需求:

  • 安装 Pillow
  • JPG 转 PNG
  • PNG 转 JPG
  • 图片压缩
  • 图片尺寸调整
  • 批量处理整个目录
  • 命令行完整代码案例
  • GUI 图形界面扩展思路

这个案例非常适合作为 Python 图像处理入门练习。掌握这些基础之后,可以继续扩展水印添加、图片裁剪、缩略图生成、图片重命名、EXIF 信息读取等功能,逐步做成一个更完整的图片处理工具箱。

相关推荐
YJlio1 小时前
2023-09-25:ChatGPT 开始支持“看、听、说”,从纯文本正式迈向多模态交互
人工智能·python·科技·chatgpt·django·交互·pygame
技术工小李1 小时前
陆家嘴国泰人寿荣誉墙|iPad人脸识别人像大屏展示案例
python
不会写DN1 小时前
PyScript-GitHubRepo:构建高性能GitHub仓库批量下载工具的技术实践
开发语言·前端·python
SilentSamsara2 小时前
生成器完全指南:`yield` 与惰性求值的工程价值
linux·开发语言·python·算法·机器学习·青少年编程
YJlio10 小时前
7.4.5 Windows 11 企业网络连接与网络重置实战:远程访问、本地策略与故障恢复
前端·chrome·windows·python·edge·机器人·django
深耕AI10 小时前
【VS Code避坑指南】点击Python图标提示“没有Python环境”,选择安装uv后这堆输出到底是什么意思?
开发语言·python·uv
第一程序员10 小时前
Rust生命周期管理实战指南:从困惑到掌握
python·github
程序员威哥10 小时前
实战!Python爬京东商品评论:从采集到情感分析+词云可视化,新手30分钟跑通
开发语言·爬虫·python·scrapy
风噪10 小时前
centos7 python3.13全套安装(可用于离线复制)
python