English 中文(简体)
计算图像中唯一颜色数量的算法
原标题:
  • 时间:2008-09-24 11:51:53
  •  标签:

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

问题回答

如果你需要一个确切的数字,那么你必须在所有的像素上循环。由于颜色的稀疏性,将颜色和计数存储在哈希中可能是最好的方法。

在hash中使用Color.ToArgb()代替Color对象可能也是一个好主意。

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

以前从未实现过这样的东西,但在我看来,这是一个原始的实现:

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

你只需要记录一种特定的颜色是否被计数,而不需要记录它被计数了多少次。这意味着你需要1个比特来记录每种颜色是否被计数。这是2MB的内存。在像素之间进行迭代,在2MB颜色集地图中设置相关位。最后,遍历颜色集映射,计算集位(如果幸运的话,您将有POPCNT指令来帮助实现这一点)。

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

这里的大多数人都提出了可能很快的解决方案(实际上,只使用2MB的解决方案在内存使用方面可能是可以接受的,而且非常快;带有哈希的解决方案可能更快,但它肯定会使用超过2MB的内存)。编程总是在内存使用和CPU时间之间进行权衡。如果你愿意“浪费”更多的内存,你通常可以更快地得到结果,或者你可以通过“浪费”更长的计算时间来减慢结果,但这通常会保护你很多内存。

这里有一个迄今为止没有人提出的解决方案。它可能是内存成本最低的一个(您可以对其进行优化,因此它几乎不会使用比将图像保存在内存中所需的内存更多的内存,然而,图像会被更改,尽管您可能必须先复制它)。我怀疑它能否在速度上击败哈希或位掩码解决方案,如果内存是你最关心的问题,那就很有趣了。

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

    int num=(红色<;<;16)+(绿色<;<;8)+蓝色;

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

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

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

正如我所说,当速度是你的主要因素时,这种算法很容易被击败(这里的其他解决方案可能都更快),但我怀疑当内存使用是你主要关心的问题时,它是否会被击败,因为除了计数器、足够的存储空间来存储一种颜色以及图像本身的存储空间之外,如果你选择的排序算法需要额外的内存,它只需要额外的存储器。

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的方式。

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

首先,我将描述32bpp的情况,它要简单得多:

  • HashSet: Sparse Matrix of colors
  • ImageData: Use a BitmapData object to directly access the underlying memory
  • PixelAccess: Use a int* to reference the memory as ints which you can iterate through

对于每个迭代,只需对该整数进行hashset.add即可。最后看看HashSet中有多少个键,这就是颜色的总数。需要注意的是,调整哈希集的大小真的很痛苦(O(n),其中n是集合中的项目数),因此您可能想首先构建一个大小合理的哈希集,也许像imageHeight*imageWidth/4这样的东西会很好。

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

你没有准确地定义独特的颜色。如果你的意思是真正唯一的代码值(而不是视觉上相同的代码值),那么唯一确切的解决方案是使用其他答案中描述的技术之一对它们进行计数。

如果您正在寻找视觉上相似的颜色,这确实会很快归结为调色板映射问题,即您正在寻找256种最佳的唯一颜色,以最接近地表示原始的全动态颜色范围图像。对于大多数图像来说,令人惊讶的是,当256种颜色被很好地选择时,一个从24位减少到1600万种不同颜色的图像可以映射到只有256种唯一颜色的图像。那些正确的256种颜色的最佳选择(对于这个例子)已经被证明是NP完全的,但有一些实际的解决方案可以非常接近。搜索一个叫万世杰的人的论文和基于他工作的东西。

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

在现代显卡出现之前,当大多数机器都以256调色板模式运行时,这是一个相当令人感兴趣的领域。对处理能力和内存的限制只是强加了一种可能对你有用的约束——所以搜索处理调色板的算法可能会找到一些有用的东西。

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

然而,如果你的照片颜色不多,最好用其他颜色。但你需要进行某种检查,以表明你应该使用哪种算法。。。

The maximum number of unique colours in an image is equal to the number of pixels, so this is predictable from the very start of the process. Using the HashSet method proposed, by Konrad, would then seem to be a reasonable solution, as the size of the hash should be no greater than the number of pixels, whereas using the bitmap approach suggested by JeeBee would required 512 MB for a 32 bit image (If there s an Alpha channel, and this is determined to contribute to the uniqueness of the colour)

不过,HashSet方法的性能可能比逐色方法差——你可能想同时尝试这两种方法,并使用许多不同的图像进行一些基准测试

颜色量化使用八叉树数据结构。注意维基百科页面,内容非常好。八叉树的优点是内存有限,所以你可以对整个图像进行采样,并在没有太多额外内存的情况下在调色板上做出决定。一旦你理解了这个概念,请按照1996多布博士的期刊文章的源代码

由于这是一个C#问题,请参阅2003年5月MSDN的文章优化ASP.NET图像的颜色量化,其中包括一些源代码。





相关问题
热门标签