用于计算图像中唯一颜色数的算法

时间:2008-09-24 11:51:54

标签: c# algorithm image-processing

寻找一个足够快且仍然优雅的记忆力。该图像是一个24bpp的System.Drawing.Bitmap。

10 个答案:

答案 0 :(得分:14)

如果您需要一个确切的数字,那么您将不得不遍历所有像素。由于颜色的稀疏性,可能将颜色和计数存储在散列中是最好的方法。

在散列中使用Color.ToArgb()而不是颜色对象也可能是一个好主意。

此外,如果速度是一个主要问题,您不希望使用像GetPixel(x,y)这样的函数 - 而是尝试一次处理块(一次一行)。如果可以,请获取指向图像内存开头的指针并使其不安全。

答案 1 :(得分:11)

之前从未实现过类似的东西,但正如我所看到的,是一个原始的实现:

对于24位图像,图像可以具有的最大颜色数是(2 ^ 24,图像像素数)的最小值。

您只需记录是否已计算特定颜色,而不记录计数的次数。这意味着您需要1位来记录是否计算每种颜色。这是2MB的内存。遍历像素,在2MB颜色集映射中设置相关位。最后迭代颜色集映射计算设置位(如果你很幸运,你将有一个POPCNT指令来帮助这个)。

对于较小的图像和较低的颜色深度,最好保留颜色表并计算图像中的每种颜色。

答案 2 :(得分:6)

这里的大多数人都建议可能会很快的解决方案(实际上只使用2 MB的内存可能是可以接受的内存使用速度非常快;带散列的那个可能更快,但它肯定会使用超过2 MB的内存)。编程总是在内存使用和CPU时间之间进行权衡。如果你愿意“浪费”更多的内存,或者你可以通过“浪费”更多的计算时间来减慢结果,你通常可以更快地获得结果,但这通常可以为你提供大量的内存。

