HOWTO · Matplotlib
Python 中将 NumPy 数组转换为 PIL 图像
本教程解释了我们如何使用 PIL 包中的 Image.fromarray()将 NumPy 数组转换为 PIL 图像。
本页内容
使用 Pillow 的 Image.fromarray() 可將 NumPy 数组转换为 PIL 图像。二维数组表示灰度像素;形状为 (height, width, 3) 或 (height, width, 4) 的数组分别表示 RGB 或 RGBA 像素。数组的 dtype 和数值也会影响转换,因此转换前请先检查。PIL 是常用的导入名称,Pillow 则是目前仍在维护的库。
要创建 RGB 图像,请生成一个包含三个通道的 uint8 数组,再将它传给 Image.fromarray()。输出会显示图像模式、以 (width, height) 表示的尺寸,以及左上角像素的值。
本节中的代码以及显示的标签或输出仍为英文。
import numpy as np
from PIL import Image
pixels = np.array(
[[[255, 0, 0], [0, 255, 0]], [[0, 0, 255], [255, 255, 255]]],
dtype=np.uint8,
)
image = Image.fromarray(pixels)
print(image.mode, image.size, image.getpixel((0, 0)))
RGB (2, 2) (255, 0, 0)
生成的图像是保存在内存中的 Pillow 对象。你可以检查它的模式和尺寸,然后在支持图像预览的环境中显示,或将它保存到文件。
使用 Image.fromarray() 将 NumPy 数组转换为 PIL 图像
Image.fromarray(array) 会根据数组的形状和 dtype 推断图像模式。灰度数据应使用形状为 (height, width) 的二维数组。RGB 数据应使用 (height, width, 3);若再加上第四个通道,则表示 RGBA。前两个维度分别是行和列,因此,包含 40 行、60 列的数组会生成宽 60 像素、高 40 像素的图像。
如果数值已经是 0 到 255 之间的整数,表示常见的 8 位图像采样值,就可以使用 np.uint8。不要不加判断地进行类型转换:将浮点数据转换为整数会截去小数部分,超出可表示范围的数值则可能发生回绕。如果数据采用其他量纲,应先决定如何将它映射为显示亮度,再进行转换。例如,范围为 0–1 的数据可以乘以 255 并四舍五入;其他范围的数据则需要根据其含义明确进行归一化。
准备数组的形状、dtype 和数值范围
形状为 (height, width) 的数组表示灰度图像。RGB 和 RGBA 数组会在末尾增加通道维度,每个像素分别包含三个或四个值。像 (height, width, 1) 这样的形状并不是常见的二维灰度格式;如果最后那个单元素维度表示灰度,可用 array[..., 0] 将它移除。转换前请检查 array.shape、array.dtype 以及最小值和最大值。数组必须构成规则的矩形网格;行长度不一的嵌套列表不是图像数据。
下面是一个完整的灰度转换示例。示例值直接处于 8 位范围内,因此不需要缩放。生成的图像使用灰度模式 L,尺寸为 (3, 2)。
本节中的代码以及显示的标签或输出仍为英文。
import numpy as np
from PIL import Image
gray = np.array([[0, 64, 128], [32, 160, 255]], dtype=np.uint8)
image = Image.fromarray(gray)
print(image.mode, image.size)
L (3, 2)
如果输入是取值范围为 0 到 1 的浮点数组,可使用 np.round(array * 255).astype(np.uint8) 有意识地进行转换。只有当这些数值是经过归一化的强度值时,这种变换才合适。对于温度或任意评分等测量数据,请选择有意义的上下界,将数据裁剪或归一化到该范围,并说明映射方式;否则,对比度和像素值可能会造成误导。
在创建 PIL 图像前应用 Matplotlib 色彩映射
如果输入是二维标量场,而你想用颜色表示其中的数值,可以使用 Matplotlib 色彩映射。先将数据归一化到所选色彩映射所需的区间,再把映射结果转换为 8 位 RGB 或 RGBA 数组。plasma 色彩映射会返回经过归一化的红、绿、蓝和 alpha 通道;使用 [..., :3] 去掉 alpha 后即可得到 RGB 数组。这是 Image.fromarray() 之外的独立步骤:色彩映射负责生成颜色,Pillow 则将这些像素封装为图像。
本节中的代码以及显示的标签或输出仍为英文。
import numpy as np
import matplotlib
from PIL import Image
values = np.array([[0.0, 0.25], [0.75, 1.0]])
colors = matplotlib.colormaps["plasma"](values)[..., :3]
rgb = np.round(colors * 255).astype(np.uint8)
image = Image.fromarray(rgb)
print(image.mode, image.size)
RGB (2, 2)
此示例假设 values 已经归一化到 0 到 1。如果实际标量范围不同,请先根据合适的数据边界进行归一化,再应用色彩映射。色彩映射适合用于可视化,但它会把标量值转换为显示颜色;如果后续分析仍需要原始测量值,请保留原始数据。生成的图像为 RGB 模式,尺寸为 (2, 2)。
调整 PIL 图像尺寸并保存
转换后,可使用 image.resize((width, height)) 调整尺寸;Pillow 接收的新尺寸顺序是宽度在前、高度在后。保存图像与显示图像是两件不同的事:show() 会使用本地环境打开预览,而 save() 会写入图像文件。请选择与目标格式相符的文件扩展名,例如无损输出使用 .png,JPEG 格式使用 .jpg。
本节中的代码以及显示的标签或输出仍为英文。
import numpy as np
from PIL import Image
pixels = np.zeros((20, 30, 3), dtype=np.uint8)
pixels[:, :15] = [255, 80, 0]
pixels[:, 15:] = [20, 90, 220]
image = Image.fromarray(pixels)
resized = image.resize((60, 40))
resized.save("resized-image.png")
print(resized.size)
示例在内存中创建输入图像,并将 resized-image.png 写入当前工作目录。在应用程序中,请使用符合输出位置的路径。保存不会修改 NumPy 数组;调整尺寸则会改变 Pillow 图像的像素尺寸。
常见转换问题
- **图像模式不符合预期或形状遭到拒绝:**检查
array.shape。灰度图使用(height, width);RGB 或 RGBA 则在最后一个维度分别使用三个或四个通道。如果多出不需要的单元素通道,请将它移除;如果来源数据使用其他通道约定,请重新排列通道。 - **亮度或颜色不正确:**转换前检查数值范围。以浮点数表示、范围为 0–1 的值,若要用于 8 位图像,需要有意识地缩放;已经处于 0–255 范围的值则不要再次乘以 255。对于其他范围,请依据明确记录的边界进行归一化。
- **dtype 不符合预期:**检查
array.dtype,再决定如何转换。单纯转换类型不会重新缩放数据;如果原始精度很重要,请保留一份源数据副本。 - **色彩映射结果不正确:**确认标量输入已按预期归一化,并检查结果是包含三个 RGB 通道还是四个 RGBA 通道。创建图像前,请将归一化后的颜色通道转换为
uint8。 - **没有生成图像文件:**使用可写入的路径和受支持的扩展名调用
save()。只有在需要预览时才使用show();它不会保存图像。