小能豆

使用 NumPy 将固定调色板应用于图像?

py

我有一个 RGB 字节的 NumPy 图像,假设它是这个 2x3 图像:

img = np.array([[[  0, 255,   0], [255, 255, 255]],
                [[255,   0, 255], [  0, 255, 255]],
                [[255,   0, 255], [  0,   0,   0]]])

我还有一个调色板,涵盖了图像中使用的每种颜色。假设它是这个调色板:

palette = np.array([[255,   0, 255],
                    [  0, 255,   0],
                    [  0, 255, 255],
                    [  0,   0,   0],
                    [255, 255, 255]])

是否有某种将图像与调色板进行索引的组合(或反之亦然),可以给我一个与此等效的调色板图像?

img_p = np.array([[1, 4],
                  [0, 2],
                  [0, 3]])

为了进行比较,我知道反向操作非常简单。 palette[img_p]将给出相当于的结果img。 我试图弄清楚是否有相反方向的类似方法可以让 NumPy 完成所有繁重的工作。

我知道我可以逐个迭代所有图像像素并构建自己的调色板图像。我希望有更优雅的选择。


好的,我实施了以下各种解决方案,并在一个中等测试集上运行它们:20 张图像,每张 2000x2000 像素,带有 32 个元素的三字节颜色调色板。像素被赋予随机调色板索引。所有算法都在相同的图像上运行。

计时结果:

  • 查找数组大部分为空- 0.89 秒
  • np.searchsorted 方法- 3.20 秒
  • Pandas 查找,单个整数- 38.7 秒
  • 使用 == 然后聚合布尔结果- 66.4 秒
  • 将调色板反转为字典并使用 np.apply_along_axis()- 基于较小的测试集,大概需要约 500 秒
  • 使用 MultiIndex 进行 Pandas 查找- 基于较小的测试集,大概需要约 3000 秒

鉴于查找数组的内存消耗很大(如果有 alpha 通道,内存消耗会非常大),我将采用这种方法。如果您想将 RAM 用在查找数组上,那么np.searchsorted查找数组的速度会快得多。


阅读 25

收藏
2024-11-19

共1个答案

小能豆

从您的测试和分析中,基于查找表的方法显然是性能最佳的选择,尽管它对内存的要求很高。如果内存使用是一个关键限制,可以考虑次优的解决方案,例如使用 np.searchsorted 或优化版本的布尔匹配。

以下是一些补充说明,以及如何实现这两种方法:


1. 基于查找数组的实现(最快但内存需求高)

原理:

创建一个查找数组,将每种可能的 RGB 颜色映射到其调色板索引。对大多数图像来说,RGB 颜色的范围是 (256^3)(16,777,216 种颜色),这会占用较大的内存空间(~64 MB,假设每个值为 4 字节)。

实现:

import numpy as np

# 创建查找表
lookup = np.zeros((256, 256, 256), dtype=np.uint32)
for idx, color in enumerate(palette):
    lookup[color[0], color[1], color[2]] = idx

# 使用查找表生成调色板图像
img_p = lookup[img[..., 0], img[..., 1], img[..., 2]]
print(img_p)

2. 使用 np.searchsorted 的实现(内存占用少,速度适中)

原理:

通过对调色板进行排序并使用 np.searchsorted 查找 RGB 值的位置。需要确保调色板是按顺序排列的。

实现:

# 将调色板排序
sorted_idx = np.lexsort((palette[:, 2], palette[:, 1], palette[:, 0]))
sorted_palette = palette[sorted_idx]

# 将图像展平并在调色板中查找索引
flat_img = img.reshape(-1, 3)
indices = np.searchsorted(sorted_palette, flat_img, axis=0)

# 恢复原始顺序并重新调整形状
img_p = indices.reshape(img.shape[:2])
print(img_p)

3. 内存与速度的权衡

  • 如果您可以承受查找表的内存占用(尤其是在 8 位颜色通道的情况下),这是最理想的选择。
  • 如果内存是一个关键因素,np.searchsorted 是次优的替代方案,但会略微增加计算时间。

其他优化方向

  1. 增加颜色通道: 如果有额外的 Alpha 通道,可以扩展查找表的维度,但内存需求也会显著增加。
  2. 稀疏查找表: 如果颜色种类有限,可以使用 scipy.sparse 模块来减少内存开销。
  3. 并行化: 如果图片非常大,可以使用 NumPy 的并行工具(如 joblibnumexpr)对图像块进行分块处理。
2024-11-19