如何计算位图的平均 rgb 颜色值

Posted

技术标签:

【中文标题】如何计算位图的平均 rgb 颜色值【英文标题】:How to calculate the average rgb color values of a bitmap 【发布时间】:2010-11-07 06:58:30 【问题描述】:

在我的 C# (3.5) 应用程序中,我需要获取位图的红色、绿色和蓝色通道的平均颜色值。最好不使用外部库。这可以做到吗?如果是这样,怎么做?提前致谢。

试图让事情更精确一点:位图中的每个像素都有一定的 RGB 颜色值。我想获取图像中所有像素的平均 RGB 值。

【问题讨论】:

嗯,天真的方法是逐个像素地获取 RGB 值,我确定这不是您想要的。你能详细说明你在寻找什么样的平均值吗? 你是对的。希望现在好多了。 可以以不同的方式逐个像素地进行 - 查看答案。我想知道 GPU 是否可以提供帮助。 【参考方案1】:

这种东西会起作用,但它可能不够快,没有那么有用。

public static Color GetDominantColor(Bitmap bmp)


       //Used for tally
       int r = 0;
       int g = 0;
       int b = 0;

     int total = 0;

     for (int x = 0; x < bmp.Width; x++)
     
          for (int y = 0; y < bmp.Height; y++)
          
               Color clr = bmp.GetPixel(x, y);

               r += clr.R;
               g += clr.G;
               b += clr.B;

               total++;
          
     

     //Calculate average
     r /= total;
     g /= total;
     b /= total;

     return Color.FromArgb(r, g, b);

【讨论】:

会很慢。只是测试它。 GetPixel() 实际上做的是锁定位图并检索单个像素。【参考方案2】:

最快的方法是使用不安全的代码:

BitmapData srcData = bm.LockBits(
            new Rectangle(0, 0, bm.Width, bm.Height), 
            ImageLockMode.ReadOnly, 
            PixelFormat.Format32bppArgb);

int stride = srcData.Stride;

IntPtr Scan0 = srcData.Scan0;

long[] totals = new long[] 0,0,0;

int width = bm.Width;
int height = bm.Height;

unsafe

  byte* p = (byte*) (void*) Scan0;

  for (int y = 0; y < height; y++)
  
    for (int x = 0; x < width; x++)
    
      for (int color = 0; color < 3; color++)
      
        int idx = (y*stride) + x*4 + color;

        totals[color] += p[idx];
      
    
  


int avgB = totals[0] / (width*height);
int avgG = totals[1] / (width*height);
int avgR = totals[2] / (width*height);

注意:我没有测试这段代码...(我可能偷工减料)

此代码还假定为 32 位图像。对于 24 位图像。将 x*4 更改为 x*3

【讨论】:

偷了上面的代码,修复了它包装在一个函数中以回答另一个SO问题***.com/questions/6177499/… 你能解释一下为什么它这么快吗? @C.Ross 我相信这是因为我们将所有图像数据转换为字节并访问字节数组,而不是调用函数来确定给定点的 rbg。此高度width + imagedata->bytes 的成本。其他高度的成本width*imagedata->rbg. 上述代码最后三行不正确。位图数据区域中的颜色字节有序BGR,所以红色通道实际上是totals[2],蓝色通道是totals[0]。 哇。这样做与使用GetPixel 的区别是惊人的。使用GetPixel 循环两次 500x600 图像大约需要 1000 毫秒,使用此方法需要大约 13 毫秒。【参考方案3】:

这里有一个更简单的方法:

Bitmap bmp = new Bitmap(1, 1);
Bitmap orig = (Bitmap)Bitmap.FromFile("path");
using (Graphics g = Graphics.FromImage(bmp))

    // updated: the Interpolation mode needs to be set to 
    // HighQualityBilinear or HighQualityBicubic or this method
    // doesn't work at all.  With either setting, the results are
    // slightly different from the averaging method.
    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
    g.DrawImage(orig, new Rectangle(0, 0, 1, 1));

Color pixel = bmp.GetPixel(0, 0);
// pixel will contain average values for entire orig Bitmap
byte avgR = pixel.R; // etc.

基本上,您使用 DrawImage 将原始位图复制到 1 像素位图。然后,该 1 个像素的 RGB 值将代表整个原始像素的平均值。 GetPixel 相对较慢,但仅当您在大型位图上逐个像素地使用它时。在这里调用一次没什么大不了的。

使用 LockBits 确实很快,但一些 Windows 用户有防止执行“不安全”代码的安全策略。我之所以提到这一点,是因为这个事实最近让我很不爽。

更新:将 InterpolationMode 设置为 HighQualityBicubic,此方法所需的时间大约是使用 LockBits 进行平均的两倍;使用 HighQualityBilinear,它只需要比 LockBits 稍长的时间。因此,除非您的用户有禁止unsafe 代码的安全策略,否则绝对不要使用我的方法。

更新 2:随着时间的推移,我现在意识到为什么这种方法根本行不通。即使是最高质量的插值算法也只包含几个相邻像素,因此在不丢失信息的情况下可以压缩多少图像是有限度的。无论您使用什么算法,将图像压缩到一个像素都远远超出了这个限制。

做到这一点的唯一方法是逐步缩小图像(可能每次缩小一半),直到将其缩小到一个像素的大小。我无法用语言来表达写这样的东西是多么浪费时间,所以我很高兴我一想到它就停下来了。 :)

请不要再为这个答案投票 - 这可能是我有史以来最愚蠢的想法。

【讨论】:

哈哈,有趣。我想知道它是否比 LockBits 方法快? (我自己会为这两个计时,但我很懒!哈哈)+1 尽管明确要求不投票,但还是无法抗拒。看了你的回答,我学到了很多。 感谢您提及阻止不安全代码的安全策略 这不是一个愚蠢的想法。这是一个好主意,碰巧是行不通的。 :-) 这不会产生预期的主色(与其他回复一样)。

以上是关于如何计算位图的平均 rgb 颜色值的主要内容,如果未能解决你的问题,请参考以下文章

GetDIBits 遍历位图 获取像素的颜色值(RGB)

如何计算RGB数字图像处理 亮度的亮度值

rgba()如何知道RGB颜色是由哪个RGB值经过百分之三十透明过来的?有计算方法吗?

使用开放CV和python提取或获取图像中几个点的平均RGB颜色

RGB模式的图像中每个像素的颜色值都有R、G、B三个数值来决定,每个数值的范围是0到255。当R、G、B 数值相

详细请教:关于十进制RGB颜色值转换十六进制颜色值的算法