文章目录
- 参考
- 描述
- 模块
- Pillow
- PIL
- Pillow
- 获取
- Numpy
- 获取
- 使用 Pillow 实现图片的灰度处理
- ImageOps.grayscale()
- convert()
- 原理
- 灰度模式与彩色模式
- 图片表示与三维数组
- ImageOps.grayscale() 与 convert('L') 背后的逻辑
- 心理学灰度加权公式
- Python 实现
- 灰度模式下的灰度图片
- 彩色模式下的灰度图片
- axis
- apply_along_axis()
- Python 实现
- 更简单的实现
- 为什么将 RGB 均设置为灰度值后能够将彩色图片转换为灰度图?
- 空间对比
- PNG
- JPEG
参考
项目 | 描述 |
---|---|
搜索引擎 | |
Pillow | 官方文档 |
描述
项目 | 描述 |
---|---|
Python 解释器 | 3.10.6 |
模块
Pillow
PIL
PIL(Python Imaging Library)是 Python 中最早的图像处理模块之一,用于打开、操作和保存多种图像格式。但是,PIL 的最新版本已经停止开发并且不支持 Python 3.x。
Pillow
Pillow 是一个基于 PIL 开发的第三方模块,同时支持 Python 2.x 与 Python 3.x。它包含了 PIL的所有功能,并在此基础上增加了新的特性,如更好的 JPEG 支持、更好的文档、更好的性能等。
Pillow 是 PIL 的一个分支,是目前 Python 中最常用的图像处理模块之一,被广泛应用于图像处理、计算机视觉、机器学习等领域。
获取
由于 Pillow 是 Python 的一个第三方模块,因此在使用前需要对其进行下载安装。你可以通过在终端中执行如下命令来完成上述步骤。
pip install Pillow
如果下载速度较慢,你可以尝试使用镜像源进行第三方模块的下载安装。
使用 -i 选项执行使用的镜像源,这里以清华镜像源为例进行介绍。
pip install Pillow -i https://pypi.tuna.tsinghua.edu.cn/simple
Numpy
NumPy 是一个用于科学计算的 Python 库,提供了高效的多维数组对象和相关的数学函数。NumPy 的核心数据类型是 ndarray(即 N 维数组),它可以表示向量、矩阵和任意维度的数组。NumPy 提供了许多用于创建、操作和处理 ndarray 的函数和方法,以及数学和统计函数,例如求和、平均值、方差、标准差、最大值、最小值等。NumPy 的向量化运算和广播机制可以高效地处理大量的数值计算,特别适用于高维度数据和科学计算领域。
获取
Numpy 同样为 Python 中的第三方模块,在使用该模块前请确保已经将其进行安装。
使用 Pillow 实现图片的灰度处理
ImageOps.grayscale()
ImageOps 的全称是 Image Operators,是 Pillow 图像处理库中的一个模块,提供了一些常用的图像操作函数,用于对图像进行增强、变换和过滤等处理。这些函数通常是基于像素级别的操作,能够快速、高效地处理大量的图像数据。
ImageOps.grayscale() 能够接收一个图片对象并将其转化为灰度图。对此,请参考如下示例:
# 从 Pillow 模块中导入 Image 及 ImageOps 模块
from PIL import Image, ImageOps
# 使用 Image 模块中的 open 函数导入图片
img = Image.open('boat.jpg')
# 使用 ImageOps 中的 grayscale 将图片转换为灰度图
result = ImageOps.grayscale(img)
# 将转化后的结果保存至当前工作目录并将该图片命名为 result.jpg
result.save('result.jpg')
执行效果
左图为原图,右图为左图进行灰度处理操作后的结果。
convert()
Pillow 模块为图片对象提供了 convert() 方法,该方法能够按照指定的模式将图片进行转化。当模式为 L 时,你将能够得到目标图片的灰度处理结果。对此,请参考如下示例:
# 从 Pillow 模块中导入 Image 模块
from PIL import Image, ImageOps
# 使用 Image 模块中的 open 函数导入图片
img = Image.open('boat.jpg')
# 使用图片对象的 convert 方法将图片转换为灰度图
result = img.convert('L')
# 将转化后的结果保存至当前工作目录并将该图片命名为 result.jpg
result.save('result.jpg')
执行效果
左图为原图,右图为左图进行灰度处理操作后的结果。
原理
灰度模式与彩色模式
彩色模式用于表示真实世界中的彩色图像,它由红色(R)、绿色(G)和蓝色(B)三个颜色通道组成。每个像素点的颜色由三个通道的不同强度组合而成,通常用 24 个二进制位(每个通道 8 位)来表示一个像素点。
RGB
RGB 是一种彩色模式,每个像素由三个颜色分量组成,分别表示红、绿、蓝三个颜色分量的强度或亮度。因此,一个像素的颜色可以表示为一个三元组 (R, G, B)。在 RGB 模式下,每个颜色分量的取值范围为 0 到 255。
举个栗子
一张彩色的照片包含了很多颜色信息,这些颜色通过 RGB 三个通道混合而成。像素点的颜色信息再依据其所包含的位置信息显示在屏幕中的正确位置,从而形成了真实世界中的图像。
灰度模式则只用一种亮度值(仅存在一个通道)来表示一个像素点的颜色,其亮度值通常用一个 8 位二进制数来表示。灰度值的范围从 0(黑色)到 255(白色),中间的值代表不同的灰度层次。因此,灰度图像可以看作是一种颜色信息比较简单的图像。在灰度模式下,每个像素只需要用一个字节(即八个二进制位)表示亮度,可以有效地减少图像的存储空间。
灰度模式与彩色模式的空间对比
boat.jpg 与 result.jpg 分别为同一张图片在彩色模式(原图)与灰度模式(由原图的彩色模式转化为灰度模式)下的状态。
图片表示与三维数组
在计算机中,图片通常被表示为三维数组。具体来说,对于一个 n × m n \times m n×m 的彩色图片,可以看成是由 n n n 行,每行有 m m m 个像素组成的,而每个像素又由三个值构成,分别表示红、绿、蓝三种颜色的强度,因此可以用一个 n × m × 3 n \times m \times 3 n×m×3 的三维数组来表示。其中,第一维表示行数,第二维表示列数,第三维表示颜色通道。
灰度图则是由单通道构成,因此可以看成是一个 n × m n \times m n×m 的二维数组。每个像素只有一个值,表示灰度强度。
这是一个大小为 3*5 像素的彩色图片转换为三维数组的结果
[[[ 65 81 55]
[ 60 53 64]
[232 104 254]]
[[153 101 201]
[116 185 143]
[147 162 93]]
[[179 234 55]
[254 127 237]
[ 50 61 81]]
[[ 47 107 72]
[ 63 143 208]
[ 16 79 120]]
[[187 251 158]
[ 76 149 204]
[233 161 164]]]
ImageOps.grayscale() 与 convert(‘L’) 背后的逻辑
from PIL import Image, ImageOps
import numpy as np
# 将图像进行导入
img = Image.open('boat.jpg')
# 输出图像中第一行的前五个像素点的颜色信息
print(np.array(img)[:1])
# 将图片进行灰度处理
result = ImageOps.grayscale(img)
result1 = img.convert('L')
# 将灰度处理后的图片转换为三维数组,
# 同样输出图像中第一行的前五个像素点的颜色信息
print('【Result】', np.array(result)[0][:5])
print('【Result1】', np.array(result1)[0][:5])
执行效果
[[[ 68 105 183]
[ 68 105 183]
[ 68 104 182]
...
[102 108 142]
[101 107 141]
[101 107 141]]]
【Result】 [103 103 102 101 101]
【Result1】 [103 103 102 101 101]
分析:
-
由该示例可知,使用 Pillow 中的 ImageOps.grayscale() 或 convert(‘L’) 将彩色图片转换为灰度图,其本质是使用一个合适的灰度值将图片数组中的 RGB 数组进行代替。
-
两者处理的结果是相同的那么我们完全可以怀疑两者使用的是相同的 API,这点我们可以通过查看 ImageOps.grayscale() 的源代码进行验证。
def grayscale(image):
"""
Convert the image to grayscale.
:param image: The image to convert.
:return: An image.
"""
return image.convert("L")
可以看到,使用 ImageOps.grayscale() 将图片转换为灰度图,本质上是调用目标图片自身的 convert() 方法进行实现的。
心理学灰度加权公式
在进行灰度处理的深入探究后,想必你对如何将 RGB 转换为合适的灰度值而感到困惑。在将 RGB 转换为灰度值时,常使用心理学灰度加权公式进行两者的转换。
心理学灰度加权公式是一种常用的计算灰度值的方法,用于将彩色图像转换为灰度图像。它考虑了人眼对不同颜色的感知程度不同,将红、绿、蓝三种颜色的值加权求和,得到一个综合的灰度值。
具体公式如下:
g r a y = 0.299 × R + 0.587 × G + 0.114 × B gray = 0.299 \times R + 0.587 \times G + 0.114 \times B gray=0.299×R+0.587×G+0.114×B
其中:
在心理学加权灰度公式中,绿色的权重值相比其他两种颜色更高是因为 人眼对于绿色的敏感度更高。
绿色的权重值更高意味着在进行灰度值的计算时,绿色通道的取值会对最终的灰度值产生更大的影响,从而更加符合人眼对图像的感知。
这样做的优点是,能够更好地反映人眼对图像的感知,使得灰度图像更加接近于彩色原图的视觉效果。 这在很多图像处理领域都是非常有用的,例如在数字图像处理、计算机视觉、图像识别等方面。
convert() 方法是否是通过心理学灰度加权公式将图片转换为灰度图像的呢?
import numpy as np
# 定义心理学加权灰度公式常量
RED_WEIGHT = 0.299
GREEN_WEIGHT = 0.587
BLUE_WEIGHT =0.114
# 将列表转换为 ndarray 多维数组对象
arr = np.array([[68, 105, 183],
[68, 105, 183],
[68, 104, 182],
[67, 103, 181],
[67, 103, 181]], dtype=np.uint8)
result = np.dot(arr, [RED_WEIGHT, GREEN_WEIGHT, BLUE_WEIGHT]).astype(np.uint8)
print(result)
执行效果
在将数组
[[ 68 105 183]
[ 68 105 183]
[ 68 104 182]
[ 67 103 181]
[ 67 103 181]]
中的每一项转换为合适的灰度值后的结果与上一个示例中通过 ImageOps.grayscale() 及 convert() 将彩色图片转换为灰度图后得到的灰度值十分接近。
# 在本示例中得到的输出
[102 102 102 101 101]
# 由 convert() 方法处理得到的灰度值为
# [103 103 102 101 101]
因此,我们有理由相信, convert() 方法是通过心理学灰度加权公式将 RGB 转化为合适的灰度值来对彩色图片进行灰度处理的。处理结果存在的微小差异可能是由于使用的权重值不同而引起的。
注:
np.dot() 函数用于计算数组间的点积或矩阵乘积,具体取决于输入参数的维度。当输入的第一个参数与第二个参数均为一维数组时将计算数组间的点积。计算数组间的点积的公式如下所示:
a ⋅ b = ∑ i = 1 n a i b i \mathbf{a} \cdot \mathbf{b} = \sum_{i=1}^{n} a_i b_i a⋅b=∑i=1naibi
举个栗子
import numpy as np
# 定义两个一维数组用于点积的计算
arr = [1, 2, 3]
arr1 = [3, 2, 1]
# 计算两个一维数组的点积
result = np.dot(arr, arr1)
# 具体的计算公式为
# 1 * 3 + 2 * 2 + 3 * 1 = 10
print(result)
执行结果
10
Python 实现
在接下来的部分中,我们将使用 Numpy 及 Pillow 模块通过心理学灰度加权公式对彩色图片进行灰度处理。
灰度模式下的灰度图片
灰度模式下的灰度图片仅使用一个通道来表示灰度值。
from PIL import Image
import numpy as np
# 定义心理学灰度权重公式中的常量
RED_WEIGHT = 0.299
GREEN_WEIGHT = 0.587
BLUE_WEIGHT =0.114
# 将目标图片导入到程序中
img = Image.open('boat.jpg')
# 将图片对象转换为数组
arr = np.array(img, dtype=np.uint8)
# 通过对数组进行进行点积运算,将图片转换为灰度图
result_arr = np.dot(arr, [RED_WEIGHT, GREEN_WEIGHT, BLUE_WEIGHT]).astype(np.uint8)
print(result_arr[:1])
# 从数组中导入图片信息并据此生成图片对象
result = Image.fromarray(result_arr)
# 将灰度处理的结果保存至当前工作目录下的 result.jpg 文件中
result.save('result.jpg')
执行效果
左图为原图,右图为左图进行灰度处理操作后的结果。
输出结果为
[[102 102 102 ... 110 109 109]]
彩色模式下的灰度图片
彩色模式下的灰度图片使用三个通道来表示灰度值。
axis
在多维数组中,每个维度都有一个相应的轴。轴通常被认为是数组沿着该维度延伸的方向。 例如,对于一个二维数组,第一个维度的轴称为行轴,第二个维度的轴称为列轴。
axis 参数通常用于指定沿着哪个轴应用某个函数或进行某些操作。例如,numpy.sum() 函数可以在指定轴上计算数组的和。如果 axis=0,则在第一个维度上进行求和;如果 axis=1,则在第二个维度上进行求和。
apply_along_axis()
numpy.apply_along_axis() 函数是 NumPy 中的一个高级函数,它的作用是在指定的轴上,使用指定的函数处理该轴上的每一个元素。
numpy.apply_along_axis(func1d, axis, arr, *args, **kwargs)
其中:
项目 | 描述 |
---|---|
func1d | 该函数将用于处理多维数组对象中指定轴(由 axis 参数指定)中的每一个元素(元素可能可能为一个具体的值,也可能为一个数组)。你必须为该函数提供一个形参用以接收该函数所需要处理的元素,否则,Python 将抛出 TypeError 错误。 |
axis | 指定 func1d 函数需要处理的元素所处的轴。 |
arr | 指定需要被处理的多维数组对象。 |
args 与 kwargs | args 与 kwargs 为可选参数,用于将自定义函数所需要的参数传递给自定义函数。 |
举个栗子
import numpy as np
# 定义目标数组
arr = np.array([[[1, 2, 3],
[4, 5, 6]],
[[7, 8, 9],
[10, 11, 12]]])
# 定义处理函数
def process(item):
return item[0]
# 将处理函数应用至多维数组对象 arr 在 axis=1 中的每一个元素
result = np.apply_along_axis(process, 1, arr)
print(result)
print()
print('----------------')
print()
# 将处理函数应用至多维数组对象 arr 在 axis=2 中的每一个元素
result = np.apply_along_axis(process, 2, arr)
print(result)
执行效果
[[1 2 3]
[7 8 9]]
----------------
[[ 1 4]
[ 7 10]]
Python 实现
彩色模式下也可以有灰度图,你仅需要将 RGB 三个通道的值均设置为计算得到的灰度值即可。对此,请参考如下示例:
import numpy as np
from PIL import Image
# 定义处理函数
def process(item):
# 处理函数的返回值将作为被处理元素的结果值
# 使用如下语句(被注释部分)作为返回值,你将得到彩色模式下的灰度图
# return item[0] * RED_WEIGHT + item[1] * GREEN_WEIGHT + item[2] * BLUE_WEIGHT
item[0] *= RED_WEIGHT
item[1] *= GREEN_WEIGHT
item[2] *= BLUE_WEIGHT
# 将 item 中的所有元素的值都设置为 np.sum(item)
item[...] = np.sum(item)
return item
if __name__ == '__main__':
# 定义心理学灰度权重公式中所需要使用到的常数
RED_WEIGHT = 0.299
GREEN_WEIGHT = 0.597
BLUE_WEIGHT = 0.114
# 将目标图片进行导入
img = Image.open('boat.jpg')
# 将图片对象转换为 ndarray 对象
arr = np.array(img, dtype=np.uint8)
# 将处理函数应用至多维数组对象 arr 在 axis=2 中的每一个元素
result_array = np.apply_along_axis(process, 2, arr).astype(np.uint8)
print(result_array[:1])
# 从 ndarray 多维数组对象中导入图片对象
result = Image.fromarray(result_array)
# 将灰度处理后的结果图保持至当前工作目录下的 result.jpg 文件中
result.save('result.jpg')
执行效果
[[[102 102 102]
[102 102 102]
[102 102 102]
...
[110 110 110]
[109 109 109]
[109 109 109]]]
# 如果为彩色模式下的灰度图,此处的输出结果是
# [[102 102 102 ... 110 109 109]]
# 彩色模式使用 RGB 三个通道表示灰度值
# 灰度模式使用一个通道来表示灰度值
性能
上述代码存在很严重的性能问题。np.apply_along_axis() 函数是通过对指定轴进行 迭代,并将每个元素作为参数传递给指定的函数来完成相关任务的。为了提高性能,可以尝试使用向量化运算来替代 np.apply_along_axis 方法。例如,可以使用 NumPy 的广播功能,直接对数组进行运算。
优化
import numpy as np
from PIL import Image
if __name__ == '__main__':
RED_WEIGHT = 0.299
GREEN_WEIGHT = 0.597
BLUE_WEIGHT = 0.114
img = Image.open('boat.jpg')
arr = np.array(img, dtype=np.float32)
# 利用 NumPy 提供的广播机制分别对位于最低维度三个通道(RGB)进行运算
result_arr = arr * [RED_WEIGHT, GREEN_WEIGHT, BLUE_WEIGHT]
# 将最低维度中的 RGB 三个通道均转换为灰度值
# 为了达成上述目标,我们需要增加求和结果数组的维度
result_arr[...] = np.sum(arr, axis=2).reshape(arr.shape[0], arr.shape[1], 1)
result_arr = result_arr.astype(np.uint8)
print(result_arr[:1])
# 从 ndarray 多维数组对象中导入图片对象
result = Image.fromarray(result_arr)
# 将灰度处理后的结果图保持至当前工作目录下的 result.jpg 文件中
result.save('result_c.jpg')
更简单的实现
如果你觉得上一个实现(彩色模式下的灰度图片)较为复杂,我们也可以使用 Pillow 模块提供的 API 以更简便的方式来实现相应的功能。
from PIL import Image, ImageOps
import numpy as np
img = Image.open('boat.jpg')
# 得到灰度模式下的灰度图
gray_img = img.convert('L')
# 将灰度模式下的灰度图转换为彩色模式下的灰度图
result = ImageOps.colorize(gray_img, white='white', black='black')
print(np.array(result)[:1])
result.save('result_c.jpg')
执行效果
[[[103 103 103]
[103 103 103]
[102 102 102]
...
[110 110 110]
[109 109 109]
[109 109 109]]]
为什么将 RGB 均设置为灰度值后能够将彩色图片转换为灰度图?
白光
从物理学角度来讲,白光是由多种波长的电磁辐射混合而成的。但是在光学和图像处理领域中,我们可以使用三种颜色(即红、绿和蓝)来表示白光,这是因为这 三种颜色可以通过合适的混合得到任何颜色,同时这种表示方法也具有实用性和方便性。因此,从这个角度来说,我们可以说白光是由三原色组成的。
强度
由于不同的材质对于光的反射强度以及人眼对不同颜色的敏感程度是不同的,在白天我们更容易注视到某些物体因而更容易忽视另一部分物体。但在黑夜,由于光线十分微弱,这种差异将被淡化。
当 RGB 三个通道接近时,三者对颜色的最终呈现所起到的作用微乎其微。 因此,当 RGB 三者的值十分接近时将呈现黑色或白色(当 RGB 三个通道均取得最大值 255 时,颜色呈现为白色;当 RGB 三个通道均取得最小值 0 时,颜色呈现为黑色)或两者之间的灰色(微弱的白色),显示结果取决于 RGB 的值的大小。
空间对比
PNG
其中:
flower.png 为原图,而 f_result.png 与 f_result_c.png 分别为原图在灰度模式及彩色模式下的灰度图。
JPEG
其中:
boat.jpg 为原图,而 result.jpg 与 result_c.jpg 分别为原图在灰度模式及彩色模式下的灰度图。
对比
与 PNG 格式相比,JPEG 格式下,处于彩色模式与灰度模式下的灰度图的内存变化并不大。这也许是因为 JPEG 是一种有损压缩数据格式,因而对位于彩色模式下的灰度图进行了某种处理。
PNG 格式是否会彩色模式下的灰度图进行优化?
由于,PNG 格式支持 RGBA 四种通道(其中的 A 代表透明度)的图片,而恰巧 flower.png 使用了 RGBA 四个通道(flower.png 在彩色模式下的灰度图 f_result_c.png 去除了透明度通道并将剩余通道的值均转化为了灰度值。当然你也可以将 RGBA 四个通道均转化为灰度值来获取灰度图,但这样可能会导致图片的某些部分因透明度过高而受到不良影响),因此我们无法直接得出 PNG 格式是否会彩色模式下的灰度图进行优化 的结论。
为了得出 PNG 格式是否会彩色模式下的灰度图进行优化 的结论,我们需要将原图 flower.png 中的透明度通道去除以得到 flower_3.png。
from PIL import Image, ImageOps
import numpy as np
img = Image.open('flower.png')
# 将图片转换为多维数组对象,并舍弃其中的透明的通道
arr = np.array(img)[:, :, :3]
# 将处理后的数组转换为图片对象
result = Image.fromarray(arr)
# 保存图片
result.save('flower_3.png')
可以看到 PNG 格式会彩色模式下的灰度图进行优化,但力度并没有像 JPEG 格式那么大。
boat.jpg
flower.png