PIL Image模块图像处理入门:从打开到批量实战

发布时间:2026/11/3 23:15:10
PIL Image模块图像处理入门:从打开到批量实战 图像处理这件事很多人第一次接触都是从 PIL 开始的。我印象特别深早年接手一个批量处理商品图的活儿几百张图片要统一裁切、调色、加水印当时第一反应是找现成工具结果要么功能对不上要么批量处理卡得不行。后来一位老同事甩给我一段from PIL import Image开头的脚本几十行代码跑完我才意识到这个库有多实在。PIL 的全称是 Python Imaging Library后来社区维护的版本叫 Pillow它是 Python 生态里做图像处理最基础也最常用的库之一。这篇文章我想聊的就是用 PIL 的 Image 模块做图像处理的基本操作——打开、查看、裁剪、缩放、旋转、调色、格式转换、批量处理这些日常高频动作。不管你是刚学 Python 想找个练手项目还是工作中需要处理图片但不想装一堆重型软件又或者想给自动化流程加个图片处理环节这些内容都能直接拿去用。我会把每个操作的原理、参数怎么选、踩过的坑都摊开讲尽量让你看完就能上手写脚本。1. 先搞清楚 PIL 和 Pillow 的关系别在安装这一步就卡住1.1 为什么你 pip install PIL 会失败这是新手最容易遇到的第一个坎。PIL 这个项目本身早就停止维护了最后一个版本停留在很多年前而且不支持 Python 3。现在你在 Python 3 环境里执行pip install PIL大概率直接报错找不到匹配版本。真正在维护的是 Pillow它是 PIL 的一个分支接口基本兼容但适配了现代 Python 版本修了大量 bug还加了不少新功能。所以正确的安装命令是pip install Pillow装完之后导入的时候用的还是 PIL 这个名字from PIL import Image这个命名上的错位经常让人困惑——装的是 Pillow导入的是 PIL。你可以这么理解Pillow 是新瓶PIL 是旧标瓶换了但标签没换为的是让老代码不用改导入语句就能跑。我见过不少人装完 Pillow 发现import PIL能用但import Pillow报错然后怀疑自己装错了其实完全正常。提示如果你在虚拟环境里操作记得先激活环境再装。另外某些精简版系统镜像可能缺少图像编解码依赖装完 Pillow 后打开 JPEG 报错的话补装系统级的图像库依赖即可具体包名跟操作系统有关。1.2 Image 模块在整个库里的定位Pillow 是个大库Image 只是其中一个模块但它是最核心的那个。你可以把 Image 理解成图片对象的总管家——打开图片得到的是一个 Image 对象后续所有裁剪、缩放、旋转、调色操作本质上都是在这个对象上做变换或者生成新的 Image 对象。除了 ImagePillow 还有几个常用模块值得知道ImageDraw用来画线画矩形写文字ImageFont配合它设置字体ImageFilter提供模糊锐化等滤镜ImageEnhance做亮度对比度增强ImageOps有一些便捷的批量操作比如自动对比度、边框。但所有这些的前提都是先用 Image 把图片读进来。所以把 Image 的基本操作吃透是后面一切的地基。我个人的习惯是写任何图片处理脚本开头永远是那三行from PIL import Image import os # 后面才是具体逻辑先把地基打牢再往上盖。2. 打开、查看、保存图像处理的三步基本功2.1 打开图片时到底发生了什么Image.open()这个函数看起来简单但它的行为有个关键细节很多人不知道它并不是立刻把整张图片的像素数据读进内存而是先读取文件头信息建立一个懒加载的对象。也就是说你调用 open 之后图片的尺寸、格式、模式这些元信息已经能拿到了但真正的像素数据要等到你第一次访问它比如显示、裁剪、保存时才加载。这个设计的好处是省内存尤其是批量处理大量图片时你可以先快速遍历所有图片的尺寸信息做筛选不用把每张图都完整读进来。但坏处是如果你 open 之后文件被删了或者移动了等到真正访问像素时才会报错排查起来容易懵。from PIL import Image img Image.open(example.jpg) print(img.size) # 宽高比如 (1920, 1080) print(img.format) # 格式比如 JPEG print(img.mode) # 色彩模式比如 RGBsize返回的是(宽度, 高度)的元组注意顺序是宽在前高在后这点跟很多图形库一致但跟数学里的坐标习惯先横后纵容易混。mode是色彩模式最常见的三种RGB是彩色三通道L是灰度单通道RGBA是带透明通道的彩色。格式和模式是两回事——格式指文件存储格式JPEG、PNG模式指像素的组织方式一张 PNG 可以是 RGB 也可以是 RGBA。2.2 显示图片的两种方式及适用场景调试阶段经常需要看一眼图片长什么样。最直接的是img.show()它会调用系统默认的图片查看器打开图片。这个方法在本地开发时很方便但在服务器或者没有图形界面的环境里会失败或者没反应。另一种方式是在 Jupyter Notebook 这类交互环境里直接输出对象或者用display(img)。如果你在做数据分析或者教学演示这种方式更顺手图片直接嵌在输出里。我自己的习惯是写批量脚本时基本不用 show因为几百张图一张张弹窗会疯掉。取而代之的是先打印尺寸、模式这些元信息做快速检查实在要看图就抽几张代表性的保存到临时目录再统一看。这个习惯帮我省了大量时间——批量处理最怕的就是中途某张图有问题导致整个流程崩掉提前抽样检查能规避很多麻烦。2.3 保存图片时的格式与质量权衡img.save()是保存但这里面的门道比想象中多。最基础的用法是给个文件名Pillow 会根据扩展名自动推断格式img.save(output.png) img.save(output.jpg)但 JPEG 是有损压缩保存时可以控制质量参数img.save(output.jpg, quality85)quality范围是 1 到 95100 也可以但收益递减默认是 75。这个参数直接影响文件大小和画质。我做过对比测试同一张 1920x1080 的照片quality95 时文件大概 800KBquality75 时降到 300KB 左右quality50 时只有 150KB 但肉眼已经能看出块状模糊了。日常网页展示用 80 到 85 是比较好的平衡点需要存档或者印刷才用 95 以上。PNG 是无损格式没有 quality 参数但有个optimize选项可以压缩文件体积img.save(output.png, optimizeTrue)这个选项会让保存慢一点但文件通常能小 10% 到 30%对于要上传到网上的图片很值得开。注意保存时的格式和原图格式可以不同比如打开 JPEG 存成 PNGPillow 会自动做转换。但如果你把带透明通道的 RGBA 图存成 JPEG透明部分会变成黑色或者报错因为 JPEG 不支持透明。这种情况要么先转成 RGB 再存要么改存 PNG。3. 裁剪、缩放、旋转几何变换的核心操作3.1 裁剪理解坐标系的边界裁剪用crop()参数是一个四元组(left, top, right, bottom)表示从原图左上角开始裁出这个矩形区域。这里的关键是坐标系原点在左上角x 轴向右y 轴向下跟数学坐标系是反的跟屏幕显示习惯一致。img Image.open(example.jpg) # 裁出左上角 400x300 的区域 cropped img.crop((0, 0, 400, 300))right和bottom是开区间还是闭区间实测下来crop 的区域是左闭右开的也就是(0, 0, 400, 300)裁出来的图宽度正好是 400高度正好是 300。这个细节在拼接图片或者做精确对齐时很重要差一个像素就可能出现缝隙或者重叠。裁剪最容易踩的坑是坐标越界。如果你给的 right 或 bottom 超过了原图尺寸Pillow 不会报错而是用黑色或者透明填充超出部分。这个行为有时候是特性有时候是 bug——做图片补边时可以利用它但如果你本意是裁出有效区域越界就会得到一张带黑边的图。所以裁剪前最好先确认一下img.size或者用min()把边界卡住。我处理过一批用户上传的头像需求是统一裁成正方形。思路是先取宽高里较小的那个作为边长然后从中心裁w, h img.size side min(w, h) left (w - side) // 2 top (h - side) // 2 square img.crop((left, top, left side, top side))这段代码很实用建议直接抄走。中心裁剪是头像、缩略图场景的标配操作。3.2 缩放resize 的插值算法怎么选缩放用resize()参数是目标尺寸的元组resized img.resize((800, 600))但这里有个大坑如果你只给目标尺寸图片会被强行拉伸到那个尺寸宽高比就变了人脸会变胖或者变瘦。正确做法是先算好等比缩放后的尺寸def resize_keep_ratio(img, target_width): w, h img.size ratio target_width / w target_height int(h * ratio) return img.resize((target_width, target_height))resize()还有个resample参数决定插值算法这个参数对画质影响很大很多人不知道就用了默认值。不同算法的对比如下算法适用场景特点Image.NEAREST像素风、需要保留硬边缘最快但缩放后锯齿明显Image.BILINEAR一般缩放速度和质量平衡默认值Image.BICUBIC缩小图片质量较好边缘更平滑Image.LANCZOS高质量缩小质量最好速度稍慢实测下来缩小图片比如从 4000px 缩到 800px用LANCZOS效果明显好于默认的BILINEAR细节保留更多摩尔纹更少。放大图片则要谨慎任何算法都救不回原本不存在的细节BICUBIC相对自然一些。做缩略图批量生成时我固定用LANCZOS虽然慢一点但值得。提示Pillow 新版本里Image.ANTIALIAS这个老名字已经被移除了如果你看到老教程里用它会报错统一换成Image.LANCZOS即可。3.3 旋转与翻转角度和方向的细节旋转用rotate()参数是角度逆时针为正rotated img.rotate(90)默认情况下旋转后图片尺寸不变超出原图范围的部分会被裁掉空出来的角落用黑色填充。如果你希望旋转后完整保留图片内容加expandTruerotated img.rotate(45, expandTrue)这样输出图片的尺寸会自动扩大以容纳旋转后的完整内容。做证件照旋转、扫描件纠偏时这个参数很关键不加的话四个角就被切掉了。翻转用transpose()配合几个常量img.transpose(Image.FLIP_LEFT_RIGHT) # 水平翻转左右镜像 img.transpose(Image.FLIP_TOP_BOTTOM) # 垂直翻转上下颠倒 img.transpose(Image.ROTATE_90) # 逆时针旋转 90 度这里有个容易混淆的点rotate(90)和transpose(Image.ROTATE_90)结果一样吗实测下来rotate(90)是逆时针转 90 度transpose(Image.ROTATE_90)也是逆时针转 90 度结果一致。但transpose的旋转是无损的直接重排像素速度快rotate走的是通用旋转逻辑任意角度都支持但 90 度这种特殊角度反而绕了远路。所以做 90 度整数倍旋转时优先用transpose效率更高。4. 色彩模式转换与调色从 RGB 到灰度再到各种增强4.1 色彩模式转换的常见需求convert()是模式转换的核心方法。最常见的场景是把彩色图转灰度gray img.convert(L)转灰度时 Pillow 用的是加权平均权重大致是红 0.299、绿 0.587、蓝 0.114这个权重来自人眼对不同颜色敏感度的差异——人眼对绿色最敏感对蓝色最不敏感。所以同样亮度的纯绿和纯蓝转成灰度后绿色会明显更亮。这个细节在做图像分析时要注意如果你需要的是简单的三通道平均得自己算。另一个常见转换是 RGB 转 RGBA给图片加透明通道rgba img.convert(RGBA)反过来 RGBA 转 RGB 时透明部分会被丢弃通常变成黑色。如果你想让透明部分变成白色背景得先创建一个白色底图再粘贴上去background Image.new(RGB, img.size, (255, 255, 255)) background.paste(img, maskimg.split()[3]) # 用 alpha 通道做蒙版这段代码处理带透明 PNG 转白底 JPEG 特别有用电商场景里商品图去透明底经常用到。4.2 用 ImageEnhance 做亮度对比度调整调色这块ImageEnhance模块提供了几个开箱即用的增强器比手动算像素方便太多from PIL import ImageEnhance enhancer ImageEnhance.Brightness(img) brighter enhancer.enhance(1.5) # 亮度提升 50% enhancer ImageEnhance.Contrast(img) more_contrast enhancer.enhance(1.2) # 对比度提升 20% enhancer ImageEnhance.Color(img) more_saturated enhancer.enhance(1.3) # 饱和度提升 30%enhance()的参数是个倍数1.0 表示不变小于 1 是减弱大于 1 是增强。这个接口设计很直观不用记复杂的公式。我做过一个批量提亮的脚本对偏暗的商品图统一enhance(1.3)效果立竿见影比手动调曲线快多了。但要注意增强是叠加的如果你对同一张图连续调用多次亮度增强效果会累积。批量处理时最好每次从原图重新开始避免反复增强导致过曝。4.3 灰度图与二值化的实际应用灰度图在 OCR、边缘检测、文档扫描这些场景里是标配预处理步骤。而二值化把灰度图变成纯黑白用point()方法配合阈值gray img.convert(L) # 像素值大于 128 变 255白否则变 0黑 binary gray.point(lambda x: 255 if x 128 else 0, mode1)这里的 128 是阈值实际用的时候要根据图片亮度分布调整。文档扫描件通常背景偏白文字偏黑阈值设在 150 到 180 之间效果比较好如果是照片类的图二值化往往损失大量信息要谨慎使用。我处理过一批扫描的表格图片原始图偏灰直接二值化后文字断断续续。后来加了一步先用ImageOps.autocontrast()自动拉伸对比度再二值化效果好很多。这个组合拳值得记住先自动对比度再二值化是文档图像预处理的经典套路。5. 批量处理与实战脚本把零散操作串成流水线5.1 批量处理的目录遍历与异常兜底单个图片操作会了之后真正的生产力来自批量。核心就是遍历目录、对每张图执行操作、保存到输出目录。但批量脚本最容易死在异常上——某张图损坏、格式不支持、权限不足整个流程就中断了。我的标准写法是给每张图的处理套上 try-except单张失败不影响整体import os from PIL import Image input_dir input output_dir output os.makedirs(output_dir, exist_okTrue) failed [] for filename in os.listdir(input_dir): if not filename.lower().endswith((.jpg, .jpeg, .png)): continue src os.path.join(input_dir, filename) dst os.path.join(output_dir, filename) try: with Image.open(src) as img: img img.convert(RGB) img.thumbnail((800, 800)) # 等比缩放到不超过 800x800 img.save(dst, quality85) except Exception as e: failed.append((filename, str(e))) print(f处理完成失败 {len(failed)} 张) for name, err in failed: print(f {name}: {err})这里用了thumbnail()而不是resize()区别在于thumbnail会原地修改图片并保持宽高比而且只缩小不放大——如果原图本来就小于目标尺寸它不会放大。这个行为在生成缩略图时正是我们想要的。另外用了with语句管理文件句柄确保图片对象用完及时释放批量处理大量图片时能避免内存泄漏。5.2 一个完整的实战脚本统一尺寸加水印把前面学的串起来写一个电商场景常见的脚本——把商品图统一成 800x800 白底方图右下角加文字水印from PIL import Image, ImageDraw, ImageFont import os def process_product_image(src, dst, size800, watermarkSAMPLE): with Image.open(src) as img: img img.convert(RGBA) # 等比缩放长边不超过 size img.thumbnail((size, size), Image.LANCZOS) # 创建白底画布 canvas Image.new(RGBA, (size, size), (255, 255, 255, 255)) # 居中粘贴 x (size - img.width) // 2 y (size - img.height) // 2 canvas.paste(img, (x, y), img) # 加水印 draw ImageDraw.Draw(canvas) try: font ImageFont.truetype(arial.ttf, 24) except OSError: font ImageFont.load_default() bbox draw.textbbox((0, 0), watermark, fontfont) tw bbox[2] - bbox[0] th bbox[3] - bbox[1] draw.text((size - tw - 20, size - th - 20), watermark, fill(150, 150, 150, 180), fontfont) canvas.convert(RGB).save(dst, quality88)这段脚本里有几个值得说的点。第一thumbnail之后图片尺寸可能小于画布所以用居中粘贴而不是直接 resize 拉伸避免变形。第二paste的第三个参数传了img作为蒙版这样带透明通道的图片粘贴时透明部分不会覆盖白底。第三字体加载用了 try-except因为不同系统字体路径不一样找不到就退回默认字体保证脚本不会因为字体问题崩掉。第四水印颜色用了带 alpha 的灰色半透明效果更自然不会太抢眼。注意textbbox是较新版本 Pillow 的方法老版本用的是textsize如果你在旧环境里跑报错换成draw.textsize(watermark, fontfont)即可。这类版本差异在跨环境部署时特别容易踩建议脚本里加个版本判断或者直接用兼容写法。5.3 内存与性能的实操心得批量处理几百上千张图时性能和内存是两个绕不开的话题。我踩过的坑总结几条第一及时释放图片对象。用with语句或者手动img.close()尤其是在循环里。我早期写脚本没注意这点处理几千张图后内存飙到几个 G最后被系统杀掉。第二大图先缩后处理。如果最终输出是 800px没必要在 4000px 的原图上做滤镜和绘制先thumbnail再处理速度能快好几倍。像素级操作的开销跟像素数量成正比这个优化立竿见影。第三JPEG 保存质量别盲目拉满。quality95 和 quality85 的文件大小可能差一倍但肉眼几乎看不出区别。批量场景下这个差异累积起来很可观存储和传输成本都受影响。第四多进程能提速但要小心。图片处理是 CPU 密集型任务用multiprocessing并行确实能提速但进程间传大图片对象有序列化开销而且 Pillow 对象不一定能直接跨进程传递。我的经验是图片数量多、单张处理快的时候并行收益明显单张处理本身就慢比如大图复杂滤镜时并行的收益会被开销吃掉。可以先小批量测试再决定要不要上并行。6. 常见报错与排查速查实际写脚本时报错信息往往不够直观这里整理一份我遇到过的高频问题对照表报错或现象可能原因解决方向ModuleNotFoundError: No module named PIL没装 Pillow 或装错环境确认虚拟环境pip install PillowUnidentifiedImageError文件损坏或不是图片检查文件完整性跳过异常文件保存 JPEG 报错cannot write mode RGBA as JPEGRGBA 图存 JPEG先convert(RGB)再存旋转后四角被切没加expandTrue旋转时加expandTrue缩放后人脸变形没保持宽高比先算等比尺寸或用thumbnail中文水印显示成方块字体不支持中文指定支持中文的字体文件批量处理内存暴涨图片对象未释放用with或手动close()ANTIALIAS属性不存在新版 Pillow 移除了旧名改用Image.LANCZOS中文水印那个问题特别值得展开说。ImageFont.load_default()加载的默认字体只支持 ASCII写中文会变成方块或者问号。解决办法是显式指定一个支持中文的字体文件路径比如系统里的黑体、宋体之类。但字体路径跨平台差异大Windows、macOS、Linux 各不相同所以脚本里最好做成可配置的或者用 try 多个候选路径。我一般会准备一个字体路径列表逐个尝试直到加载成功这样脚本换台机器也能跑。还有一个隐蔽的坑是图片的 EXIF 方向信息。手机拍的照片经常带一个 EXIF 旋转标记图片查看器会自动按标记旋转显示但 Pillow 打开时不会自动应用这个旋转。结果就是你在查看器里看是正的脚本处理出来却是躺倒的。解决办法是用ImageOps.exif_transpose()from PIL import ImageOps img ImageOps.exif_transpose(img)这一步在处理用户上传的照片时几乎是必加的不然批量输出的图方向乱七八糟。这个坑我踩过不止一次后来养成了习惯——凡是处理来源不明的照片打开后第一件事就是exif_transpose。7. 我对这套基本操作的理解用 PIL 的 Image 模块做图像处理说到底就是把一堆基础动作组合起来解决实际问题。打开、裁剪、缩放、旋转、调色、保存每个单独看都不复杂但真正拉开差距的是对细节的把握——坐标系的边界、插值算法的选择、色彩模式的转换、异常的处理、内存的管理。这些东西文档里都有但只有实际跑过几百张图、被各种报错折磨过之后才会变成肌肉记忆。我个人在实际操作中的体会是写图片处理脚本最忌讳一上来就追求一步到位的复杂逻辑。先把单张图的处理流程跑通确认每一步的输出符合预期再套上批量循环和异常兜底最后才考虑性能优化。这个顺序反过来做出了问题很难定位是哪一环的锅。另外养成随手保存中间结果的习惯处理到哪一步就存一张看看比盯着代码猜哪里错了高效得多。图像处理是所见即所得的事眼睛比日志更可靠。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询