【问题标题】:Image palette reduction图像调色板缩减
【发布时间】:2011-04-20 10:21:34
【问题描述】:

我是第一次玩计算机图形编程。我想将 RGB(24 位)图像转换为索引调色板(8 位)图像(如 GIF)。我最初的想法是使用 k-means(k=256)。

如何为给定图像选择最佳调色板?这对我来说是一次学习经历,所以我更喜欢对源代码的概述类型的答案。

编辑:抖动目前是题外话。我只指“简单”的颜色转换,心理视觉/感知模型除外;颜色空间目前也是题外话,虽然在颜色空间之间移动是让我首先想到这个的原因:)

【问题讨论】:

  • 这不是一个简单的任务。这种类型的转换有很多内容(例如抖动和人类颜色感知)。你刚刚问了很多……我敢打赌整个大学课程都满了:)
  • +1 提出一个好问题...请参阅下面的答案。您可能希望从 24 位值和标准“网络安全”调色板之间的转换开始。这将远没有确定自己的调色板那么复杂(尽管可能没有那么有趣)。

标签: algorithm image-processing gif


【解决方案1】:
【解决方案2】:

人们提供的参考链接很好,这个问题有几个解决方案,但是由于我最近一直在研究这个问题(完全不知道其他人是如何解决的),我提供了我的方法简单的英语:

首先,意识到(人类感知的)颜色是 3 维的。这基本上是因为人眼有 3 种不同的受体:红色、绿色和蓝色。同样,您的显示器也有红色、绿色和蓝色像素元素。可以使用其他表示,如色调、饱和度、亮度 (HSL),但基本上所有表示都是 3 维的。

这意味着 RGB 颜色空间是一个立方体,具有红色、绿色和蓝色轴。从 24 位源图像来看,这个立方体在每个轴上有 256 个离散级别。将图像减少到 8 位的一种简单方法是简单地减少每个轴的级别。例如,一个 8x8x4 立方体调色板具有 8 个红色和绿色级别,4 个蓝色级别,通过取红色和绿色值的高 3 位以及蓝色值的高 2 位很容易创建。这很容易实现,但有几个缺点。在生成的 256 调色板中,根本不会使用许多条目。如果图像具有使用非常细微的颜色偏移的细节,这些偏移将随着颜色捕捉到相同的调色板条目而消失。

自适应调色板方法不仅需要考虑图像中的平均/常见颜色,还需要考虑颜色空间的哪些区域具有最大的差异。也就是说,与具有数千个像素完全相同的浅绿色阴影的图像相比,具有数千种微妙浅绿色阴影的图像需要不同的调色板,因为后者理想情况下会对该颜色使用单个调色板条目。

为此,我采用了一种方法,可以生成 256 个存储桶,每个存储桶包含完全相同数量的不同值。因此,如果原始图像包含 256000 种不同的 24 位颜色,则该算法会产生 256 个桶,每个桶包含 1000 个原始值。这是通过使用存在的不同值的中值(而不是平均值)对颜色空间进行二进制空间分区来实现的。

在英文中,这意味着我们首先将整个颜色立方体分成小于中值红色值的像素的一半和大于中值红色值的一半。然后,将每个结果的一半除以绿色值,然后除以蓝色,依此类推。每个分割都需要一个位来指示像素的下半部分或上半部分。经过 8 次拆分后,方差已被有效地拆分为色彩空间中 256 个同等重要的簇。

在伪代码中:

// count distinct 24-bit colors from the source image
// to minimize resources, an array of arrays is used 
paletteRoot = {colors: [ [color0,count],[color1,count], ...]} // root node has all values 
for (i=0; i<8; i++) {
  colorPlane = i%3 // red,green,blue,red,green,blue,red,green
  nodes = leafNodes(paletteRoot) // on first pass, this is just the root itself
  for (node in nodes) {
    node.colors.sort(colorPlane) // sort by red, green, or blue
    node.lo = { colors: node.colors[0..node.colors.length/2] }
    node.hi = { colors: node.colors[node.colors.length/2..node.colors.length] } 
    delete node.colors // free up space! otherwise will explode memory
    node.splitColor = node.hi.colors[0] // remember the median color used to partition
    node.colorPlane = colorPlane // remember which color this node split on
  }
}

