Pillow 图像算术运算与通道计算

Pillow 图像算术运算与通道计算

图像处理不仅包括几何变换、颜色空间转换、滤波增强与图层合成,还包括一类非常基础且重要的操作:图像算术运算与通道计算。这类操作不再关注图像在空间中的位置映射,也不以透明叠加为核心,而是直接把图像看作由像素值构成的数值矩阵,对这些矩阵执行逐像素、逐通道的运算。

在 Pillow 中,这类功能主要由 ImageChops 模块提供。它支持图像之间的差异比较、加法、减法、亮值选择、暗值选择以及反相等操作,适用于变化检测、结果比较、局部响应分析、通道级处理和基础图像增强等场景。

本章围绕 Pillow 中的 ImageChops 模块展开,重点说明 difference()add()subtract()lighter()darker()invert() 等常用操作的基本原理、接口用法与可视化效果,帮助建立"图像不仅是图层对象,也是数值矩阵"的处理视角。


1. 图像算术运算的基本含义

一幅数字图像可以表示为定义在规则网格上的函数:
I:(x,y)→v,v∈Rc I:(x,y)\rightarrow \mathbf{v},\quad \mathbf{v}\in\mathbb{R}^c I:(x,y)→v,v∈Rc

其中,(x,y)(x,y)(x,y) 表示像素坐标,v\mathbf{v}v 表示该位置上的像素值向量,ccc 表示通道数。

当两幅图像尺寸一致、模式兼容时,可以对它们在每个像素位置进行逐点运算。其一般形式可以写为:
Iout(x,y)=F(I1(x,y),I2(x,y)) I_{out}(x,y)=F(I_1(x,y),I_2(x,y)) Iout(x,y)=F(I1(x,y),I2(x,y))

其中 FFF 表示某种像素级映射规则。例如,加法运算对应像素值叠加,减法运算对应像素值相减,差异运算对应绝对差值,亮值与暗值选择对应逐像素极值运算,而反相则对应单幅图像的亮暗互补变换。

因此,图像算术运算的核心并不是图层覆盖关系,而是像素值本身的数值关系


2. Pillow 的通道运算模块:ImageChops

Pillow 提供了专门的图像通道运算模块:

python 复制代码
from PIL import Image, ImageChops

其中常见方法包括:

  • ImageChops.difference():计算两幅图像的绝对差值;
  • ImageChops.add():计算两幅图像逐像素加法;
  • ImageChops.subtract():计算两幅图像逐像素减法;
  • ImageChops.lighter():逐像素取较亮值;
  • ImageChops.darker():逐像素取较暗值;
  • ImageChops.invert():对图像进行反相处理。

下面继续使用 scikit-image 中的示例图像说明这些操作:

python 复制代码
from PIL import Image, ImageChops
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})

img1 = Image.fromarray(data.astronaut()).resize((256, 256))
img2 = Image.fromarray(data.chelsea()).resize((256, 256))

3. 差异运算:difference()

3.1 原理:逐像素绝对差值

difference() 用于计算两幅图像在每个像素位置上的绝对差异,其形式可写为:
Idiff(x,y)=∣I1(x,y)−I2(x,y)∣ I_{diff}(x,y)=|I_1(x,y)-I_2(x,y)| Idiff(x,y)=∣I1(x,y)−I2(x,y)∣

这意味着:

  • 若两幅图像在某位置像素值接近,则结果较暗;
  • 若两幅图像在某位置差异较大,则结果较亮。

因此,差异图本质上刻画的是两幅图像在像素层面的不一致程度,常用于图像变化检测、处理前后结果比较、图像对齐误差观察以及简单的运动区域分析。

3.2 Pillow 接口:difference()

python 复制代码
diff_img = ImageChops.difference(img1, img2)

3.3 示例:两幅图像的差异可视化

python 复制代码
diff_img = ImageChops.difference(img1, img2)

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

axes[0].imshow(img1)
axes[0].set_title("图像 1")
axes[0].axis("off")

axes[1].imshow(img2)
axes[1].set_title("图像 2")
axes[1].axis("off")

axes[2].imshow(diff_img)
axes[2].set_title("difference() 差异图")
axes[2].axis("off")

plt.tight_layout()
plt.show()

差异图并不强调"融合效果",而是直接显示像素值差异的强弱分布。

3.4 示例:原图与旋转版本的差异

