欢迎光临
我们一直在努力

Python Pillow图像处理实战:从基础绘图到分形渲染

做图像处理这么多年,我越来越离不开的一个工具就是 Python 的 Pillow 库。很多人一听“计算机图形学”就觉得是高深的数学和图形渲染,其实我们日常遇到的大量需求——批量改图片、生成验证码、给照片加水印、把数据变成可视化图形、甚至是自己动手写一个简单的光栅渲染器——都可以用 Pillow 轻松实现。它本质上是经典 PIL(Python Imaging Library)的延续版本,保留了简洁的 API,同时又修复了多年不更新带来的兼容性问题。如果你和当初的我一样,手里有一堆图片需要处理,或者想用自己的代码在屏幕上“画”出点什么,那这篇分享就是为你准备的。我会从环境配置、基础绘图、图像合成,一直讲到用像素级操作做函数曲线和分形渲染,把我会踩的坑和用着顺手的方法都写在里面。

1. 图像基础与Pillow环境准备

1.1 为什么选择 Pillow 而不是其他图像库

在开始写代码之前,我想先说清楚一个经常被问到的选择题:图像处理用 Pillow、OpenCV 还是 Matplotlib?

如果你需要做视频流处理、摄像头实时采集、或者大量用到边缘检测、特征点匹配这类高级视觉算法,OpenCV 是更合适的。但它依赖的底层库较多,安装体积大,学习曲线也陡。而 Matplotlib 更适合做数据图表呈现,虽然也能生成图像,但它的定位是“绘图展示”,不是像素级的图像编辑。

Pillow 正好卡在中间:它是一个纯粹的 2D 光栅图形处理库,安装简单,支持几十种图像格式的读写,能处理裁剪、缩放、旋转、滤镜、合成,还能用 ImageDraw 在图像上绘制各种基本图元。它的设计哲学就是“轻量、直接、够用”。做批量图片处理脚本、生成图片素材、调试图形算法原型,Pillow 是首选。很多老项目里

import PIL

,指的就是这个库,因为 Pillow 保留了 PIL 的包名。这一点经常让新手困惑,后面我会专门提。

计算机图形学这个概念听起来很大,但其实 Pillow 覆盖的正是其中的 2D 光栅化部分:把几何描述(点、线、圆、文字)转换成屏幕上的像素,以及对这些像素进行各种变换和处理。想要理解更底层的渲染管线,用 Pillow 做实践是个特别好的入门方式。

1.2 安装与图像文件读写

安装实在太简单了,一条命令:

pip install Pillow

如果是在 Windows 上,请确保你的 Python 环境变量已经配置好,否则

pip

命令可能找不到。遇到下载慢的情况,可以加上国内镜像源,比如清华源:

pip install Pillow -i https://pypi.tuna.tsinghua.edu.cn/simple

装完之后验证一下版本:

from PIL import Image
import PIL
print(PIL.__version__)

Pillow 最基础的两个函数是

Image.open()

和

Image.save()

。这里有个新手容易踩坑的点:

Image.open()

是惰性加载的,它不会一次性把像素数据全部读进内存,只有在调用

load()

、

save()

或者强制转换时才会真正读取文件内容。这就意味着如果你打开了文件却没有用完就关闭文件句柄,后续再操作图像时可能会抛异常。

我习惯的做法是打开后立刻调用

load()

把数据载入内存,或者用

with

语句保证资源释放:

from PIL import Image

with Image.open("example.png") as im:
im.load()
im.thumbnail((400, 400))
im.save("example_thumb.jpg", quality=90)

save()

的时候,Pillow 会根据文件扩展名自动判断输出格式,也可以直接用第二个参数手动指定,比如

im.save("output", format="PNG")

。

1.3 图像模式与坐标系

