How to Calculate the Average Rgb Color Values of a Bitmap

How to calculate the average rgb color values of a bitmap

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

How to get average RGB value of Bitmap on Android?

I think below code for exact answer to you.
Get the Average(Number of pixels)of Red, Green and Blue value for the given bitmap.

Bitmap bitmap = someBitmap; //assign your bitmap here
int redColors = 0;
int greenColors = 0;
int blueColors = 0;
int pixelCount = 0;

for (int y = 0; y < bitmap.getHeight(); y++)
{
for (int x = 0; x < bitmap.getWidth(); x++)
{
int c = bitmap.getPixel(x, y);
pixelCount++;
redColors += Color.red(c);
greenColors += Color.green(c);
blueColors += Color.blue(c);
}
}
// calculate average of bitmap r,g,b values
int red = (redColors/pixelCount);
int green = (greenColors/pixelCount);
int blue = (blueColors/pixelCount);

C - rgb values - Calculating the average of rgb values for a blur filter

Note that average* variables are uninitialized, so when you sum to them, you have UB. They need to be preset to 0, certainly at the outset, but possibly before each major loop.


Also, aside from your other issues that others have noted, you may need to do saturation math.

That's because for rgbt* (e.g. rgbtRed) is a byte, so the value can be clipped incorrectly.

You are doing:

image[i][j].rgbtRed = round((float)averageRed / 6);

This can be rewritten as:

averageRed = round((float)averageRed / 6);
image[i][j].rgbtRed = averageRed;

