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

时间:2022-11-17 21:23:25

In my C# (3.5) application I need to get the average color values for the red, green and blue channels of a bitmap. Preferably without using an external library. Can this be done? If so, how? Thanks in advance.

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

Trying to make things a little more precise: Each pixel in the bitmap has a certain RGB color value. I'd like to get the average RGB values for all pixels in the image.

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

3 个解决方案

#1


20  

The fastest way is by using unsafe code:

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

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);

Beware: I didn't test this code... (I may have cut some corners)

注意:我没有测试这个代码……(我可能抄近路)

This code also asssumes a 32 bit image. For 24-bit images. Change the x*4 to x*3

这段代码还包含一个32位的图像。24位图像。把x*4换成x*3

#2


10  

Here's a much simpler way:

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

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.

Basically, you use DrawImage to copy the original Bitmap into a 1-pixel Bitmap. The RGB values of that 1 pixel will then represent the averages for the entire original. GetPixel is relatively slow, but only when you use it on a large Bitmap, pixel-by-pixel. Calling it once here is no biggie.

基本上,您使用DrawImage将原始位图复制到一个1像素的位图中。这个1像素的RGB值将代表整个原始值的平均值。GetPixel的速度相对较慢,但只能在逐像素的大位图上使用。在这里打一次电话没什么大不了的。

Using LockBits is indeed fast, but some Windows users have security policies that prevent the execution of "unsafe" code. I mention this because this fact just bit me on the behind recently.

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

Update: with InterpolationMode set to HighQualityBicubic, this method takes about twice as long as averaging with LockBits; with HighQualityBilinear, it takes only slightly longer than LockBits. So unless your users have a security policy that prohibits unsafe code, definitely don't use my method.

更新:如果将插值模式设置为高质量双三次运算,则该方法所花费的时间大约是lockbit平均时间的两倍;对于高质量双线性,它只需要比LockBits长一点点。所以除非你的用户有一个安全政策禁止不安全的代码,绝对不要使用我的方法。

Update 2: with the passage of time, I now realize why this approach doesn't work at all. Even the highest-quality interpolation algorithms incorporate only a few neighboring pixels, so there's a limit to how much an image can be squashed down without losing information. And squashing a image down to one pixel is well beyond this limit, no matter what algorithm you use.

更新2:随着时间的推移,我现在意识到为什么这种方法根本不起作用。即使是高质量的插值算法也只包含几个相邻的像素,所以在不丢失信息的情况下,图像可以被压缩的程度是有限的。不管你用什么算法,把图像压缩到一个像素都远远超过这个极限。

The only way to do this would be to shrink the image in steps (maybe shrinking it by half each time) until you get it down to the size of one pixel. I can't express in mere words what an utter waste of time writing something like this would be, so I'm glad I stopped myself when I thought of it. :)

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

Please, nobody vote for this answer any more - it might be my stupidest idea ever.

拜托,没有人再投票给这个答案了——这可能是我最愚蠢的想法了。

#3


8  

This kind of thing will work but it may not be fast enough to be that useful.

这类事情会起作用,但它可能不够快,不会有那么大的用处。

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);
}

#1


20  

The fastest way is by using unsafe code:

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

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);

Beware: I didn't test this code... (I may have cut some corners)

注意:我没有测试这个代码……(我可能抄近路)

This code also asssumes a 32 bit image. For 24-bit images. Change the x*4 to x*3

这段代码还包含一个32位的图像。24位图像。把x*4换成x*3

#2


10  

Here's a much simpler way:

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

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.

Basically, you use DrawImage to copy the original Bitmap into a 1-pixel Bitmap. The RGB values of that 1 pixel will then represent the averages for the entire original. GetPixel is relatively slow, but only when you use it on a large Bitmap, pixel-by-pixel. Calling it once here is no biggie.

基本上,您使用DrawImage将原始位图复制到一个1像素的位图中。这个1像素的RGB值将代表整个原始值的平均值。GetPixel的速度相对较慢,但只能在逐像素的大位图上使用。在这里打一次电话没什么大不了的。

Using LockBits is indeed fast, but some Windows users have security policies that prevent the execution of "unsafe" code. I mention this because this fact just bit me on the behind recently.

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

Update: with InterpolationMode set to HighQualityBicubic, this method takes about twice as long as averaging with LockBits; with HighQualityBilinear, it takes only slightly longer than LockBits. So unless your users have a security policy that prohibits unsafe code, definitely don't use my method.

更新:如果将插值模式设置为高质量双三次运算,则该方法所花费的时间大约是lockbit平均时间的两倍;对于高质量双线性,它只需要比LockBits长一点点。所以除非你的用户有一个安全政策禁止不安全的代码,绝对不要使用我的方法。

Update 2: with the passage of time, I now realize why this approach doesn't work at all. Even the highest-quality interpolation algorithms incorporate only a few neighboring pixels, so there's a limit to how much an image can be squashed down without losing information. And squashing a image down to one pixel is well beyond this limit, no matter what algorithm you use.

更新2:随着时间的推移,我现在意识到为什么这种方法根本不起作用。即使是高质量的插值算法也只包含几个相邻的像素,所以在不丢失信息的情况下,图像可以被压缩的程度是有限的。不管你用什么算法,把图像压缩到一个像素都远远超过这个极限。

The only way to do this would be to shrink the image in steps (maybe shrinking it by half each time) until you get it down to the size of one pixel. I can't express in mere words what an utter waste of time writing something like this would be, so I'm glad I stopped myself when I thought of it. :)

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

Please, nobody vote for this answer any more - it might be my stupidest idea ever.

拜托,没有人再投票给这个答案了——这可能是我最愚蠢的想法了。

#3


8  

This kind of thing will work but it may not be fast enough to be that useful.

这类事情会起作用,但它可能不够快,不会有那么大的用处。

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);
}