python 复制代码
rotated = img1.rotate(5)
diff_rotated = ImageChops.difference(img1, rotated)

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

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

axes[1].imshow(rotated)
axes[1].set_title("旋转后图像")
axes[1].axis("off")

axes[2].imshow(diff_rotated)
axes[2].set_title("差异结果")
axes[2].axis("off")

plt.tight_layout()
plt.show()

这种方式可以直观观察几何变换对图像内容带来的局部变化。


4. 加法运算:add()

4.1 原理:逐像素相加

add() 对两幅图像执行逐像素加法,可写为:
Iadd(x,y)=I1(x,y)+I2(x,y) I_{add}(x,y)=I_1(x,y)+I_2(x,y) Iadd(x,y)=I1(x,y)+I2(x,y)

由于像素值通常限制在一定范围内,Pillow 提供了 scaleoffset 参数,用于控制输出:
Iout(x,y)=I1(x,y)+I2(x,y)scale+offset I_{out}(x,y)=\frac{I_1(x,y)+I_2(x,y)}{\text{scale}}+\text{offset} Iout(x,y)=scaleI1(x,y)+I2(x,y)+offset

其中,scale 用于缩放结果,offset 用于整体偏移输出值。因此,add() 不只是简单叠加,也可以通过缩放构造更平衡的数值组合。

4.2 Pillow 接口:add()

python 复制代码
add_img = ImageChops.add(img1, img2, scale=2.0, offset=0)

4.3 示例:加法运算效果

python 复制代码
add_img = ImageChops.add(img1, img2, scale=2.0)

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

axes[0].imshow(img1)
axes[0].set_title("图像 1")
axes[0].axis("off")

axes[1].imshow(img2)
axes[1].set_title("图像 2")
axes[1].axis("off")

axes[2].imshow(add_img)
axes[2].set_title("add(scale=2.0)")
axes[2].axis("off")

plt.tight_layout()
plt.show()

这里设置 scale=2.0,可以避免直接相加导致的亮度溢出,使结果更容易观察。

从实际用途看,add() 常用于合并两幅响应图、构造简单的图像增强结果,或者对不同处理中间结果进行数值叠加。不过如果不合理控制 scale,很容易导致高亮区域饱和。


5. 减法运算:subtract()

5.1 原理:逐像素相减

subtract() 对两幅图像执行逐像素减法,其形式可写为:
Isub(x,y)=I1(x,y)−I2(x,y) I_{sub}(x,y)=I_1(x,y)-I_2(x,y) Isub(x,y)=I1(x,y)−I2(x,y)

Pillow 同样支持:
Iout(x,y)=I1(x,y)−I2(x,y)scale+offset I_{out}(x,y)=\frac{I_1(x,y)-I_2(x,y)}{\text{scale}}+\text{offset} Iout(x,y)=scaleI1(x,y)−I2(x,y)+offset

  • difference() 不同,减法是有方向的 。也就是说,I_1-I_2I_2-I_1 的结果通常不同,因此它更适合表示某一幅图像相对于另一幅图像"多出了什么"或"减去了什么"。

5.2 Pillow 接口:subtract()

python 复制代码
sub_img = ImageChops.subtract(img1, img2, scale=1.0, offset=0)

5.3 示例:减法结果可视化

python 复制代码
sub_img = ImageChops.subtract(img1, img2)

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

axes[0].imshow(img1)
axes[0].set_title("图像 1")
axes[0].axis("off")

axes[1].imshow(img2)
axes[1].set_title("图像 2")
axes[1].axis("off")

axes[2].imshow(sub_img)
axes[2].set_title("subtract()")
axes[2].axis("off")

plt.tight_layout()
plt.show()

5.4 示例:原图减去模糊图,观察细节分量

python 复制代码
from PIL import ImageFilter

blurred = img1.filter(ImageFilter.GaussianBlur(radius=2))
detail_part = ImageChops.subtract(img1, blurred, offset=128)

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

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

axes[1].imshow(blurred)
axes[1].set_title("模糊图像")
axes[1].axis("off")

axes[2].imshow(detail_part)
axes[2].set_title("原图 - 模糊图")
axes[2].axis("off")

plt.tight_layout()
plt.show()

这个结果可以近似看作原图中被模糊过程削弱掉的局部高频细节。这里加入 offset=128,是为了让正负差异在可视化时更加明显。


6. 亮值选择:lighter()

6.1 原理:逐像素取较大值