But, if (e.g.) averageRed was 256, then rgbtRed would end up as 1 [because the assignment to image is [effectively]:

image[i][j].rgbtRed = averageRed & 0xFF;

So, instead of storing bright red, you're storing nearly black. The final would need to be 255, the "saturated" maximum color value.

So, to fix that [or merely to guard against it], do:

averageRed = round((float)averageRed / 6);
if (averageRed > 255)
averageRed = 255;
image[i][j].rgbtRed = averageRed;

Edit: Upon further reflection, you only need to do this if the right hand side can exceed 255, but I'm [now] not sure that it can. To check against this, you could add (e.g.):

if (averageRed > 255) {
fprintf(stderr,"value overflow\n");
exit(1);
}

You could wrap this in an #ifdef, do tests, and if it doesn't trigger, you can remove it later.


UPDATE:

As stupid as the question might sound, but how can the value ever reach 256? Even if every pixel is white, none of the values can reach 256 or where is my mistake? (1 white Px: 255 255 255 -> 10 white Px: 2550 2550 2550 / 10 --> .....

Yes, per my "Edit:" above, it may not. I recently answered a similar question where the value could exceed 255.

But, your runtime error shows that the value does exceed the capacity of a byte (i.e. unsigned char).

That is probably due to the uninitialized sum variables.

But, also it is because the sum/average variables are not being reset at the start of a loop. You never reset them, so they just continue to grow and grow.

They need to be reset after you complete each 3x3 convolution kernel (i.e. after you store each output pixel).

And, I don't think your for (n = j; n <= 1; n++) loops are correct. You're mixing up absolute coordinate values (from j) and coordinate offsets.

You probably want something like:

for (m = -1; m <= 1; m++) {
for (n = -1; n <= 1; n++) {
averageRed += image[i + m][j + n].rgbtRed;
}
}

UPDATE #2:

It may be easier to have a single set of loops, using some extra limit variables.

Also, on a per pixel basis, using floating point (i.e. round) can be slow. Although, I didn't do it, it can be replaced with integer math easily enough.

Further, using more descriptive names instead of i, j, m, n can help make the code a bit easier to understand and maintain.

Anyway, here's a somewhat refactored version of your function that is a bit simpler:

#include <math.h>

#if 1
typedef struct {
unsigned char rgbtRed;
unsigned char rgbtGreen;
unsigned char rgbtBlue;
} __attribute__((__packed__)) RGBTRIPLE;
#endif

// Blur image
void
blur(int height, int width,
RGBTRIPLE image[height][width],
RGBTRIPLE imgout[height][width])
{
int wid = width - 1;
int hgt = height - 1;
RGBTRIPLE *pixel;

// For each row..
for (int ycur = 0; ycur <= hgt; ++ycur) {
int ylo = (ycur == 0) ? 0 : -1;
int yhi = (ycur == hgt) ? 0 : 1;

// ..and then for each pixel in that row...
for (int xcur = 0; xcur <= wid; ++xcur) {
int xlo = (xcur == 0) ? 0 : -1;
int xhi = (xcur == wid) ? 0 : 1;

int avgRed = 0;
int avgGreen = 0;
int avgBlue = 0;

for (int yoff = ylo; yoff <= yhi; ++yoff) {
for (int xoff = xlo; xoff <= xhi; ++xoff) {
pixel = &image[ycur + yoff][xcur + xoff];
avgRed += pixel->rgbtRed;
avgGreen += pixel->rgbtGreen;
avgBlue += pixel->rgbtBlue;
}
}

int tot = ((yhi - ylo) + 1) * ((xhi - xlo) + 1);

pixel = &imgout[ycur][xcur];
pixel->rgbtRed = roundf((float) avgRed / tot);
pixel->rgbtGreen = roundf((float) avgGreen / tot);
pixel->rgbtBlue = roundf((float) avgBlue / tot);
}
}
}

Average color of bitmap

You can develop a c++ dll doing same calculation with SIMD optimized/vectorized code using intrinsics. Then cpu usage will be much more efficient even at same usage percent. Process the nonaligned header part, then process remaining aligned part using faster instrinsic functions.

If this isn't enough, try moving only half or even quarter of image to GPU since pci-e is bottleneck.

Pipelining also helps to hide some of latency of copying to gpu but uses more CPU but finishes quicker so less total cycles are used.

If a bitmap is already in cpu cache, it should be able to process it concurrently while GPU is processing a "mapped" memory tile(another bitmap or part of same bitmap) without bottlenecking RAM. Don't copy to GPU if data is meant to be streamed. Let GPU map it on its own controller using proper access functions or flags.

The "mapping"'s start point could be bitmap byte array's first multiple of 4096 addressed element.

If you have an integrated-gpu, try opencl with it, because it is closer to RAM.


For pure C# solution, try multiple accumulators to use cpu pipelines better. Use them in unsafe context. Read by int or long, not bytes. Then process it using bithacks unless C# is already doing vectorizations.


Scanning for an average doesn't use multiplication units. So you can multiply things with some interleaved code or doing async. Maybe you can blend some other bitmaps meanwhile?


c[i]=a[i]+b[i]

is 18 times faster with fully optimized gpgpu method compared to simple C# one-liner. I'm using Visual Studio 2015 Community Edition (project in release mode and 64-bit targeted). Using Intel HD-400 iGPU(600MHz) and C3060(1.6GHz) (single channel RAM) this is a low end laptop and CPU usage was %50ish instead of %70ish of pure C#.

Get average RGB values from picture displayed inside Image control in vb.net

Here is a straightforward, pure WPF solution, which directly accesses the pixel buffer of a BitmapSource. It works for the Bgr24, Bgr32, Bgra32 and Pbgra32 formats. In case of Pbgra32 all alpha values should be 255, otherwise you may have to divide each pixel's (pre-multiplied) color values by alpha / 255.

public Color GetAverageColor(BitmapSource bitmap)
{
var format = bitmap.Format;

if (format != PixelFormats.Bgr24 &&
format != PixelFormats.Bgr32 &&
format != PixelFormats.Bgra32 &&
format != PixelFormats.Pbgra32)
{
throw new InvalidOperationException("BitmapSource must have Bgr24, Bgr32, Bgra32 or Pbgra32 format");
}

var width = bitmap.PixelWidth;
var height = bitmap.PixelHeight;
var numPixels = width * height;
var bytesPerPixel = format.BitsPerPixel / 8;
var pixelBuffer = new byte[numPixels * bytesPerPixel];

bitmap.CopyPixels(pixelBuffer, width * bytesPerPixel, 0);

long blue = 0;
long green = 0;
long red = 0;

for (int i = 0; i < pixelBuffer.Length; i += bytesPerPixel)
{
blue += pixelBuffer[i];
green += pixelBuffer[i + 1];
red += pixelBuffer[i + 2];
}

return Color.FromRgb((byte)(red / numPixels), (byte)(green / numPixels), (byte)(blue / numPixels));
}

As the Image control's Source property is of type ImageSource, you have to cast it to BitmapSource before passing it to the method:

var bitmap = (BitmapSource)image.Source; 
var color = GetAverageColor(bitmap);

How can I get the average color of an image?

Bitmap bitmap = someFunctionReturningABitmap();
long redBucket = 0;
long greenBucket = 0;
long blueBucket = 0;
long pixelCount = 0;

for (int y = 0; y < bitmap.getHeight(); y++)
{
for (int x = 0; x < bitmap.getWidth(); x++)
{
Color c = bitmap.getPixel(x, y);

pixelCount++;
redBucket += Color.red(c);
greenBucket += Color.green(c);
blueBucket += Color.blue(c);
// does alpha matter?
}
}

Color averageColor = Color.rgb(redBucket / pixelCount,
greenBucket / pixelCount,
blueBucket / pixelCount);

get average color from bmp

The average color is not neccessarily the color most used. I recommend calculating the HUE of pixels which have saturation over a certain threshold, and use an array to create a histogram of the image. (How many times a certain hue value was used).

Then smooth the histogram (calculate local average values with both neighbours), then get the place where this smoothed histogram takes the maximal value.

You can get HSL values with:

Color.GetHue
Color.GetSaturation
Color.GetBrightness

How to get an average hue / saturation of a bitmap?

You could calculate the average hue/sat by looking at each of the pixels and simply calculating it yourself using the HSV methods in Color to get it from the RGB values. Since you would be doing it on a single image and not on a video preview it shouldn't be too hard, nor would it take too long on a phone to look over all the pixels.



Related Topics



Leave a reply



Submit