Pillow 灰度化、二值化与阈值处理

Pillow 灰度化、二值化与阈值处理

在前面的内容中,已经介绍了图像的基础读写、几何变换以及颜色模式与通道表示。在此基础上,图像处理的重点可以进一步从颜色表达转向结构表达。灰度化、二值化与阈值处理正是这一过程中的基础操作,它们能够将连续的颜色信息逐步压缩为更适合分析轮廓、区域、前景与背景关系的形式。

灰度化、二值化与阈值处理常用于目标区域提取、文档图像处理、前景分离、掩码构造以及图像分割的前期准备。在 Pillow 中,这些操作可以通过 convert()、point() 以及结合 NumPy 的像素级逻辑运算来实现。

本章将围绕灰度图的生成、阈值映射、二值化处理、掩码生成以及简单的伪彩色可视化展开,说明图像如何从连续的颜色表示逐步过渡为更明确的结构表达。整体思路仍然延续前文,即从图像的数学表示出发,再落实到 Pillow 的具体接口与 Notebook 中的可视化实现。


1. 灰度化:从彩色表示到亮度表示

1.1 原理:单通道亮度建模

一幅彩色图像可表示为:
I : ( x , y ) → ( R , G , B ) I: (x, y) \rightarrow (R, G, B) I:(x,y)→(R,G,B)

而灰度化的目标,是将每个像素从三通道颜色向量压缩为单一亮度值:
I g r a y : ( x , y ) → L I_{gray}: (x, y) \rightarrow L Igray:(x,y)→L

其中 L L L 表示亮度强度,通常取值范围为 [ 0 , 255 ] [0, 255] [0,255]。

这意味着灰度图不再保留颜色类别信息,而是强调图像中的明暗变化、边缘过渡与局部结构。

从图像分析角度看,灰度化的意义在于:

  • 降低数据维度;
  • 消除颜色差异对结构分析的干扰;
  • 为阈值分割、边缘检测与区域提取提供更直接的输入。

1.2 Pillow 接口:convert('L')

在 Pillow 中,灰度化最直接的方法是:

python 复制代码
gray = img.convert('L')

这里的 'L' 表示 8 位灰度模式,即每个像素由单个亮度值构成。前文已经说明,convert() 是 Pillow 中进行图像模式转换的核心入口,而灰度图正是最常见的一种模式转换结果。

1.3 示例:彩色图转灰度图

下面继续使用 scikit-image 自带的宇航员图像作为示例:

python 复制代码
from PIL import Image
from skimage import data
import matplotlib.pyplot as plt
import seaborn as sns

sns.set_theme(style="whitegrid", font="SimHei", rc={"axes.unicode_minus": False})

img = Image.fromarray(data.astronaut())
gray = img.convert('L')

fig, axes = plt.subplots(1, 2, figsize=(8, 4))

axes[0].imshow(img)
axes[0].set_title("原始图像")
axes[0].axis("off")

axes[1].imshow(gray, cmap='gray')
axes[1].set_title("灰度图像")
axes[1].axis("off")

plt.tight_layout()
plt.show()

从结果可以看到,灰度化之后图像不再依赖颜色差异,而是通过亮度变化来表达面部轮廓、衣物纹理和背景层次。这种表达方式虽然损失了色彩信息,但更便于后续进行阈值分析与结构提取。

1.4 NumPy 视角:数组维度变化

灰度化之后,图像的数组形状也会随之变化:

python 复制代码
import numpy as np

rgb_arr = np.array(img)
gray_arr = np.array(gray)

print(rgb_arr.shape)
print(gray_arr.shape)

输出示例:

python 复制代码
(512, 512, 3)
(512, 512)

这说明:

  • 原始 RGB 图像在数组中包含三个通道;
  • 灰度图只保留二维亮度矩阵。

这一点与前文关于"图像模式决定数组结构"的讨论完全一致,只不过这里进一步将颜色表达压缩为了结构更简洁的单通道形式。


2. 阈值处理:从连续灰度到离散判别

2.1 原理:阈值映射

灰度图虽然已经简化为单通道表示,但每个像素仍然取连续的 0--255 数值。若希望更明确地区分"前景"和"背景",可以进一步引入阈值函数,将灰度值映射为离散类别。