lighter() 在两幅图像的对应位置逐像素取较亮值,可表示为:
Ilight(x,y)=max⁡(I1(x,y),I2(x,y)) I_{light}(x,y)=\max(I_1(x,y),I_2(x,y)) Ilight(x,y)=max(I1(x,y),I2(x,y))

这类操作不做平均,也不做差值,而是直接保留两个输入中更亮的那个结果。

6.2 Pillow 接口:lighter()

python 复制代码
lighter_img = ImageChops.lighter(img1, img2)

6.3 示例:亮值选择结果

python 复制代码
lighter_img = ImageChops.lighter(img1, img2)

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

axes[0].imshow(img1)
axes[0].set_title("图像 1")
axes[0].axis("off")

axes[1].imshow(img2)
axes[1].set_title("图像 2")
axes[1].axis("off")

axes[2].imshow(lighter_img)
axes[2].set_title("lighter()")
axes[2].axis("off")

plt.tight_layout()
plt.show()

这种方法适合保留两幅图像中的高亮区域,或者构造亮响应优先的结果。从本质上看,它是一种逐像素极大值合成。


7. 暗值选择:darker()

7.1 原理:逐像素取较小值

lighter() 相对,darker() 会在对应位置保留较暗值:
Idark(x,y)=min⁡(I1(x,y),I2(x,y)) I_{dark}(x,y)=\min(I_1(x,y),I_2(x,y)) Idark(x,y)=min(I1(x,y),I2(x,y))

它本质上是一种逐像素极小值运算。

7.2 Pillow 接口:darker()

python 复制代码
darker_img = ImageChops.darker(img1, img2)

7.3 示例:暗值选择结果

python 复制代码
darker_img = ImageChops.darker(img1, img2)

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

axes[0].imshow(img1)
axes[0].set_title("图像 1")
axes[0].axis("off")

axes[1].imshow(img2)
axes[1].set_title("图像 2")
axes[1].axis("off")

axes[2].imshow(darker_img)
axes[2].set_title("darker()")
axes[2].axis("off")

plt.tight_layout()
plt.show()

它常用于保留较暗区域、构造极小值组合结果,或者对局部暗部特征进行简单比较。与 lighter() 一样,它也是一种典型的逐像素极值选择操作。


8. 反相运算:invert()

8.1 原理:亮暗互补变换

invert() 用于对图像进行反相处理。对于 8 位图像,其形式可写为:
Iinv(x,y)=255−I(x,y) I_{inv}(x,y)=255-I(x,y) Iinv(x,y)=255−I(x,y)

也就是说:

  • 原本亮的区域会变暗;
  • 原本暗的区域会变亮。

这是一种非常直接的像素互补运算。

8.2 Pillow 接口:invert()

python 复制代码
invert_img = ImageChops.invert(img1)

8.3 示例:彩色图像反相

python 复制代码
invert_img = ImageChops.invert(img1)

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

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

axes[1].imshow(invert_img)
axes[1].set_title("invert()")
axes[1].axis("off")

plt.tight_layout()
plt.show()

8.4 示例:灰度图像反相

python 复制代码
gray = img1.convert("L")
gray_invert = ImageChops.invert(gray)

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

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

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

plt.tight_layout()
plt.show()

在灰度图中,反相效果通常更直观,因此很适合观察亮暗结构之间的互补关系。


9. 多种通道运算的统一对比

为了更直观地比较不同运算方式的作用,可以将几类常见结果统一展示:

python 复制代码
diff_img = ImageChops.difference(img1, img2)
add_img = ImageChops.add(img1, img2, scale=2.0)
sub_img = ImageChops.subtract(img1, img2)
lighter_img = ImageChops.lighter(img1, img2)
darker_img = ImageChops.darker(img1, img2)
invert_img = ImageChops.invert(img1)

fig, axes = plt.subplots(2, 3, figsize=(12, 8))

images = [diff_img, add_img, sub_img, lighter_img, darker_img, invert_img]
titles = ["difference", "add", "subtract", "lighter", "darker", "invert"]

for ax, im, title in zip(axes.ravel(), images, titles):
    ax.imshow(im, cmap="gray" if getattr(im, "mode", "") == "L" else None)
    ax.set_title(title)
    ax.axis("off")

plt.tight_layout()
plt.show()

通过这一组结果可以看出,difference() 强调的是差异强度,add() 强调的是数值叠加,subtract() 强调的是有方向的变化,lighter()darker() 强调的是极值选择,而 invert() 强调的是亮暗互补。