这是迄今为止没有人提出过的解决方案。它可能是占用内存最少的内存(您可以优化它,因此它几乎不会使用比将图像保留在内存中所需的更多内存,但是,图像将被更改,尽管您可能必须先将其复制)。我怀疑它能否在速度上击败散列或位掩码解决方案,如果内存是您最关心的问题,那就太有趣了。

  1. 按颜色对图像中的像素进行排序。您可以轻松地将每个像素转换为32位数字,并且可以将32位数字相互比较,一个数字小于另一个数字,大于或等于。如果使用Quicksort,除了额外的堆栈空间之外,不需要额外的存储空间进行排序。如果你使用Shellsort,根本不需要额外的内存(尽管Shellsort会比Quicksort慢很多)。

    int num =(RED<< 16)+(GREEN<<<<<<<<<<<"     

  2. 一旦你像这样对像素进行了排序(这意味着你已经在图像中重新排列它们),所有相同颜色的像素总是彼此相邻。因此,您可以只对图像进行一次迭代,并查看颜色变化的频率。例如。将像素的当前颜色存储在(0,0),然后启动一个值为1的计数器。下一步是转到(0,1)。如果它与以前颜色相同,则无需执行任何操作,继续下一个像素(0,2)。但是,如果它不相同,请将计数器增加1并记住下一次迭代时该像素的颜色。

  3. 一旦你查看了最后一个像素(并且可能再次增加了计数器,如果它与第二个最后一个像素不同),则计数器包含唯一颜色的数量。

  4. 在任何情况下,无论解决方案如何,都必须至少迭代一次所有像素,因此它对此解决方案的影响不会比其他解决方案更慢或更快。此算法的速度取决于您按颜色对图像像素进行排序的速度。

    正如我所说的,当速度是你的主要音乐会时这个算法很容易被打败(这里的其他解决方案可能都更快),但我怀疑当你的主要关注内存使用时它可以被打败,因为除了计数器之外,足够用于存储一种颜色的存储空间和图像本身的存储空间,如果您选择的排序算法需要任何存储空间,则只需要额外的内存。

答案 3 :(得分:4)

var cnt = new HashSet<System.Drawing.Color>();

foreach (Color pixel in image)
    cnt.Add(pixel);

Console.WriteLine("The image has {0} distinct colours.", cnt.Count);

/编辑:正如Lou所说,由于.GetArgb()实现Color的方式,使用Color代替GetHashCode值本身可能会稍快一些。

答案 4 :(得分:3)

这里的大多数其他实现都会变慢。为了快速,您需要直接扫描线访问和某种稀疏矩阵来存储颜色数据。

首先我将描述32bpp的情况,它更容易:

  • HashSet:颜色稀疏矩阵
  • ImageData:使用 BitmapData直接反对 访问底层内存
  • PixelAccess:使用int *来引用 你可以将内存作为整数 迭代

对于每次迭代,只需执行该整数的hashset.add。最后,只看到HashSet中有多少个键,这就是颜色的总数。重要的是要注意调整HashSet的大小非常痛苦(O(n),其中n是集合中的项目数),因此您可能想要构建一个合理大小的HashSet,可能像imageHeight * imageWidth / 4会很好。

在24bpp的情况下,PixelAccess需要是一个字节*,你需要为每种颜色迭代超过3个字节才能构造一个int。对于3个第一位中的每个字节,向左移动8(一个字节)并将其添加到整数。你现在有一个由32位int表示的24bpp Color,其余的都是相同的。

答案 5 :(得分:2)

您没有准确定义唯一的颜色。如果你实际上是指真正独特的代码值(而不是在视觉上相同),那么唯一确切的解决方案是使用其他答案中描述的技术之一来实际计算它们。

如果您正在寻找视觉上相似的颜色,这会快速提炼到调色板映射问题,您正在寻找256种最佳的独特颜色,以最接近地代表原始的完整动态颜色范围图像。对于大多数图像来说,令人惊讶的是,当这256种颜色被很好地选择时,从24位和高达1600万种不同颜色开始的图像可以被映射到仅具有256种独特颜色的图像。那些正确的256色(对于这个例子)的最佳选择已经被证明是NP完全的,但是有一些实用的解决方案可以非常接近。搜索一个名叫世杰万的人的文章和他的作品。

如果您正在寻找图像中代码值颜色数的近似值,我会使用无损压缩方案压缩图像。压缩率将直接与图像中唯一代码值的数量相关。您甚至不必保留压缩输出,只是累积沿途的字节数并丢弃实际的输出数据。使用一组示例图像作为参考,您可以在压缩比和图像中不同代码值的数量之间构建一个查找表。同样,这种最后的技术虽然非常快,但肯定会是近似值,但它应该相当好地相关联。

答案 6 :(得分:1)

在现代显卡之前,当大多数机器以256色调色板模式运行时,这是一个相当引人注目的领域。对处理能力和内存的限制只会产生一些可能对你有用的约束 - 因此搜索处理调色板的算法可能会产生一些用处。

答案 7 :(得分:1)

这取决于您要分析的图像类型。对于24位图像,您将需要高达2MB的内存(因为在最坏的情况下,您必须处理每种颜色)。对于这个,位图是最好的想法(你有一个2 MB的位图,其中每个位对应一个颜色)。这对于具有高色数的图像来说是一个很好的解决方案,可以在O(#pixels)中实现。对于16位图像,使用此技术只需要8 kB这个位图。

但是,如果您的图片颜色不多,最好使用其他颜色。但是你需要进行某种检查来指出你应该使用哪种算法......

答案 8 :(得分:1)

图像中唯一颜色的最大数量等于像素数,因此从过程的一开始就可以预测。 使用Konrad提出的HashSet方法似乎是一个合理的解决方案,因为散列的大小不应该大于像素数,而使用JeeBee建议的位图方法需要512 MB的32位image(如果有一个Alpha通道,并且确定这有助于颜色的唯一性)

但是,HashSet方法的性能可能比“每颜色位”方法更糟糕 - 你可能想要尝试两种方法并使用大量不同的图像做一些基准测试

答案 9 :(得分:0)

color quantization的现代流行实现使用octree数据结构。注意维基百科页面,内容非常好。八叉树的优点是可以根据需要进行内存限制,因此您可以对整个图像进行采样,并在没有太多额外内存的情况下决定调色板。理解了这一概念后,请点击1996 Dr Dobb's journal article's source code

的链接

由于这是一个C#问题,请参阅2003年5月的MSDN文章Optimizing Color Quantization for ASP.NET Images,其中包含一些源代码。