理解 Pillow 的图像模式是后续一切操作的基础。最常见的三个模式是:

  • RGB

    :三通道彩色图,每个像素三个字节。

  • RGBA

    :多了一个 Alpha 透明通道,适合处理 PNG 和带透明效果的合成。

  • L

    :灰度图,每个像素一个字节,取值范围 0 到 255。

图像内部还有一个“坐标系”的概念。Pillow 使用标准的图像坐标系:左上角是原点

(0, 0)

,水平向右是 x 方向,垂直向下是 y 方向。这一点和我们在数学课上学到的直角坐标系不同,后者 y 轴向上。初学者如果忘记这一点,画出来的图形很可能是上下颠倒的。

创建一张新图像的基本写法:

from PIL import Image

# 创建一个 800×600 的白色画布
img = Image.new("RGB", (800, 600), "white")

第一个参数是模式,第二个是尺寸(宽、高),第三个可以是颜色名称、十六进制字符串或者 RGB 元组。这张画布一旦创建,就代表一个可以自由绘制的二维像素空间了。

2. 用 Pillow 绘制基础图形:把脑子里的图像变成像素

2.1 ImageDraw 与基本图形绘制

图像光有画布不会自己产生内容,真正往上面画图的是

ImageDraw

模块。它提供的接口特别直观,就像在纸上用不同颜色的笔画线、画矩形、画椭圆。

from PIL import Image, ImageDraw

img = Image.new("RGB", (600, 400), "#FFFFFF")
draw = ImageDraw.Draw(img)

# 画一条红色线段
draw.line((50, 50, 550, 50), fill="#FF0000", width=3)

# 画一个蓝色空心矩形
draw.rectangle((100, 100, 300, 200), outline="#0000FF", width=2)

# 画一个绿色实心圆
draw.ellipse((350, 100, 450, 200), fill="#00FF00", outline="#000000")

# 画多边形
draw.polygon([(100, 300), (200, 250), (300, 300)], fill="#FFAA00")

img.save("draw_basic.png")

这里有个细节:

ellipse()

的入参是一个包围圆的矩形区域,而不是圆心和半径。想画一个半径为 50、圆心在

(200, 200)

的圆,就要传入

(150, 150, 250, 250)

。很多从其他图形库转过来的人会在这里卡住,我当初也对着文档愣了半天。

rectangle()

和

ellipse()

支持的参数组合是一样的:

fill

指填充色,

outline

指边框色,

width

指边框宽度。如果你想要半透明效果,图像模式必须是

RGBA

,颜色也要带 alpha 值,比如

fill=(255, 0, 0, 128)

。

2.2 文字与字体渲染

图像上写文字也是高频需求,比如给图片加水印、生成海报标题、做验证码。Pillow 的文字绘制函数在

ImageDraw.text()

,但它默认的字体比较朴素,而且在不同操作系统上表现不一致。我的建议是直接用本地字体文件或者常见的系统字体路径。

from PIL import Image, ImageDraw, ImageFont

img = Image.new("RGB", (500, 200), "white")
draw = ImageDraw.Draw(img)

# 加载一个 32 号字体,Windows 下常见路径
font = ImageFont.truetype("C:/Windows/Fonts/arial.ttf", 32)

# 先测量文字宽度,方便让文字居中
bbox = draw.textbbox((0, 0), "Hello Pillow", font=font)
text_width = bbox[2] – bbox[0]
text_height = bbox[3] – bbox[1]

x = (500 – text_width) // 2
y = (200 – text_height) // 2
draw.text((x, y), "Hello Pillow", fill="black", font=font)

img.save("text_centered.png")

新版 Pillow 里

textsize()

已经标记为弃用,推荐使用

textbbox()

或者

textlength()

。如果不小心用了旧方法,控制台上会看到 DeprecationWarning,虽然不影响运行,但终归不够干净。

字体还有一个隐藏的坑:渲染中文时如果指定英文字体,会出现方框或乱码。解决办法很简单,选择一个支持中文的字体,比如 Windows 下的