10. 构建一个简单的变化分析流程

下面给出一个更完整的示例,将模糊、差异运算与反相组合起来,用于观察图像处理前后的变化:

python 复制代码
from PIL import Image, ImageChops, ImageFilter
from skimage import data
import matplotlib.pyplot as plt

img = Image.fromarray(data.astronaut()).resize((256, 256))

# 1. 构造一个模糊版本
blurred = img.filter(ImageFilter.GaussianBlur(radius=2))

# 2. 计算差异
diff_img = ImageChops.difference(img, blurred)

# 3. 对差异图做反相,便于观察互补效果
diff_invert = ImageChops.invert(diff_img)

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

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

axes[1].imshow(blurred)
axes[1].set_title("模糊图像")
axes[1].axis("off")

axes[2].imshow(diff_img)
axes[2].set_title("差异图")
axes[2].axis("off")

axes[3].imshow(diff_invert)
axes[3].set_title("差异图反相")
axes[3].axis("off")

plt.tight_layout()
plt.show()

这个流程体现了图像算术运算在结果比较中的一个典型用途:先生成不同处理版本,再通过差异运算观察变化,最后通过反相增强显示上的可读性。


11. 使用 ImageChops 时的注意事项

11.1 输入图像尺寸应一致

多数运算都要求两幅图像具有相同尺寸。若尺寸不同,应先统一大小:

python 复制代码
img1 = img1.resize((256, 256))
img2 = img2.resize((256, 256))

11.2 图像模式尽量一致

为了避免不必要的问题,两幅输入图像最好具有相同模式。例如都转换为 RGB

python 复制代码
img1 = img1.convert("RGB")
img2 = img2.convert("RGB")

11.3 add()subtract() 要注意结果范围

像素值通常限制在 0~255 范围内,因此在加减运算中,合理使用 scaleoffset 很重要,否则容易出现过亮、过暗或层次丢失的问题。

11.4 difference() 更适合做变化比较

如果目标是观察两幅图像哪里不同,通常优先使用 difference(),因为它比有方向的减法更直观、更适合可视化。


12. 总结

本章围绕 Pillow 中的 ImageChops 模块,对 difference()add()subtract()lighter()darker()invert() 等常见图像算术运算与通道计算方式进行了系统说明。

通过这些操作可以看到:

  • difference() 本质上是逐像素绝对差值,适合用于变化分析;
  • add()subtract() 表示逐像素加减运算,强调像素值之间的数值关系;
  • lighter()darker() 分别对应逐像素极大值与极小值选择;
  • invert() 则实现亮暗互补的反相变换;
  • ImageChops 提供了从图像对象层面理解像素矩阵计算的简洁入口。

结合这些操作,可以更直观地理解图像在 Pillow 中不仅可以被视为可显示、可合成的视觉对象,也可以被视为能够直接参与数值运算的像素矩阵。这些方法构成了图像比较、变化检测、通道分析与基础图像处理流程中的重要组成部分。

相关推荐
小陈phd2 小时前
多模态大模型学习笔记(十四)——transformer学习之Self-Attention
人工智能·自然语言处理·transformer
小超同学你好2 小时前
Langgraph 4. 反思 Reflection
人工智能·语言模型·langchain
带娃的IT创业者2 小时前
神经形态意识模块理论基础详解:六大核心理论支柱
人工智能·深度学习·脑科学·神经科学·认知科学·意识理论·ai 架构
北京阿法龙科技有限公司2 小时前
解放双手,透视数据:AR+AI技术正在如何解决新能源储能行业的老大难问题
人工智能·ar
产品人卫朋2 小时前
AI硬件产品怎么做?——桌面机器人
人工智能·机器人
K姐研究社2 小时前
阿里QoderWork实测 – 打工人桌面AI助手,零配置替代OpenClaw
人工智能·aigc
机器觉醒时代2 小时前
DreamZero:从语言理解到世界建模——具身智能的WAM新范式
人工智能·具身智能·人形机器人·世界模型
FluxMelodySun2 小时前
机器学习(二十一) 集成学习-结合策略与多样性
人工智能·机器学习·集成学习
WangUnionpub2 小时前
别只盯着MDPI,又贵还卡单位,平替SCI/EI,免收版面费,这本15天录用!
大数据·人工智能·深度学习·物联网·计算机视觉