我第一次玩电脑图形编程。我想将RGB(24位)图像转换为索引调色板(8位)图像(如GIF)。我最初的想法是使用k-means(k = 256)。
如何为给定图像选择最佳调色板?这对我来说是一次学习经历,所以我更喜欢源代码的概述型答案。
编辑:抖动目前是偏离主题的。我只是提到“简单”的颜色转换,心理视觉/感知模型;色彩空间目前也是偏离主题的,虽然在色彩空间之间移动是让我首先考虑这个问题的原因:)
答案 0 :(得分:6)
答案 1 :(得分:5)
人们提供的参考链接都很好,并且有几种解决方案可以解决这个问题,但是由于我最近一直在研究这个问题(完全不了解其他人如何解决它),我提供了我的方法简明英文:
首先,要意识到(人类感知的)颜色是三维的。这基本上是因为人眼有3种不同的受体:红色,绿色和蓝色。同样,您的显示器具有红色,绿色和蓝色像素元素。可以使用其他表示,如色调,饱和度,亮度(HSL),但基本上所有表示都是三维的。
这意味着RGB色彩空间是一个立方体,有红色,绿色和蓝色轴。从24位源图像,此立方体在每个轴上具有256个离散级别。将图像缩小到8位的简单方法是简单地降低每轴的电平。例如,通过获取红色和绿色值的高3位以及蓝色值的高2位,可以轻松创建8x8x4立方体调色板,其中8个级别用于红色和绿色,4个级别用于蓝色。这很容易实现,但有几个缺点。在生成的256色调色板中,根本不会使用许多条目。如果图像具有使用非常微妙的颜色偏移的细节,则当颜色捕捉到相同的调色板条目时,这些偏移将消失。
自适应调色板方法不仅要考虑图像中的平均/常见颜色,还要考虑颜色空间的哪些区域具有最大的差异。也就是说,具有数千个浅绿色调的图像需要与具有完全相同的浅绿色的数千个像素的图像不同的调色板,因为后者理想地使用该颜色的单个调色板条目。 / p>
为此,我采用了一种方法,产生了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,您可以将更高的量化放在亮度维度而不是蓝色。此外,上述算法将略微降低整体动态范围(因为它最终平均颜色值),因此动态扩展生成的调色板是另一种可能性。
答案 2 :(得分:4)
修改强> 已更新以支持256色调色板
如果您需要最简单的方法,那么我会建议基于直方图的方法:
Calculate histograms of R/G/B channels Define 4 intensity ranges For each channel in intensity range Split histogram into 4 equal parts For each histogram part Extract most frequent value of that part
现在你将拥有4 * 4 ^ 3 = 256色调色板。将像素指定为调色板颜色时,只需计算像素的平均强度即可查看必须使用的强度区域。之后,只需将64种强度区域中的一种颜色映射到像素值。
祝你好运。答案 3 :(得分:0)
这可能有点迟了,但是尝试一下:
每次抑制颜色时,都必须将颜色及其目标添加到hash_map中。