simhei.ttf

或者

msyh.ttc

,Linux 下用

wqy-microhei.ttc

。另外,

ImageFont.truetype()

需要的是文件路径,不是系统中注册的字体名称,所以跨平台项目最好把字体文件放到项目目录里,然后用相对路径加载。

2.3 像素级操作:自己动手写光栅算法

前面说的都是高层接口,真正理解计算机图形学还要接触像素操作。Pillow 提供了

getpixel()

和

putpixel()

两个方法,但底层效率不算高,原因在于每个像素的取值都涉及一次 Python 函数调用。如果你图省事,可以用

Image.load()

返回的数组对象,它在大多数情况下比

getpixel()

快不少。

下面这个例子是用像素循环给图像做一个渐变背景:

from PIL import Image

width, height = 800, 400
img = Image.new("RGB", (width, height))
pixels = img.load()

for y in range(height):
for x in range(width):
# 从左到右,红色分量逐渐增强
pixels[x, y] = (x * 255 // (width – 1), 0, 128)

img.save("gradient.png")

运行完你会发现,这张图片从左到右由黑色逐步变成红色,非常直观。用

putpixel

画圆也很简单,只要判断像素点到圆心的距离是否小于半径就行:

import math
from PIL import Image

img = Image.new("RGB", (400, 400), "white")
pixels = img.load()
cx = cy = 200
radius = 150

for y in range(400):
for x in range(400):
distance = math.sqrt((x – cx) ** 2 + (y – cy) ** 2)
if distance <= radius:
pixels[x, y] = (0, 0, 255)

img.save("circle.png")

这就是光栅化的基本形态:遍历屏幕上的每个像素,判断它是否属于某个几何形状,再决定颜色。虽然 Pillow 本身已经有画圆的高层方法,但理解这个判断逻辑后,你就能自己扩展出各种复杂图形。

3. 图像处理与合成:批量操作的高级场景

3.1 几何变换:裁剪、缩放、旋转与翻转

Pillow 的几何变换非常顺手,几乎每个绘图脚本都会用到。先说缩放,

resize()

和

thumbnail()

都是改变图像尺寸,但行为不同:

  • resize()

    会严格把图片变成你指定的尺寸,不考虑宽高比。

  • thumbnail()

    是在保持宽高比的前提下,把图片缩小到指定范围内,只会变小不会变大。

from PIL import Image

im = Image.open("big_photo.jpg")
print("原始尺寸:", im.size)

# 强制缩放成 400×300
im_resized = im.resize((400, 300))
im_resized.save("resized.jpg")

# 保持宽高比,缩放到最大边为 400
im_thumb = im.copy()
im_thumb.thumbnail((400, 400))
im_thumb.save("thumbnail.jpg")

旋转和翻转也很常用。

rotate()

默认不会扩展画布,如果旋转 45 度,图片四个角会被裁剪掉。想要完整显示旋转后的图片,要加上

expand=True

参数,同时可以指定背景填充色。

im_rotated = im.rotate(45, expand=True, fillcolor="white")
im_rotated.save("rotated.jpg")

# 水平翻转
im_flip = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
im_flip.save("flipped.jpg")

注意循环旋转同一个图片对象时要小心累积误差,最好的做法是每次从原始图像拷贝一份再旋转。

3.2 滤镜与增强:ImageFilter 与 ImageEnhance

Pillow 内置了不少滤镜,在

ImageFilter

模块里。最常用的是高斯模糊、轮廓提取和边缘增强。

from PIL import Image, ImageFilter

im = Image.open("photo.jpg")

# 高斯模糊
blurred = im.filter(ImageFilter.GaussianBlur(radius=5))
blurred.save("blurred.jpg")

# 轮廓提取(可以拿来做边缘检测演示)
outline = im.filter(ImageFilter.CONTOUR)
outline.save("outline.jpg")

# 浮雕效果,做文字水印背景挺好用
embossed = im.filter(ImageFilter.EMBOSS)
embossed.save("embossed.jpg")

如果你觉得图片太暗或者太淡,可以用

ImageEnhance

模块调整亮度、对比度、色度和锐度:

from PIL import Image, ImageEnhance

im = Image.open("photo.jpg")

enhancer = ImageEnhance.Brightness(im)
im_bright = enhancer.enhance(1.3) # 亮度提升 30%

enhancer = ImageEnhance.Contrast(im_bright)
im_contrast = enhancer.enhance(1.2)

enhancer = ImageEnhance.Sharpness(im_contrast)
im_sharp = enhancer.enhance(2)

im_sharp.save("enhanced.jpg")

实际批量处理时,这类操作常常被封装在一个循环里,对一批图片统一增强,做成批量滤镜工具。

3.3 图像合成与蒙版:让两张图无缝融合

合成操作在图像处理中特别重要,最常见的是把带透明背景的 PNG 贴到另一张背景图上。Pillow 提供了

paste()

方法,第三参数传入 PNG 的 alpha 通道作为蒙版,就能实现透明融合:

from PIL import Image

background = Image.open("background.jpg").convert("RGBA")
overlay = Image.open("logo.png").convert("RGBA")

# 把 logo 缩小
overlay.thumbnail((200, 200))

# 计算粘贴位置,比如放在右下角
pos = (background.width – overlay.width – 50,
background.height – overlay.height – 50)

# 利用 overlay 自身的 alpha 通道当蒙版
background.paste(overlay, pos, overlay)

# 如果背景需要保存为 JPEG,记得转回 RGB
result = background.convert("RGB")
result.save("composite.jpg")

这里容易翻车的地方有三个:

  • 背景图如果不是 RGBA 模式,

    paste()

    可能报错或忽略透明通道,最好先统一转换为

    RGBA

    。

  • paste()

    的目标位置只接受一个二元组

    (x, y)

    ,不接受四个坐标值那个版本跟

    ImageDraw

    的矩形参数不一样。

  • 保存为 JPEG 时不支持透明通道,必须转成 RGB,否则会出现黑底或者报错。
  • 如果做的是两张图之间真正的像素级融合,可以用

    Image.composite()

    ,它的参数是两张图和一个 mask 图,三者尺寸和模式必须完全一致:

    from PIL import Image, ImageDraw

    im1 = Image.new("RGB", (300, 300), "red")
    im2 = Image.new("RGB", (300, 300), "blue")

    # 创建一个从中心向外的圆形渐变 mask
    mask = Image.new("L", (300, 300), 0)
    mask_draw = ImageDraw.Draw(mask)
    mask_draw.ellipse((75, 75, 225, 225), fill=255)

    result = Image.composite(im1, im2, mask)
    result.save("composite_circle.jpg")

    Image.composite()

    的每个像素相当于根据 mask 的灰度值做线性插值:mask 为 255 的位置完全取 im1,为 0 的位置完全取 im2。理解这个逻辑之后,你还能自由扩展出任意形状的渐变融合。

    4. 实战:从零生成一张函数曲线图(模拟图形学绘图管线)

    4.1 需求与流程拆解

    理论聊了不少,下面我们来一个完整实战:用 Pillow 生成一张带网格、坐标轴的正弦函数曲线图。这看起来像 Matplotlib 的活,但往深了说,它正好体现了一个简单的图形学渲染流程:

  • 世界空间(World Space)

    :函数定义域和值域,比如 x 从 0 到 4π,y 从 -1 到 1。

  • 屏幕空间(Screen Space)

    :图片的像素坐标系,宽 800,高 450。

  • 坐标映射(Projection)

    :把世界坐标映射到像素坐标,核心是一对线性变换函数。

  • 光栅化(Rasterization)

    :把连续曲线离散成大量采样点,再连成折线,这就是所有绘图库画连续函数的基础原理。

  • 我不直接贴一个调好的“一行代码画图”的工具,而是把底层逻辑拆开给你看,这样你之后需要画任意自定义坐标系时,都能自己写出来。

    4.2 坐标映射与曲线光栅化核心代码

    先定义两个坐标转换函数,把数学上的

    (x, y)

    变成图像上的像素

    (px, py)

    。注意图像坐标系 y 向下,所以要在 y 方向做一次翻转映射,否则正弦函数会倒过来显示。

    import math
    from PIL import Image, ImageDraw

    width, height = 800, 450
    img = Image.new("RGB", (width, height), "white")
    draw = ImageDraw.Draw(img)

    # 世界坐标范围
    x_min, x_max = 0, 4 * math.pi
    y_min, y_max = -1.2, 1.2

    def world_to_pixel(x, y):
    px = (x – x_min) / (x_max – x_min) * (width – 1)
    py = (y_max – y) / (y_max – y_min) * (height – 1)
    return int(px), int(py)

    # 1. 绘制网格线
    for gx in range(x_min, x_max + 1):
    if gx % 1 == 0:
    px, _ = world_to_pixel(gx, y_min)
    draw.line((px, 0, px, height – 1), fill="#E0E0E0", width=1)

    for gy in range(-1, 2):
    _, py = world_to_pixel(x_min, gy)
    draw.line((0, py, width – 1, py), fill="#E0E0E0", width=1)

    # 2. 绘制坐标轴
    x_axis_py = world_to_pixel(x_min, 0)[1]
    draw.line((0, x_axis_py, width – 1, x_axis_py), fill="black", width=2)
    y_axis_px = world_to_pixel(0, y_min)[0]
    draw.line((y_axis_px, 0, y_axis_px, height – 1), fill="black", width=2)

    # 3. 正弦曲线:密集采样,连成折线
    points = []
    for i in range(width * 2):
    x = x_min + (x_max – x_min) * i / (width * 2 – 1)
    y = math.sin(x)
    px, py = world_to_pixel(x, y)
    points.append((px, py))

    if len(points) > 1:
    draw.line(points, fill=(255, 0, 0), width=2)

    img.save("sin_plot.png")
    print("图像已生成")

    这段代码里最核心的思路就是“折线逼近曲线”:把函数曲线看成无数密集的小线段,采样点越多,曲线越平滑。理论上直接用所有采样点调用一次

    draw.line(points…)

    比逐条线段画更快,Pillow 会对首尾相连的点自动连线。

    还有一个值得注意的地方:我在网格里控制循环变量用了

    range

    ,但由于

    x_min

    和

    x_max

    带 π,直接用

    range(x_min, x_max)

    会报类型错误。实际写的时候最好用整数索引循环,然后换算成 x 坐标值,避免浮点数比较的精度问题。

    4.3 进阶:用像素操作实现曼德博集合

    如果你觉得画函数曲线还不过瘾,我们来个真正的分形渲染——曼德博集合。这东西在计算机图形学里是经典案例:一个极其简单的迭代公式,却能在二维平面上生成无限复杂的图案,非常适合用来理解“逐像素着色”的概念。

    曼德博集合的判断规则很简单:复平面上的点

    c

    ,从

    z=0

    开始不断迭代

    z = z² + c

    ,如果迭代若干次后

    z

    的模长不超过 2,就认为这个点属于集合。我们要做的就是把图片的每个像素映射到复平面的坐标区域,然后迭代计算并涂色。

    直接循环

    putpixel

    会慢到你想砸电脑,所以我用列表先存储全部像素,最后用

    putdata()

    一次性写入图像,速度能提升好几倍。

    from PIL import Image

    width, height = 800, 600
    max_iter = 100

    x_min, x_max = -2.2, 0.8
    y_min, y_max = -1.2, 1.2

    pixels = []

    for py in range(height):
    for px in range(width):
    # 像素坐标 -> 复平面坐标
    c_real = x_min + (x_max – x_min) * px / (width – 1)
    c_imag = y_min + (y_max – y_min) * py / (height – 1)

    z_real, z_imag = 0.0, 0.0
    iter_count = 0

    while z_real * z_real + z_imag * z_imag <= 4.0 and iter_count < max_iter:
    # z = z^2 + c
    z_real, z_imag = (
    z_real * z_real – z_imag * z_imag + c_real,
    2 * z_real * z_imag + c_imag,
    )
    iter_count += 1

    if iter_count == max_iter:
    pixels.append((0, 0, 0)) # 集合内部为黑色
    else:
    # 简单着色:迭代次数映射到灰度
    gray = int(iter_count * 255 / max_iter)
    pixels.append((gray, gray, 255))

    img = Image.new("RGB", (width, height))
    img.putdata(pixels)
    img.save("mandelbrot.png")

    这个例子把前面说的像素操作发挥到了极致。你可能会注意到迭代次数的映射用了蓝色调,看起来更漂亮。实际渲染时间取决于

    max_iter

    和图像尺寸,800×600 下大约需要几秒到十几秒,已经完全可以接受了。如果想更快,可以换成 numpy 向量化计算,但那就是另一个故事了。

    5. 常见问题与排查技巧实录

    5.1 颜色与 Alpha 通道的坑

    Pillow 操作中关于透明通道的报错和异常现象绝对能排进新手问题前三名。最常见的现象是:一张带透明背景的 PNG 图片用

    save()

    保存成 JPG 后,透明区域变成了黑色。这是因为 JPEG 格式本身不支持透明度,Pillow 在转换时会把 RGBA 图像的 alpha 通道简单丢弃。

    正确做法是在保存前显式转换:

    img = Image.open("with_alpha.png").convert("RGBA")
    # 做一些透明合成操作…
    background = Image.new("RGB", img.size, "white")
    background.paste(img, (0, 0), img.split()[3])
    background.save("no_alpha.jpg")

    上面这种“先铺白色背景,再把原图贴上去”的做法能保留透明区域的视觉白底效果。如果你直接

    .convert("RGB")

    ,透明像素的 RGB 分量是本来的颜色,但 alpha 信息没了,最终看到的可能是黑色或奇怪的边缘颜色。

    另一个与 alpha 有关的坑出现在

    Image.blend()

    和

    Image.composite()

    里:两张图的模式和尺寸必须一致,否则会直接抛

    ValueError

    。我在写合成脚本时习惯先做一次断言:

    assert im1.mode == im2.mode, f"模式不一致: {im1.mode} vs {im2.mode}"
    assert im1.size == im2.size, f"尺寸不一致: {im1.size} vs {im2.size}"

    5.2 字体与文字溢出问题的排查

    关于字体的排查,我总结过一套组合拳。中文字体乱码,先用

    fonts

    文件夹下的系统字体列表确认 TTF 文件路径是否存在,然后检查编码,Pillow 的

    text()

    默认按 Unicode 处理,一般不会出现编码问题,但如果你读取的文本文件编码不对,或者用了

    bytes

    字符串,就会显示成乱码。

    文字位置不对是我经常遇到的问题。Pillow 绘图坐标的锚点(anchor)默认是左上角,但不同字体、不同字号下文字的边距不同,直接用

    (0, 0)

    写文字会导致视觉上上下偏。解决办法是用

    textbbox()

    准确测量文字包围盒,或者指定

    anchor="mm"

    让锚点定位在文字中心。后者在需要多行文字居中对齐时非常好用。

    绕开字体文件路径的跨平台问题,有一个取巧方案:把字体文件放进项目目录,用

    pathlib

    定位:

    from pathlib import Path
    from PIL import ImageFont

    font_path = Path(__file__).parent / "fonts" / "SourceHanSansSC-Regular.otf"
    font = ImageFont.truetype(str(font_path), 28)

    5.3 性能优化:Pillow 绘图慢怎么办

    Pillow 本身是 C 语言实现的,但 Python 层的循环却很慢。如果你需要处理超大尺寸图片,或者对每个像素做复杂计算,前面提到的

    putdata()

    是明智之选。另一个技巧是用

    ImageDraw.line()

    一次性画多段线,而不是在循环里反复调用

    draw.point()

    。

    举个例子,我以前写过一个像素扩散模拟,在 1000×1000 的图片里逐帧更新每个像素,一开始用

    putpixel

    ,渲染一帧要好几秒;后来改成维护一个

    pixel_list

    ,最后一帧用

    putdata()

    批量写,时间立刻降到 0.3 秒。如果你觉得

    putdata()

    不够自由,还可以把 PIL 的

    Image

    对象转成字节数组,用

    struct

    或者

    array

    模块操作极快。

    还有一种常见的性能问题是

    resize()

    大图缩略图时,如果使用默认的

    resample=Image.BICUBIC

    ,处理几十兆的图片会比较慢。可以用

    resample=Image.LANCZOS

    换取质量,但如果只是预览,选

    Image.NEAREST

    会更快。

    最后提醒一下,当你在循环中反复打开同一张图片做裁剪时,建议先

    copy()

    一份到内存里操作,避免反复读磁盘。

    5.4 安装与环境问题速查

    Pillow 的安装问题层出不穷,我整理一个速查表,遇到类似问题直接对着看:

    问题现象

    可能原因

    解决办法

    ModuleNotFoundError: No module named 'PIL'

    没安装 Pillow,或安装到了其他 Python 环境 检查当前解释器,执行

    pip install Pillow

    安装报错

    Could not find a version…

    pip 版本太老或 Python 版本不兼容 升级 pip:

    python -m pip install –upgrade pip

    SecurityError: …

    或 JPEG 相关的报错

    系统缺少 JPEG/PNG 支持库 Linux 先安装

    libjpeg-dev

    、

    zlib1g-dev

    ,再重装 Pillow

    导入后版本号很低,比如 7.x 还是老 PIL 或旧版 Pillow

    pip uninstall PIL -y

    后重装,

    pip install -U Pillow

    写中文文本时方框 字体不支持中文 换成中文字体文件,如

    simhei.ttf

    、

    msyh.ttc

    paste()

    后透明区域是黑色

    mode 不是 RGBA,或当成 JPEG 保存 统一转 RGBA,保存时转回 RGB

    这里我想多提一句“PIL”和“Pillow”的关系。PyPI 上的

    PIL

    已经停止维护很多年了,现在你在

    pip install PIL

    时看到的大多是历史版本,不要安装。正确的包名是

    Pillow

    ,但导入时仍然写作

    from PIL import xxx

    ,因为 Pillow 延续了 PIL 的命名空间。只要记住这个规则,环境问题基本就躲开了一半。

    在实际工作中,我还会给批量处理脚本加一个定时输出,比如每处理 1000 张图片打印一次进度。别小看这个习惯,遇到图片数量多、单张几十兆的场景,它能帮你及时发现问题,而不是让程序默默跑到一半卡住。

    做图像和图形学相关的小工具,Pillow 给我最大的感觉就是“透明”——一切都围绕像素和坐标展开,没有太多魔法。你画一条线,就是真的在一张像素网格上逐点落定颜色;你写一个分形,就是真的在逐像素做迭代。这种贴近底层的特质,对想深入图形学的人来说其实是很好的学习路径。万一你在实践过程中踩到了这篇文章里没提到的坑,不妨先回去检查一下模式、坐标和尺寸这三个基础量,八成问题都出在那里。

    赞(0)
    未经允许不得转载:171主机测评 » Python Pillow图像处理实战:从基础绘图到分形渲染
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址