Python图像处理入门:Pillow库读取、显示与保存图片全解析

发布时间:2026/8/1 8:33:11
Python图像处理入门:Pillow库读取、显示与保存图片全解析 1. 从“打不开图”到“玩转图片”为什么PIL是Python图像处理的起点最近在帮一个刚学Python的朋友处理他拍的一堆产品照片他遇到的问题很典型用Python打开一张图片想看看尺寸结果要么报错说找不到模块要么打开后不知道怎么显示出来更别提调整大小、加个水印然后保存了。这让我想起自己刚开始的时候面对一堆图片文件想用Python做点自动化处理第一步“怎么把图片读进来”就卡了半天。其实对于绝大多数Python图像处理任务无论是简单的尺寸查看、格式转换还是复杂的滤镜添加、信息提取第一步都绕不开一个核心库PIL或者更准确地说它的现代继承者Pillow。你可能在网上搜“Python 处理图片”时会看到各种教程都提到PIL。PIL全称是Python Imaging Library是Python领域图像处理的事实标准。虽然原版PIL已经停止维护但一个兼容并增强的复刻版Pillow接过了接力棒并且成为了现在大家安装和使用的标准。当你执行pip install Pillow时你安装的就是这个活跃维护的库。它几乎能处理所有常见的图片格式JPEG, PNG, BMP, GIF, TIFF等提供了从最基础的读取、显示、保存到裁剪、旋转、滤镜、绘制等一整套功能。掌握PIL/Pillow的这三板斧——读取、显示、保存就像是拿到了进入图像处理世界的钥匙。这不仅仅是执行几个函数调用那么简单背后涉及到图像在计算机内存中的表示一个由像素组成的多维数组、不同颜色模式如RGB、灰度的理解以及文件格式与压缩算法的权衡。很多人卡在第一步往往是因为环境没配好或者对图像对象的本质理解不透。接下来我会结合这些年处理各种图片格式、应对不同显示环境从命令行到图形界面的实际经验把这三个核心操作掰开揉碎了讲清楚让你不仅能跑通代码更能明白每一步背后的“所以然”避开那些新手常踩的坑。2. 环境搭建与核心对象安装Pillow并理解Image对象在开始写代码之前确保你的Python环境里已经安装了Pillow库。这是所有操作的基础。打开你的终端Windows上是CMD或PowerShellmacOS/Linux上是Terminal输入以下命令pip install Pillow如果你使用了Anaconda也可以用conda install pillow。安装成功后可以在Python交互环境中验证一下import PIL print(PIL.__version__) # 查看Pillow版本确认安装成功这里有一个关键的细节虽然我们安装的包叫Pillow但在代码中导入时为了兼容历史习惯我们通常使用PIL这个模块名。这是Pillow库特意设计的一种兼容方式让你可以用导入原版PIL的语法来使用它。所以在你的代码开头标准的导入语句是from PIL import Image这行代码从Pillow库中导入了最核心的Image模块。在Pillow的世界里Image模块提供了一个同名的Image类。你之后打开的每一张图片在内存中都会是一个Image对象。理解这个对象至关重要它不是一个简单的文件句柄而是一个封装了图像所有数据像素矩阵、尺寸、格式、模式等和操作方法如裁剪、旋转的实体。你可以把Image对象想象成一个智能的“图片容器”。它知道自己的宽度和高度.size属性知道每个像素是用RGB三种颜色还是灰度来表示.mode属性还知道自己的原始格式.format属性。我们后续的所有操作无论是显示还是保存都是基于这个Image对象进行的。因此from PIL import Image这行导入是你每次进行图像处理脚本编写时几乎必然要写的第一行代码。3. 图片读取的多种姿势与核心参数解析读取图片是第一步Pillow的Image.open()函数是完成这个任务的瑞士军刀。它的基本用法非常简单from PIL import Image # 读取当前目录下的图片文件 img Image.open(example.jpg)执行这行代码后img变量就成为了一个代表example.jpg的Image对象。但事情往往没这么简单在实际项目中你会遇到各种情况。3.1 处理文件路径与格式首先文件路径可以是绝对路径或相对路径。相对路径是相对于你运行Python脚本的当前工作目录。一个常见的坑是你在IDE如PyCharm、VSCode里运行脚本工作目录可能是项目根目录而在终端直接运行python script.py工作目录则是脚本所在目录。为了避免“FileNotFoundError”我习惯使用os.path模块来构建绝对路径这样更可靠import os from PIL import Image # 假设脚本和图片在同一目录下 script_dir os.path.dirname(os.path.abspath(__file__)) image_path os.path.join(script_dir, example.jpg) img Image.open(image_path)Image.open()函数非常“聪明”它不依赖文件扩展名来判断格式而是通过读取文件头的魔法数字magic number来识别。这意味着即使你把一个.png文件改名为.jpgPillow通常也能正确识别并打开它。但是这并不意味着你可以随意乱改扩展名。因为后续的保存操作.save()默认会使用打开时识别的格式如果扩展名和实际格式不符可能会导致其他软件无法打开。所以保持文件名与格式一致是一个好习惯。3.2 理解“延迟加载”与load()方法这里有一个非常重要的性能优化特性Image.open()执行的是“延迟加载”lazy loading。它只是打开了文件读取了图像的头信息如尺寸、模式但并没有立即将所有的像素数据加载到内存中。真正的像素数据加载会延迟到你需要访问它们的时候比如调用.size属性时它已经知道但当你调用.getpixel()或进行像素级操作时才会加载。你可以通过调用.load()方法来强制立即将整个图像数据加载到内存并返回一个像素访问对象img Image.open(large_image.tiff) pixel_map img.load() # 此时所有像素数据被加载到内存 # 现在可以通过 pixel_map[x, y] 快速访问或修改任意像素对于非常大的图片延迟加载可以节省初始内存但如果你确定要进行全图遍历或修改提前load()可能会让后续的像素访问更快。这是一个根据实际情况权衡的策略。3.3 打开模式与颜色空间转换有时你可能希望以特定的颜色模式打开图片比如不管原图是什么我都想把它当作灰度图来处理。Image.open()本身不支持直接指定模式但可以在打开后立即转换img Image.open(color_image.jpg).convert(L) # ‘L’ 模式代表8位像素的黑白灰度图convert()方法非常强大可以在RGB、RGBA带透明度、L灰度、1二值图等模式间转换。例如将RGBA图片转换为RGB去除Alpha通道以兼容不支持透明度的格式rgb_img rgba_img.convert(RGB)4. 在多种环境中显示图片从终端到GUI读取图片后我们常常需要看一眼它长什么样确认读取是否正确或者预览处理效果。Pillow提供了.show()方法但它的行为可能和你想象的不太一样而且在不同环境下差异很大。4.1 默认的Image.show()及其局限性最直接的方法是调用Image对象的.show()方法img.show()这个方法会做什么呢它会将图像临时保存为一个PNG文件通常在你的系统临时目录然后调用你操作系统默认的图片查看器来打开这个临时文件。在Windows上可能调用的是“照片”应用在macOS上是“预览”在Linux上可能是xdg-open命令关联的程序。注意.show()是一个“阻塞式”操作吗这取决于操作系统和查看器。在某些配置下Python脚本会等待图片查看器关闭后才继续执行这可能会中断你的自动化流程。它主要用于简单的调试和预览不适合集成到需要连续运行的图形界面程序中。4.2 集成到GUI程序如Tkinter, PyQt中显示在开发带界面的应用程序时我们更需要的是将图片显示在程序自己的窗口里。这就需要将Pillow的Image对象转换为GUI库能识别的图像对象。以最标准的Python GUI库Tkinter为例from PIL import Image, ImageTk import tkinter as tk # 创建Tkinter窗口 root tk.Tk() root.title(显示图片) # 用Pillow打开图片 pil_image Image.open(example.jpg) # 关键步骤将PIL Image对象转换为Tkinter PhotoImage对象 tk_image ImageTk.PhotoImage(pil_image) # 创建一个Label组件来显示图片 label tk.Label(root, imagetk_image) label.pack() # 启动事件循环 root.mainloop()这里的关键是ImageTk.PhotoImage()这个转换函数。有一个非常重要的坑需要避开你必须将转换后的tk_image对象赋值给一个生命周期长的变量比如全局变量或对象的属性。如果它只是一个局部变量在函数执行完后可能会被垃圾回收导致图片在窗口上显示不出来。这就是为什么上面例子中tk_image被直接使用实际上在更复杂的程序中你通常需要把它保存为类的一个属性例如self.tk_image ImageTk.PhotoImage(pil_image)。对于PyQt/PySide原理类似需要将PIL Image转换为QImage再转换为QPixmap用于显示from PIL import Image from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtWidgets import QLabel, QApplication import sys app QApplication(sys.argv) pil_image Image.open(example.jpg) # 将PIL Image转换为QImage data pil_image.tobytes() qimage QImage(data, pil_image.width, pil_image.height, QImage.Format_RGB888) # 将QImage转换为QPixmap用于显示 pixmap QPixmap.fromImage(qimage) label QLabel() label.setPixmap(pixmap) label.show() sys.exit(app.exec_())4.3 在Jupyter Notebook中内联显示如果你在Jupyter Notebook或类似的环境中进行数据分析或原型开发可以使用IPython的显示功能from PIL import Image from IPython.display import display img Image.open(example.jpg) display(img) # 图片会直接渲染在Notebook的输出单元格中这种方法非常方便适合做交互式的图像处理实验和教学。5. 保存图片格式、参数与质量控制的细节处理完图片后最终步骤就是保存。Image.save()方法看似简单但里面的参数配置直接影响输出文件的大小、质量和兼容性。5.1 基本保存与格式推断img.save(output.jpg)这行代码会将img对象保存为output.jpg文件。save()方法非常智能格式推断它首先通过文件名扩展名.jpg来判断要保存的格式。如果扩展名无法识别或者你希望指定格式可以使用format参数。自动转换如果图片当前的模式img.mode与目标格式不兼容Pillow会自动进行转换。例如如果你尝试将一个RGBA带透明度模式的图片保存为JPEG格式Pillow会先将其转换为RGB模式因为JPEG不支持透明度。5.2 关键格式的保存参数详解不同的图片格式支持不同的保存参数通过save()方法的额外关键字参数传递。JPEG格式平衡质量与体积JPEG是一种有损压缩格式核心参数是quality范围从1最差文件最小到95最好文件最大默认值通常是75。对于网络应用75-85是一个不错的平衡点。img.save(high_quality.jpg, quality95) img.save(small_file.jpg, quality50, optimizeTrue) # optimize参数会进行额外的优化以减小体积经验之谈quality高于95对于JPEG格式基本没有视觉提升但文件体积会显著增大。而低于50时压缩伪影模糊的色块会变得非常明显。对于缩略图可以适当降低质量到60-70。PNG格式无损压缩与透明度PNG是无损压缩格式支持透明度Alpha通道。主要参数是compress_level范围0-90是不压缩文件最大保存最快9是最大压缩文件最小保存最慢默认是6。对于带有大面积纯色或简单图形的图片如图标、截图PNG压缩率很高。img.save(compressed.png, compress_level9) # 最大压缩如果你处理的图片本身是RGBA模式保存为PNG时会自动保留透明度。GIF格式动图与调色板GIF格式最多支持256色。保存彩色图片为GIF时Pillow会自动将其转换为调色板模式“P”模式。你可以通过palette和dither参数控制转换效果。img.save(animation.gif, save_allTrue, append_images[frame2, frame3], duration200, loop0)上面这行代码展示了保存多帧GIF动图的方法。save_allTrue表示保存所有帧append_images是一个包含后续帧的列表duration是每帧的持续时间毫秒loop0表示无限循环。5.3 保存中的常见“坑”与解决方案“OSError: cannot write mode RGBA as JPEG”这是最常见的错误之一。正如前面提到的JPEG不支持透明度。解决方案是在保存前将图片转换为RGB模式rgb_img rgba_img.convert(RGB) rgb_img.save(output.jpg)保存后文件异常大这通常发生在保存为PNG格式时源图是JPEG转换过来的。JPEG是有损压缩已经丢失了一些信息再将其保存为无损的PNG文件体积会比原JPEG大很多且画质不会提升。除非你需要透明度或绝对的无损否则不要将JPEG转存为PNG。保存时丢失EXIF信息EXIF信息包含了照片的拍摄参数、GPS位置等元数据。默认情况下Pillow的save()操作不会保留这些信息。如果你需要保留需要手动处理from PIL import Image img Image.open(with_exif.jpg) exif_data img.info.get(exif) # 获取EXIF数据 # ... 对img进行处理 ... img.save(output.jpg, exifexif_data) # 保存时传入exif参数注意某些处理操作如尺寸变换、模式转换可能会使EXIF信息失效或不准确。6. 一个完整的实战案例批量生成产品缩略图让我们把读取、处理、保存串起来完成一个实际的任务假设你有一个产品图片目录里面全是高分辨率的.jpg文件你需要为每张图片生成一个宽度固定为300像素、保持比例缩放的缩略图并以_thumb为后缀保存。import os from PIL import Image def generate_thumbnails(source_dir, target_dir, max_width300): 为源目录下的所有jpg图片生成缩略图。 参数: source_dir: 存放原图的目录路径。 target_dir: 存放缩略图的目录路径。 max_width: 缩略图的最大宽度像素。 # 确保目标目录存在 os.makedirs(target_dir, exist_okTrue) # 遍历源目录下的所有文件 for filename in os.listdir(source_dir): if filename.lower().endswith((.jpg, .jpeg, .png, .bmp, .gif)): try: # 1. 读取图片 source_path os.path.join(source_dir, filename) with Image.open(source_path) as img: # 2. 计算新的尺寸保持宽高比 width_percent max_width / float(img.size[0]) new_height int(float(img.size[1]) * width_percent) new_size (max_width, new_height) # 3. 调整图片尺寸。使用高质量的重采样滤波器LANCZOS img.thumbnail(new_size, Image.Resampling.LANCZOS) # 4. 准备输出文件名和路径 name, ext os.path.splitext(filename) output_filename f{name}_thumb{ext} output_path os.path.join(target_dir, output_filename) # 5. 保存缩略图。对于JPEG设置一个较好的质量 if ext.lower() in [.jpg, .jpeg]: img.save(output_path, JPEG, quality85, optimizeTrue) else: # 其他格式按原格式保存 img.save(output_path) print(f已生成: {output_filename}) except Exception as e: print(f处理文件 {filename} 时出错: {e}) # 使用示例 if __name__ __main__: generate_thumbnails(./product_images, ./thumbnails)这个案例中的几个关键点使用with语句with Image.open(...) as img:这是一种最佳实践。它能确保即使在处理图片过程中发生异常图像文件也会被正确关闭避免资源泄露。thumbnail()vsresize()这里我们用了img.thumbnail(new_size)而不是img.resize(new_size)。thumbnail()方法会原地修改图像对象并且保证生成的图片尺寸不会超过给定的元组尺寸同时保持宽高比。而resize()则是严格调整为给定尺寸可能会变形。生成缩略图时thumbnail()通常更符合需求。重采样滤波器Image.Resampling.LANCZOS是一种高质量的下采样滤波器在缩小图片时能获得更平滑、更少锯齿的结果。对于放大操作BICUBIC或LANCZOS也是好选择对于速度优先的缩小NEAREST最快但质量差。条件保存根据原文件扩展名决定保存参数。对于JPEG我们指定了质量和优化选项对于PNG等格式则按默认方式保存。7. 性能优化与异常处理实践当处理的图片数量很多或者单张图片很大时性能和稳定性就变得很重要。7.1 内存管理与大图处理Pillow在打开图片时默认会尝试将整个图片加载到内存。对于超大的图片比如几GB的卫星图像或医学图像这可能导致内存不足。此时可以使用Image模块的open()方法结合ImageFile.LOAD_TRUNCATED_IMAGES选项或者更推荐使用Image对象的getdata()方法进行流式处理但更常见的做法是直接处理这类图片的缩略图或分块处理。一个实用的技巧是如果你只需要获取图片的尺寸、格式等元信息而不需要像素数据可以这样from PIL import Image with Image.open(huge_image.tiff) as img: width, height img.size print(f尺寸: {width}x{height}) # 图片文件在此处会被关闭像素数据并未全部加载7.2 异常处理让你的脚本更健壮在实际的批量处理脚本中总会遇到一些损坏的、格式奇怪的或者权限不足的文件。健壮的代码必须处理这些异常。from PIL import Image, UnidentifiedImageError import os def safe_image_process(filepath): 安全地处理单张图片捕获常见异常。 if not os.path.exists(filepath): print(f文件不存在: {filepath}) return try: with Image.open(filepath) as img: # 验证图片是否可以被成功加载 img.verify() # verify()方法快速检查文件是否损坏但不解码数据 except UnidentifiedImageError: print(f无法识别的图像文件可能已损坏或非图片: {filepath}) return except Exception as e: print(f打开或验证图片时发生未知错误 ({filepath}): {e}) return # 重新打开文件进行处理因为verify()后需要重新打开 try: with Image.open(filepath) as img: # 这里进行你的实际处理操作例如转换模式 if img.mode ! RGB: img img.convert(RGB) # ... 其他处理 ... img.save(processed_ os.path.basename(filepath)) print(f成功处理: {filepath}) except IOError as e: print(f处理或保存图片时发生IO错误 ({filepath}): {e}) except Exception as e: print(f处理过程中发生未知错误 ({filepath}): {e}) # 批量处理时 for file in image_files: safe_image_process(file)这里的关键点UnidentifiedImageError这是Pillow在无法识别文件格式时抛出的特定异常。img.verify()这是一个轻量级的方法用于快速检查文件是否完整、是否是一个Pillow能解析的图片。它比完全加载图片更快适合在批量处理前做初步筛选。注意verify()之后图像对象就不可用了需要重新Image.open()。将文件打开和核心处理逻辑放在不同的try...except块中可以更精确地定位错误来源。掌握了Pillow中读取、显示、保存这三个核心操作及其背后的细节你就已经搭建起了Python图像处理的坚实地基。无论是简单的脚本还是复杂的应用这些知识都是你构建更高级功能如图像分析、计算机视觉预处理、图形界面开发的必备工具。记住多动手试多处理几种不同格式、不同大小的图片遇到报错时仔细阅读错误信息你就能越来越熟练地驾驭这个强大的库。