您现在有 256 个叶节点,每个叶节点都包含与原始图像相同数量的不同颜色,并在空间上聚集在颜色立方体中。要为每个节点分配一种颜色,请使用颜色计数找到加权平均值。加权是改善感知颜色匹配的优化,但不是那么重要。确保独立平均每个颜色通道。结果非常好。请注意,蓝色比红色和绿色少分一次是有意的,因为眼睛中的蓝色受体对细微变化的敏感度低于红色和绿色。

还有其他可能的优化。通过使用 HSL,您可以将更高的量化放在亮度维度而不是蓝色。此外,上述算法会略微降低整体动态范围(因为它最终会平均颜色值),因此动态扩展生成的调色板是另一种可能性。

【讨论】:

    【解决方案3】:

    编辑: 更新为支持 256 色调色板

    如果您需要最简单的方法,那么我建议您使用基于直方图的方法:

    计算 R/G/B 通道的直方图 定义 4 个强度范围 对于强度范围内的每个通道 将直方图分成 4 个相等的部分 对于每个直方图部分 提取该部分的最频繁值

    现在您将拥有 4*4^3=256 个调色板。将像素分配给调色板颜色时,只需计算像素的平均强度即可查看必须使用的强度区域。之后只需将这 64 种颜色的强度区域中的一种映射到像素值。

    祝你好运。

    【讨论】:

    • 因此,对于每个 (RGB) 通道,我有 4 个强度范围桶(0-63、64-127、128-191、192-255)。每个进一步划分为 4 个子范围桶(例如 [0-63] 范围的 0-15、16-31、32-47、48-63);每个通道总共提供 16 个桶。每个这样的(子)桶都将以其平均值的形式表示(比如 R[0-15] 子桶的 12)。我现在总共有 16 种红色(绿色和蓝色也一样),总共 16*16*16=4096 种颜色?!我做错了什么?!
    • 那是因为你混合了来自不同强度范围桶的颜色。算法思想是找出使用哪个强度桶——例如计算像素强度 Ip=(R+G+B)/3。然后按此 Ip 选择强度桶,并对所有三个 R/G/B 通道使用相同的强度桶。 (如果某个值超出强度范围,只需使用当前范围内最左边或最右边的子桶)。所以,恢复 - 不要混合来自不同存储桶的子存储桶,一切都会好起来的。也不要对子桶使用平均值 - 使用最常见的值,否则此方法将不起作用。
    • 或者你也可以尝试 8*8*4 的方法(根本没有子桶),因为人眼对红色、绿色比对蓝色更敏感。
    • 有很多可能的方法可以解决它。事实上,我不确定上面提出的一个,是我要接受的那个。然而,它都很好,而且后来评论很好。所以我的 V 去了这个。
    【解决方案4】:

    这可能有点迟回答,但试试这个:

    1. 为图像中的每种颜色制作一组,
    2. 按照红、绿、蓝对它们进行排序(如果前面的通道相等),(它们现在进入一个列表)
    3. 如果附近的颜色太相似,则抑制附近的颜色,即 rgb 空间中的距离小于 4。
    4. 如果颜色仍然过多,请抑制最不常用的颜色。

    每次抑制颜色时,都必须将颜色及其目的地添加到 hash_map 中。

    【讨论】:

      猜你喜欢
      • 2013-10-23
      • 2013-08-25
      • 2014-12-29
      • 2011-09-06
      • 1970-01-01
      • 2021-12-03
      • 1970-01-01
      • 2015-08-24
      • 1970-01-01
      相关资源
      最近更新 更多