ARTICLE DETAIL

资讯详情

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

Python图像处理入门:用Pillow从像素操作到图形学实战

Python图像处理入门:用Pillow从像素操作到图形学实战 开篇先问个问题你接触Python后的第一张图片处理代码是不是这样写的from PIL import Image img Image.open(test.jpg) img.show()如果是那你其实已经踩在了计算机图形学的门槛上。Pillow这个库前身叫PILPython Imaging Library2009年原项目停更后由社区接手做成了活分支名字改成Pillow但import的时候还是用from PIL import ...。我第一次用的时候也纳闷明明装的是Pillow为什么导入的是PIL这个后面细说。电脑里的图片、屏幕上的画面、游戏里的建模渲染本质上都是计算机图形学的范畴。而对绝大多数Python开发者来说Pillow就是进入这个领域最平滑的一块跳板。它不需要你会线性代数不需要装什么几百兆的依赖一条pip install Pillow就能上手。这篇内容想解决的不只是教你怎么调用几个API而是带你建立一套“图像即矩阵、操作即算法”的图形学直觉。适合刚学完Python基础语法、想找个看得见摸得着的方向练手的同学也适合后续要转OpenCV、做图像处理的初学者先打个底。我自己在折腾图像处理的路上踩过不少坑比如滤镜参数调了半天没效果、大图处理直接把内存干爆、jpg格式反复保存后画质惨不忍睹。这些经验普通文档里不会写我会一并整理在最后。你把这篇文章当一份踩坑笔记看会省下不少时间。1. 为什么拿Pillow做图形学入门1.1 先搞清楚Pillow和PIL的关系很多新手在安装环节就开始懵。pip install Pillow装完之后写代码却是from PIL import Image。这里面的历史原因并不复杂PIL是Python早期最知名的图像处理库后来维护者弃坑了社区fork了一份继续开发为了避免和原项目混淆新的发行包叫Pillow但代码里的模块名保持PIL不变。这带来的实际影响有两个。第一网上老教程里写的import Image这种写法是PIL早期版本的习惯现在直接运行会报ModuleNotFoundError。正确的做法永远是from PIL import Image。第二如果你看到某个报错说找不到PIL模块大概率不是没装Pillow而是你的运行环境不对——装了Python 3.9却在3.11的解释器里跑或者VSCode里选择的解释器和pip对应的不是同一个。这是我被问过最多的问题。1.2 Pillow在图形学学习里能承担什么角色计算机图形学这个概念很大往深了走有光栅化、光线追踪、着色模型、几何变换一堆硬核内容。但对入门者来说最直观的入口就是光栅图形学的基本操作图像的读取与存储、像素点的修改、颜色的表示与变换、几何变换、图像滤波与合成。这些恰好全部落在Pillow的能力范围里。相比直接上OpenCVPillow的优势是轻和直白。OpenCV里一幅图是一个numpy矩阵各种操作封装得很细参数多到看不懂Pillow则有一套更贴近“图像”本身的对象模型打开一张图就是一个Image对象缩放、旋转、裁剪都是对象上的方法。你不用先搞清楚BGR和RGB的区别OpenCV的经典坑Pillow的通道顺序和大多数人的直觉一致。我并不是说Pillow能替代OpenCV而是建议顺序别搞反。先用Pillow把“图像是像素矩阵”这个直觉建立起来再切换到OpenCV处理速度要求更高的场景会顺畅得多。Pillow在处理小图、做原型的场景下开发效率甚至比OpenCV还高代码也更好读。1.3 环境搭建Python与Pillow的安装细节安装环节踩的坑往往比写代码踩的还多。我的建议是别用系统自带的Python也别图省事直接全局pip install花两分钟用虚拟环境隔离一下之后能省心很多。如果你还在纠结Python装哪个版本选3.10或3.11就行没必要追新到3.13有些库的预编译wheel没跟上。VSCode里配置Python环境时重点是左下角解释器要选对否则终端里运行和编辑器里F5运行用的根本不是同一个环境pip装完的包当然找不到。# 创建并激活虚拟环境Windows python -m venv .venv .venv\Scripts\activate # 创建并激活虚拟环境macOS / Linux python -m venv .venv source .venv/bin/activate # 安装Pillow pip install Pillow # 验证是否装好 python -c from PIL import Image; print(Image.__version__)国内网络环境下载慢的话临时换一下镜像源就行pip install Pillow -i https://pypi.tuna.tsinghua.edu.cn/simple环境就绪后我建议你顺手装一个matplotlib后面把图像转成numpy数组做分析时用plt.imshow()预览比本地打开图片窗口方便很多尤其是调试阶段。2. 像素级操作从读图到改图2.1 图像的本质是数字矩阵图形学的第一课请忘掉“图片”这个概念把图像想象成一个二维数组。灰度图是一个二维矩阵每个值在0到255之间代表该点的亮度彩色图是三个二维矩阵叠在一起分别代表红、绿、蓝通道。Pillow中一幅典型的RGB图像结构是宽度 × 高度 × 3。注意高度在前面还是后面这个问题写成代码时很绕img.size返回的是(width, height)而numpy数组的shape是(height, width, channels)。第一次用Pillow把图像转成数组再去遍历方向搞反是家常便饭。看一张图的细节可以这样操作from PIL import Image img Image.open(test.jpg) print(img.size) # (1920, 1080)宽1920高1080 print(img.mode) # RGB 或 RGBA 等 print(img.format) # JPEG # 取某个像素点的颜色 pixel img.getpixel((100, 50)) print(pixel) # 例如 (120, 80, 60)2.2 用getpixel和putpixel实现灰度化与反色理解了图像是像素阵列之后第一个动手小实验是手动实现灰度化。灰度化最常用的公式是心理学亮度公式Gray 0.299 * R 0.587 * G 0.114 * B为什么不是简单取平均因为人眼对绿色最敏感对蓝色最不敏感。工业标准里用的就是这套加权系数。Pillow提供的convert(L)底层也是这个逻辑。from PIL import Image def to_grayscale(img): width, height img.size gray_img Image.new(L, (width, height)) for y in range(height): for x in range(width): r, g, b img.getpixel((x, y)) gray int(0.299 * r 0.587 * g 0.114 * b) gray_img.putpixel((x, y), gray) return gray_img img Image.open(test.jpg).convert(RGB) gray to_grayscale(img) gray.save(result_gray_manual.jpg)这段代码能跑但你马上会发现问题慢。拿1920×1080的图Python纯循环遍历大约两百万个像素点一幅图可能要卡好几秒。这不是Pillow的问题是Python解释器的天然瓶颈。所以生产环境基本不会这么写而是直接用img.convert(L)底层是C代码瞬间完成。不过作为学习手动写一遍双循环非常值得。它能让你真切感受到“每个像素都是独立处理的”这是后面理解卷积、滤波的基础。反色操作同理原理是对每个通道做255 - value。def invert(img): width, height img.size inverted Image.new(RGB, (width, height)) for y in range(height): for x in range(width): r, g, b img.getpixel((x, y)) inverted.putpixel((x, y), (255 - r, 255 - g, 255 - b)) return inverted2.3 用point和LUT批量处理像素手动双循环体验一次就够了实际工程里要用Pillow的point()方法。它接受一个函数对图像每个通道的每个像素值做映射。这个方法早就在C层面做了优化速度比Python循环快几个数量级。# 用point实现反色 inverted img.point(lambda v: 255 - v) # 用point实现暗部压缩提升对比度 enhanced img.point(lambda v: 255 if v 200 else int(v * 1.2))更进阶的用法是查找表LUTLook Up Table。因为像素值范围固定是0到255你完全可以把每个输入值对应的输出值预先算好存成一个长度为256的列表再一次性传入point()。这样复杂的非线性变换也能保持极快速度。# 构建一个gamma校正的LUTgamma0.7会让暗部细节更清楚 gamma 0.7 lut [int(255 * (i / 255) ** (1 / gamma)) for i in range(256)] img_gamma img.point(lut)这也是图形学里的经典技巧不要对每个像素做复杂运算而是预先算好映射表让像素处理退化成查表操作。游戏引擎里的颜色校正、色调映射大量用这个思路。2.4 像素操作的性能避坑像素级操作真正的坑不在写法而在模式不统一。一张JPEG图片打开后是RGB模式一张PNG可能是RGBA模式如果直接getpixel()返回的tuple长度不一样代码运行到一半才报错是常有的事。我的习惯是统一在操作前转格式img Image.open(input.png).convert(RGB)另一个容易忽略的问题是getpixel和putpixel本身的开销。每次调用都是一次Python到C层的函数跳转循环里反复调用尤其慢。如果确实需要在Python层逐像素处理正确姿势是先把图像转成字节序列一次取完处理完再一次性放回去# 快速获取像素数据 pixels img.load() # load()之后pixels[(x, y)] 的访问方式比getpixel快一些但本质还是逐次调用要更快就得借助numpy。把Pillow图像转numpy数组只需要一行import numpy as np arr np.array(img) # shape: (height, width, 3) # 向量化操作反色 arr_inverted 255 - arr # 再转回Pillow图像 img_inverted Image.fromarray(arr_inverted.astype(uint8))向量化操作的速度优势是碾压级的。处理一张千万像素的大图纯Python循环需要几十秒numpy一行代码几十毫秒完成。这也是为什么真实项目里Pillow和numpy总是搭配出现。3. 核心图形变换几何操作与颜色变换3.1 缩放、旋转与仿射变换的底细图形学里最基础的几何变换就三类平移、缩放、旋转。Pillow把它们封装成了简洁的方法但理解底层逻辑对你后续学OpenCV大有帮助。缩放操作# 指定目标尺寸 resized img.resize((800, 600)) # 指定缩放倍数 resized img.resize((int(img.width * 0.5), int(img.height * 0.5))) # 推荐用thumbnail保持原图比例 thumbnail img.copy() thumbnail.thumbnail((400, 400))resize和thumbnail的区别很关键。resize是硬缩放比例不对会拉伸变形thumbnail是等比缩放图片不会变形但尺寸可能小于你给的边界。做缩略图选后者。resize还有个参数叫resample控制重采样算法。默认是Image.BICUBIC双三次插值放大时质量较好缩小时用Image.LANCZOS质量更佳。图形学里的插值概念在此初露端倪目标像素的取值依赖周围像素不同采样算法决定了是平滑还是锐利。旋转操作# 逆时针旋转expandTrue会扩展画布避免裁切 rotated img.rotate(45, expandTrue, fillcolor(255, 255, 255))轮转角度必须是90的整数倍吗不是。rotate(45)是任意角度但任意角度的旋转在光栅图像上必然会引入插值误差所以旋转后的图像放大看会有轻微的模糊这属于正常现象。平移和更复杂的变换统一用transform方法需要传入仿射矩阵。仿射变换是图形学的核心概念之一它用一个3×3矩阵统一表达平移、旋转、缩放和错切from PIL import Image # 仿射变换矩阵前两行是变换矩阵 # 绕中心旋转30度并平移 import math angle 30 theta math.radians(angle) cos_a, sin_a math.cos(theta), math.sin(theta) # 这里以图像中心为旋转中心做变换 cx, cy img.width / 2, img.height / 2 affine_matrix (cos_a, sin_a, -cx * cos_a - cy * sin_a cx, -sin_a, cos_a, cx * sin_a - cy * cos_a cy) rotated img.transform((img.width, img.height), Image.AFFINE, affine_matrix, resampleImage.BICUBIC)这些矩阵推导看起来枯燥但它们是图形学绕不过去的部分。你不需要死记公式但要明白“变换是矩阵作用在坐标上的线性运算”这个本质。3.2 颜色空间转换与色彩映射计算机图形学中颜色远不止RGB一种表达。Pillow里最常用的一个是convert(L)做灰度化一个是convert(RGBA)加透明通道还有convert(HSV)把颜色从“红绿蓝”变成“色相-饱和度-明度”。为什么需要HSV举个实际场景你想把一张图片里某个特定颜色的物体提取出来。在RGB空间判断“红色”很困难因为红色在不同亮度下的RGB值差异巨大但在HSV空间里红色的色相Hue稳定落在一个区间只要判断H值就能轻松筛出来。hsv_img img.convert(HSV) h, s, v hsv_img.split() # s饱和度二值化提取高饱和区域 mask s.point(lambda x: 255 if x 128 else 0)色彩映射伪彩色在数据可视化中也很常用。把灰度图映射成热力图配色Pillow里可以自定义一个调色板from PIL import Image # 创建一张256色的调色板R,G,B依次排列 palette [] for i in range(256): # 做一个蓝-白-红的渐变 if i 128: palette.extend([int(i * 2), int(i * 2), 255 - int(i * 2)]) else: palette.extend([255, 255 - int((i - 128) * 2), 0]) gray img.convert(L) heat gray.convert(P) heat.putpalette(palette)这种“灰度转伪彩色”在医学影像、热成像分析里非常常见学会这一手将来做数据可视化时也是刚需技能。3.3 通道分离与合并一幅RGB图可以想象成三张灰度图叠在一起。Pillow提供了split()和merge()这两个方法在图形学实验里出镜率极高。r, g, b img.split() # 交换红蓝通道产生怪异的色彩偏移 merged Image.merge(RGB, (b, g, r)) # 单独提升红色通道强度 r_enhanced r.point(lambda v: min(255, int(v * 1.3))) merged Image.merge(RGB, (r_enhanced, g, b))这种通道操作是很多滤镜效果的基础。复古胶片色调本质就是降低蓝色通道的亮度、提高红色通道的亮度冷色调则是反过来。你不需要什么高科技滤镜库理解通道后自己就能调出想要的效果。alpha通道透明度也值得单独提。PNG图像相比JPEG最大的优势是支持透明alpha值同样是0到2550表示完全透明255表示完全不透明。合成两张图时alpha就是决定它们怎么叠放的权重。3.4 滤镜与卷积从模糊到边缘检测Pillow的ImageFilter模块覆盖了一系列内置滤镜用法简单到令人发指from PIL import ImageFilter blurred img.filter(ImageFilter.GaussianBlur(radius2)) sharpened img.filter(ImageFilter.SHARPEN)但真正有价值的是理解它背后的卷积操作。所谓卷积就是一个固定大小的窗口比如3×3在图像上滑动每个输出像素值等于窗口内像素与核的加权求和。这个核就是卷积核不同卷积核产生完全不同的效果。高斯模糊的核是一个中心权重高、周围逐渐衰减的矩阵边缘检测的Sobel算子则是一个不对称的核专门对水平或垂直方向的强度变化做响应。from PIL import ImageFilter # 自定义卷积核做边缘检测 edge_kernel ImageFilter.Kernel( size(3, 3), kernel[-1, -1, -1, -1, 8, -1, -1, -1, -1], scale1, offset0 ) edges img.convert(L).filter(edge_kernel)边缘检测是图形学和计算机视觉共用的基础算法很多“一键线稿化”的小工具底层就是这类卷积操作。理解卷积的核心思想——局部窗口的加权求和——之后你对GaussianBlur、SHARPEN这些内置滤镜就不再是黑盒使用而是知道它们各有各的卷积核。4. 计算机图形学核心概念从光栅化到合成4.1 ImageDraw绘图模块光栅化的最简样本图形学有一整个领域叫光栅化就是把矢量图形点、线、面转换成屏幕上的像素。Pillow的ImageDraw模块是这个流程的最小可用实现。from PIL import Image, ImageDraw # 创建一张空白画布 canvas Image.new(RGB, (800, 600), white) draw ImageDraw.Draw(canvas) # 绘制基础图元 draw.line((100, 100, 700, 100), fillblack, width3) draw.rectangle((200, 200, 600, 400), outlineblue, width2) draw.ellipse((300, 150, 500, 350), fillyellow, outlinered, width3) draw.polygon([(400, 100), (250, 300), (550, 300)], fillgreen) canvas.show()这里的核心概念是坐标系统图像左上角是(0, 0)x轴向右y轴向下。这和你学数学时的坐标系相反很多人第一次画图都在这里栽跟头图形垂直翻转就是这个原因。光栅化的关键一步是判断哪些像素属于这个图形。以画圆为例背后的算法叫Bresenham画圆法它是用整数运算避免浮点乘法逐点逼近出圆的轮廓。Pillow帮你封装好了但你得知道屏幕上看似平滑的弧线实际是一堆像素拼接的结果。ImageDraw画抗锯齿线条的表现一般放大看会有锯齿。中文教程里很少提这一点。如果你的场景需要漂亮的抗锯齿线用ImageDraw.line加jointcurve可以改善拐角或者干脆用aggdraw这一类第三方库。不过作为图形学入门Pillow自带的绘图已经够用来理解光栅化的基本逻辑了。4.2 贝塞尔曲线与文字渲染曲线绘制是图形学的进阶内容。ImageDraw支持通过line加curves参数画贝塞尔曲线虽然文档写得比较隐蔽但实际用起来很方便。draw.line([(100, 500), (250, 100), (400, 400), (700, 200)], fillpurple, width3, jointcurve)贝塞尔曲线是现代字体渲染、矢量图软件如Illustrator的核心。它的基本思想是用控制点“牵引”曲线走势而不是直接描述每个点的坐标。你在网上看到的很多“Python生成签名”“爱心曲线”教程底层都是贝塞尔曲线。文字渲染是另一个常用场景但有个大坑Pillow默认字体是位图字体尺寸特别小而且不能缩放。要正常渲染中文必须加载系统字体文件。from PIL import ImageFont # Windows系统字体路径 font ImageFont.truetype(C:/Windows/Fonts/msyh.ttc, size48) # macOS一般路径 # font ImageFont.truetype(/System/Library/Fonts/PingFang.ttc, size48) draw.text((100, 100), 计算机图形学, fontfont, fillblack)Linux服务器上经常遇到字体缺失导致中文变成方框的问题解决方案无非是安装中文字体包或者把字体文件复制到项目目录里用相对路径加载。4.3 图像合成alpha通道与混合模式图像合成就是把多张图合成为一张图的艺术。最基础的方式是pastebackground Image.new(RGB, (1024, 768), white) foreground Image.open(logo.png).convert(RGBA) # 把前景粘贴到指定位置 background.paste(foreground, (100, 100), maskforeground.split()[3])注意第三个参数mask它使用前景图的alpha通道作为蒙版这样透明区域就不会遮住背景。如果不传maskRGBA图的粘贴结果可能和你预期不符因为它是整块矩形贴上去的。更精细的合成需要混合模式blend或者说按像素做加权混合from PIL import ImageChops img1 Image.open(a.jpg).convert(RGB) img2 Image.open(b.jpg).convert(RGB) # 50%透明度混合 blended Image.blend(img1, img2, alpha0.5) # 正片叠底模拟投影效果 multiplied ImageChops.multiply(img1, img2)ImageChops.multiply背后的逻辑是pixel_out (pixel_1 * pixel_2) / 255这就是图形学中正片叠底混合模式的定义。初学阶段把这些公式亲手实现一遍比背一百个滤镜API都管用。4.4 简单动画帧制作GIF动画是图形学入门的趣味项目能直观看到“多帧连续播放产生运动”的原理也顺带理解视频的基本逻辑。from PIL import Image, ImageDraw frames [] for i in range(20): # 每帧新建一张图 frame Image.new(RGB, (300, 300), black) draw ImageDraw.Draw(frame) # 小球位置随时间变化 x 30 i * 12 y 150 int(50 * (1 if i % 2 0 else 0)) draw.ellipse((x - 20, y - 20, x 20, y 20), fillwhite) frames.append(frame) # 保存为GIF frames[0].save(bounce.gif, save_allTrue, append_imagesframes[1:], duration80, loop0)这个脚本里已经涵盖了动画的两个核心概念帧率和循环。duration80表示每帧显示80毫秒约合12.5帧每秒。低于10帧每秒时人眼能看到明显卡顿高于24帧每秒时看起来就比较顺滑了。理解了这套逻辑将来做大一些的可视化动效、甚至做视频处理的预处理脚本都能顺藤摸瓜。5. 综合实战写一个像素风火苗特效5.1 思路拆解做了这么多小的功能点最后把它们串起来做一个能看的综合案例像素风火苗特效。火苗的核心视觉特征可以总结为三点火苗底部亮、顶部暗从黄白色渐变到红色再到透明火苗形态不稳定会抖动和拉长火苗向上飘散在Pillow里实现一个简化版本思路是用随机噪声生成一列火焰的“高度”值高度越高亮度越低再把这个高度映射成颜色梯度。逐帧更新随机噪声叠加时间偏移做成动画。5.2 代码实现import random import math from PIL import Image, ImageDraw def generate_flame_frame(width80, height120, time_step0, max_particles60): # 创建火焰画布用调色板模式方便做颜色映射 img Image.new(P, (width, height)) # 构建火苗调色板从白黄到橙红到黑 palette [] for i in range(256): t i / 255 r int(min(1.0, t * 2.0) * 255) g int(max(0.0, 1.0 - abs(t - 0.5) * 2.0) * 180) b int(max(0.0, 1.0 - t * 1.5) * 60) palette.extend([r, g, b]) img.putpalette(palette) # 生成粒子 particles [] for _ in range(max_particles): # 粒子随机出现在底部区域带一点横向偏移 x random.randint(0, width - 1) y random.randint(int(height * 0.8), height - 1) # 每个粒子的生命值和上升速度 life random.random() speed 0.5 random.random() * 1.0 particles.append([x, y, life, speed]) pixels img.load() for x, y, life, speed in particles: # 根据生命值计算垂直位置生命值递减表示粒子向上移动 new_y int(y - time_step * speed * 30) if new_y 0: continue # 亮度与生命值挂钩越靠近顶部越暗 brightness int(255 * life * (1 - new_y / height)) if brightness 0: pixels[x, new_y] min(255, brightness) # 转回RGB方便显示 return img.convert(RGB) # 生成多帧合成GIF frames [] for step in range(30): frame generate_flame_frame(time_stepstep) frames.append(frame) frames[0].save(flame.gif, save_allTrue, append_imagesframes[1:], duration80, loop0)5.3 效果调试与参数调节这段代码能跑出动态的火苗效果但参数需要调。我自己测试时发现几个规律粒子数量max_particles太少火焰显得稀疏太多会糊成一片。60到80是视觉上比较舒服的范围。调色板设计决定火焰质感我的配色偏橙黄你可以试试把g通道的比重调高一些会偏亮白更像高温火焰把b通道打开一点会有蓝紫色内焰的感觉更接近真实的煤气灶火焰。火焰高度参数speed控制的是飘散速度速度太快像烟雾报警器误报太慢像慢动作视频。锯齿感是这个特效的天然属性反而给结果带上了像素风的味道这算是歪打正着。这个项目虽然小但把本章涉及的知识点都串起来了调色板做伪彩色映射、粒子系统的死亡判定、逐帧渲染合成动画。你在Pillow基础上改动它可以玩出很多花样比如把粒子换成雪花、飘叶、星空闪烁思路完全一样。6. 常见问题与排查技巧实录6.1 安装失败与导入报错遇到ModuleNotFoundError: No module named PIL九成情况是运行环境和安装环境不一致。你在终端里跑pip install Pillow装到了系统Python而VSCode里选了虚拟环境解释器两者互不相通。排查思路很简单# 在编辑器或终端里查看当前解释器路径 which python python -c import sys; print(sys.executable) # 查看这个解释器里是否装了Pillow python -c from PIL import Image; print(Image.__version__)如果确实装了还报错看看是不是虚拟环境没激活或者pip装到了--user目录。总之报错第一件事不是重装而是确认解释器路径。另外Linux下如果编译安装报错多半是缺了libjpeg-dev之类的系统库。我的建议是直接pip install Pillow装预编译版本不要从源码编译绝大多数场景下不需要自己编译。6.2 图像发红或发蓝的原因img.save()之后发现图片颜色不对最常见的场景是把RGBA模式的图像直接保存为JPG。JPEG不支持透明通道Pillow会自动丢弃alpha信息但预合成背景时如果没有指定fillcolor透明区域默认填充黑色结果就是一张黑底图。更隐蔽的是模式转换问题img Image.open(photo.png).convert(RGB)如果原图是CMYK模式比如从某些设计软件导出的图直接convert(RGB)有时会产生偏色。正确做法是先统一转成RGBA再转成RGB避免底层颜色空间的奇怪映射img Image.open(photo.tiff).convert(RGBA).convert(RGB)还有一个高频场景是ImageDraw画线画完发现颜色不对检查一下你有没有先给画布指定模式就开画。默认创建的Image.new(RGB, ...)正常但如果你从灰度图转来的画布draw时候传fillred可能并不生效因为调色板模式下的红色和你期望的RGB红对不上。遇到颜色异常先用img.mode看看当前模式。6.3 大图处理导致内存暴涨Pillow处理大图时内存压力很大因为它在解码后把整幅图的所有像素都加载到内存里。处理一张几千万像素的图占几百MB内存是正常情况。如果处理超大图导致程序崩溃有两条路。第一条路是用numpy做内存映射不要一次把整图读进内存。但这条路对Pillow支持有限更实用的方案是分块处理把大图切成若干小块逐块处理再拼回去。Pillow的crop和paste配合起来做这件事很顺。# 分块处理示例把图片切成4块分别调整亮度再拼回 block_w img.width // 2 block_h img.height // 2 blocks [] for i in range(2): for j in range(2): box (i * block_w, j * block_h, (i 1) * block_w, (j 1) * block_h) block img.crop(box).point(lambda v: min(255, int(v * 0.8))) blocks.append(block) # 再paste回新画布 new_img Image.new(RGB, img.size) new_img.paste(blocks[0], (0, 0)) new_img.paste(blocks[1], (block_w, 0)) new_img.paste(blocks[2], (0, block_h)) new_img.paste(blocks[3], (block_w, block_h))第二条路是用Pillow的ImageFile.LOAD_TRUNCATED_IMAGES和draft模式只加载缩略图但限制较大。真实项目里处理超大图更专业的方案是转用tifffile、rasterio这类面向地理或医学影像的库这里不展开。6.4 性能优化三板斧Pillow不是以性能见长的库但开发中的慢很多时候是你自己的锅。我总结了三步优化法。第一板斧确认是否需要全像素级处理。很多效果Pillow内置方法已经做了C级优化比如point()、convert()、内置滤镜都比手动循环快几个数量级。能用内置方法就不用getpixel。第二板斧接上numpy向量化。需要自定义复杂的逐像素逻辑时先np.array(img)转数组向量化计算结果再Image.fromarray()转回来。这一步带来的速度提升通常是一百倍起步。第三板斧降低分辨率做原型验证。调试算法时先用小图跑通逻辑再对原图跑最终版。比如用thumbnail((512, 512))预览效果确认无误后再全分辨率处理。这条看起来简单但真能帮你省下大量等待时间。Pillow之后的路线如果对图形学算法本身感兴趣可以深入学numpy图像处理再看OpenCV和scikit-image如果对图形学渲染理论更感兴趣沿着“像素操作-GIF动画-3D渲染”的方向去了解pygame的三维投影和raycasting会打开新世界。但建议先把Pillow吃透这里的很多概念——坐标系统、调色板、卷积核、alpha合成——是计算机图形学所有方向的通用地基。
返回列表