最简单的阈值映射可写为:
B ( x , y ) = { 255 , L ( x , y ) ≥ t 0 , L ( x , y ) < t B(x, y)= \begin{cases} 255, & L(x, y) \ge t \\ 0, & L(x, y) < t \end{cases} B(x,y)={255,0,L(x,y)≥tL(x,y)<t

其中:

  • L ( x , y ) L(x, y) L(x,y) 为灰度值;
  • t t t 为设定阈值;
  • 输出结果仅保留两类取值:0 与 255。

从数学意义上看,阈值处理是一种像素级判别函数。它将连续强度空间压缩为离散标签空间,因此非常适合用于区域分离、掩码生成和简单分割任务。

2.2 Pillow 接口:point()

Pillow 提供了 point() 方法对像素逐点映射。对于阈值操作,可以写成:

python 复制代码
binary = gray.point(lambda p: 255 if p >= 128 else 0)

这里:

  • p 表示单个像素值;
  • 当像素值不小于阈值 128 时,映射为白色 255;
  • 否则映射为黑色 0。

这种方式非常适合对灰度图进行快速规则映射。

2.3 示例:固定阈值二值化

python 复制代码
binary = gray.point(lambda p: 255 if p >= 128 else 0)

fig, axes = plt.subplots(1, 3, figsize=(12, 4))

axes[0].imshow(img)
axes[0].set_title("原始图像")
axes[0].axis("off")

axes[1].imshow(gray, cmap='gray')
axes[1].set_title("灰度图")
axes[1].axis("off")

axes[2].imshow(binary, cmap='gray')
axes[2].set_title("二值图(阈值=128)")
axes[2].axis("off")

plt.tight_layout()
plt.show()

二值化之后,图像中的亮区域与暗区域被清晰分离。尽管这种划分较为粗糙,但已经可以用于很多简单的结构提取任务,例如高亮区域筛选、文本背景分离、物体粗略轮廓定位等。


3. 二值化:从阈值判别到结构分割

3.1 原理:前景与背景的两类表达

二值图可以视为一种最简单的分割结果。此时图像不再表示连续亮度,而是只表示两类状态:

  • 0:通常对应背景;
  • 255:通常对应前景。

因此,二值图在逻辑上非常接近"结构掩码"。

它虽然不能表达复杂的纹理与层次,但可以高效突出:

  • 轮廓边界;
  • 文本与背景;
  • 明亮目标区域;
  • 简单形状的存在与否。

3.2 示例:不同阈值的影响

阈值的选择会直接影响分割结果。下面对比几个不同阈值下的二值化效果:

python 复制代码
thresholds = [64, 128, 192]

fig, axes = plt.subplots(1, 4, figsize=(14, 4))

axes[0].imshow(gray, cmap='gray')
axes[0].set_title("灰度图")
axes[0].axis("off")

for ax, t in zip(axes[1:], thresholds):
    binary_t = gray.point(lambda p, th=t: 255 if p >= th else 0)
    ax.imshow(binary_t, cmap='gray')
    ax.set_title(f"阈值={t}")
    ax.axis("off")

plt.tight_layout()
plt.show()

从结果中可以观察到:

  • 阈值较低时,更多区域会被归入前景;
  • 阈值较高时,只有更亮的区域会保留下来;
  • 阈值设定不同,本质上对应不同的结构筛选标准。

这说明二值化并不是单纯的"黑白转换",而是基于判别规则的结构提取过程。

3.3 Pillow 中更规范的二值模式:'1'

如果希望生成真正意义上的单比特图像,还可以进一步转换为模式 '1'

python 复制代码
binary_1bit = gray.point(lambda p: 255 if p >= 128 else 0).convert('1')

print(binary_1bit.mode)

输出结果:

txt 复制代码
1

这里的 '1' 表示每个像素仅占 1 bit。

L 模式下的 0/255 灰度图相比,它在存储层面更加紧凑,常用于文档扫描、黑白图稿和简单掩码保存。


4. 掩码生成:阈值结果作为结构选择器

4.1 原理:Mask 的含义

在图像处理中,Mask(掩码)通常是一幅与原图大小一致的单通道图像,用于表示哪些区域"被选中"。

从功能上看,掩码并不直接描述颜色,而是描述像素位置是否满足某种条件。

若记掩码为 M ( x , y ) M(x, y) M(x,y),则它可以表示为:
M ( x , y ) = { 1 , 该位置满足条件 0 , 该位置不满足条件 M(x, y)= \begin{cases} 1, & \text{该位置满足条件} \\ 0, & \text{该位置不满足条件} \end{cases} M(x,y)={1,0,该位置满足条件该位置不满足条件

在实际中,掩码往往由阈值处理得到。也就是说,二值图本身就可以直接作为掩码使用。

4.2 示例:基于亮度阈值生成掩码

python 复制代码
mask = gray.point(lambda p: 255 if p >= 150 else 0)

plt.figure(figsize=(5, 5))
plt.imshow(mask, cmap='gray')
plt.title("亮度掩码")
plt.axis("off")
plt.show()

这张掩码图中,较亮区域被标记为白色,较暗区域则被压制为黑色。

4.3 示例:使用掩码筛选原图区域

在 Pillow 中,可以借助 Image.composite() 用掩码选择保留原图中的部分区域:

python 复制代码
from PIL import Image

black_bg = Image.new("RGB", img.size, (0, 0, 0))
masked_result = Image.composite(img, black_bg, mask)

fig, axes = plt.subplots(1, 2, figsize=(10, 4))

axes[0].imshow(mask, cmap='gray')
axes[0].set_title("Mask")
axes[0].axis("off")

axes[1].imshow(masked_result)
axes[1].set_title("掩码筛选结果")
axes[1].axis("off")

plt.tight_layout()
plt.show()

这里的处理逻辑是:

  • 掩码为白色的位置,从原图中取值;
  • 掩码为黑色的位置,从黑色背景中取值。

因此,掩码实际上起到了"结构选择器"的作用。

这种机制在目标提取、前景保留、区域增强和图像合成中都非常常见。


5. 阈值处理中的注意事项

5.1 灰度化并不等于结构分割

灰度图虽然减少了颜色维度,但仍然保留了完整的亮度连续变化。

因此,灰度化只是阈值分割的前置步骤,而不是分割结果本身。

5.2 阈值选择会强烈影响结果

固定阈值方法实现简单,但对光照变化、背景复杂度和图像对比度较为敏感。

同一阈值在不同图像上可能产生完全不同的结果,因此在实际应用中应结合图像内容进行调整。

5.3 二值图更适合离散标签场景

与前一篇几何变换中提到的插值方法选择类似,离散类别图像通常更适合使用最近邻思想来处理,而不宜引入模糊插值。标签图、分割掩码和二值结果本质上都属于离散值图像,这一点与前文关于 NEAREST 更适合掩码类图像的讨论是一致的。


6. 简单伪彩色:让灰度结果更易观察

6.1 原理:灰度值到颜色映射

严格来说,灰度图只包含亮度,不包含颜色类别。

但在可视化时,我们常常希望让不同强度区间更容易区分,于是可以使用伪彩色映射,即:

L ( x , y ) → ( R , G , B ) L(x, y) \rightarrow (R, G, B) L(x,y)→(R,G,B)

这并不是恢复原始颜色,而只是将单通道数值映射为某种颜色表,用于增强可视化辨识度。

6.2 示例:使用 Matplotlib 伪彩色显示

虽然 Pillow 本身不提供丰富的 colormap 体系,但在 Notebook 中可以直接配合 Matplotlib 展示:

python 复制代码
fig, axes = plt.subplots(1, 3, figsize=(12, 4))

axes[0].imshow(gray, cmap='gray')
axes[0].set_title("灰度图")
axes[0].axis("off")

axes[1].imshow(gray, cmap='viridis')
axes[1].set_title("伪彩色:viridis")
axes[1].axis("off")

axes[2].imshow(gray, cmap='magma')
axes[2].set_title("伪彩色:magma")
axes[2].axis("off")

plt.tight_layout()
plt.show()

这类伪彩色并不会改变图像的结构信息,但能让亮度差异在视觉上更容易被观察到。

因此,在分析热区、亮度分布或局部对比差异时,伪彩色是一种非常实用的辅助表达方式。


7. NumPy 视角:阈值处理的数组表达

为了更直接地理解阈值处理,也可以将灰度图转换为 NumPy 数组后进行布尔运算:

python 复制代码
gray_arr = np.array(gray)

binary_arr = np.where(gray_arr >= 128, 255, 0).astype(np.uint8)

print(gray_arr.shape)
print(binary_arr.shape)
print(binary_arr.dtype)

输出示例:

python 复制代码
(512, 512)
(512, 512)
uint8

然后再转回 Pillow 图像:

python 复制代码
binary_from_np = Image.fromarray(binary_arr)

plt.figure(figsize=(5, 5))
plt.imshow(binary_from_np, cmap='gray')
plt.title("NumPy 阈值结果")
plt.axis("off")
plt.show()

这种方式与前文中 Pillow 和 NumPy 的互转思路完全一致:Pillow 提供图像对象与基础接口,NumPy 提供更灵活的数组级逻辑运算,两者结合后可以构建更复杂的像素处理流程。


8. 从灰度化到掩码提取的完整流程

下面给出一个更完整的示例,将灰度化、阈值处理与掩码应用串联起来:

python 复制代码
from PIL import Image
from skimage import data
import matplotlib.pyplot as plt
import numpy as np

# 1. 读取图像
img = Image.fromarray(data.astronaut())

# 2. 灰度化
gray = img.convert('L')

# 3. 阈值二值化
mask = gray.point(lambda p: 255 if p >= 150 else 0)

# 4. 用掩码提取亮区域
black_bg = Image.new("RGB", img.size, (0, 0, 0))
result = Image.composite(img, black_bg, mask)

# 5. 可视化
fig, axes = plt.subplots(1, 4, figsize=(16, 4))

axes[0].imshow(img)
axes[0].set_title("原始图像")
axes[0].axis("off")

axes[1].imshow(gray, cmap='gray')
axes[1].set_title("灰度图")
axes[1].axis("off")

axes[2].imshow(mask, cmap='gray')
axes[2].set_title("阈值掩码")
axes[2].axis("off")

axes[3].imshow(result)
axes[3].set_title("掩码提取结果")
axes[3].axis("off")

plt.tight_layout()
plt.show()

这个流程很好地展示了本章的主线:

  • 先通过灰度化去掉颜色冗余;
  • 再通过阈值处理建立结构判别规则;
  • 最后将二值结果作为掩码,用于区域筛选与前景提取。

这正是从"颜色表示"向"结构提取"过渡的典型过程。


9. 总结

本章围绕 Pillow 中的灰度化、二值化与阈值处理,对灰度转换、阈值映射、二值分割、掩码生成以及伪彩色可视化的实现方式与基本原理进行了系统说明。

通过这些操作可以看到:

  • 灰度化本质上是将彩色图像压缩为单通道亮度表示;
  • 阈值处理是对连续灰度值进行离散判别;
  • 二值图可以作为结构掩码使用;
  • 掩码不仅能够表示区域选择,还可以参与图像合成与目标提取;
  • 伪彩色则提供了更直观的灰度结果可视化方式。

结合 Pillow 的像素映射接口与 NumPy 的数组表示,可以更直观地理解图像如何从颜色表达过渡到结构表达。这些操作构成了图像预处理、区域提取与简单分割任务的重要基础,也是进一步理解图像增强、滤波处理与更复杂视觉分析流程的前提。

相关推荐
飞Link2 小时前
告别复杂调参:Prophet 加法模型深度解析与实战
开发语言·python·数据挖掘
测试人社区—66792 小时前
当代码面临道德选择:VR如何为AI伦理决策注入“人性压力”
网络·人工智能·python·microsoft·vr·azure
独行soc2 小时前
2026年渗透测试面试题总结-36(题目+回答)
网络·python·安全·web安全·网络安全·渗透测试·安全狮
witAI2 小时前
**Kimi小说灵感2025推荐,从零到一的创意激发指南**
人工智能·python
飞Link3 小时前
深度解析:基于专家的监管方法(Expert-Based Supervision)在复杂系统中的应用
python·数据挖掘·回归
Shining05963 小时前
Triton & 九齿系列《Triton 练气术》
开发语言·人工智能·python·学习·其他·infinitensor
天远Date Lab3 小时前
天远企业司法认证API实战:Python构建企业级供应链合规审查防火墙
大数据·开发语言·网络·python
进击的雷神3 小时前
ID隐式传参、多页面字段分散、数据强制覆盖、无分页列表解析——巴西展会爬虫四大技术难关攻克纪实
服务器·网络·爬虫·python
七夜zippoe3 小时前
[特殊字符] Python日志系统革命:Loguru结构化日志与ELK Stack集中管理实战指南
大数据·python·elk·loguru·logstash