简体   繁体   中英

How to get the most frequently appearing pixel in a bmp

As a function to determine "the base color of an image" I'm trying to implement the following code:

typedef unsigned long dword;
typedef unsigned short word;
typedef unsigned char BYTE;

typedef struct
{
    BYTE R;
    BYTE G;
    BYTE B;
} RGB;

RGB
bitfox_get_primecolor_direct
(char *FILE_NAME)
{
    RGB primecolor;
    BYTE rgb[3];

    dword *counts;
    dword max_count = 0;

    FILE* fp = fopen("sample.bmp", "rb");

    counts = calloc(pow(256, 3), sizeof(*counts));
    fseek(fp, 54, SEEK_SET);

    while (fread (rgb, sizeof(BYTE), 3, fp) == 1)
    {
        dword idx = (((dword)rgb[0]) << 16) | (((dword)rgb[1]) << 8) | (dword)rgb[2];
        if (++counts[idx] > max_count) max_count = idx;
    }

    primecolor.R = (rgb[max_count] >> 16) & 0xFF;
    primecolor.G = (rgb[max_count] >> 8) & 0xFF;
    primecolor.B = rgb[max_count] & 0xFF;

    free(counts);
    fclose(fp);
    return primecolor;
}

It is supposed to be a fast algorithm ( not really thrifty when it comes to RAM ) to return RGB struct, with the base color of an image. However.. it returns the incorrect color. What am i doing wrong?

As others have pointed out already, there are several issues, the most important one the indexing of max_count . You can make max_count an index and then derive the colour from that index with the reverse logic that you used when constructing the index. This is how Steffen's and user694733's answers work.

You could also keep max_count as a count and assign primecolor when a new max count is found. That saves you the backward calculation.

Thee's a potential other issue to do with padding. The BMP format stores its data line-wise, but the number of bytes in each line must be a multiple of 4. In your case, the image is 262 pixels wide. Each line is 786 bytes long, so it must be padded to 788. If you want to take padding into account, you must know the width of the image.

Another source of confusion is that you pass a parameter FILE_NAME to your function, but always open "sample.bmp" , so you might not really get what you want.

Also, but this is a niggle, I think that the integer cube pow(256, 3) is better rendered as 256 * 256 * 256 .

Here's varaint that works for me (it needs more error checking):

RGB bitfox_get_primecolor_direct(char *FILE_NAME)
{
    RGB primecolor = {0, 0, 0};
    BYTE hdr[54];

    dword *counts;
    dword max_count = 0;

    word w, h;
    word i, j;

    FILE* fp = fopen(FILE_NAME, "rb");

    counts = calloc(256 * 256 * 256, sizeof(*counts));

    // Read header to get width and height
    fread(hdr, sizeof(hdr), 1, fp);        
    w = (hdr[19] << 8) | hdr[18];
    h = (hdr[23] << 8) | hdr[22];

    // Loop over pixels
    for (i = 0; i < h; i++) {
        for (j = 0; j < w; j++) {
            RGB rgb;
            dword idx;

            if (fread(&rgb, 3, 1, fp) < 1) {
                fprintf(stderr, "Unexpected end of file.\n");
                exit(1);
            }
            idx = (rgb.R << 16) | (rgb.G << 8) | rgb.B;
            if (++counts[idx] > max_count) {
                max_count = counts[idx];
                primecolor = rgb;
            }
        }

        // Treat padding
        j = 3 * w;
        while (j++ % 4) getc(fp);
    }

    free(counts);
    fclose(fp);

    return primecolor;
}

Alright. Several points: One is the pow function. I found this not to behave a expected. At least on my implementation.

counts = calloc((dword)pow(256.0, 3.0), sizeof(dword));

works for me.

fread should check for the number of elements:

fread (rgb, sizeof(BYTE), 3, fp) == 3

The checking which value is max should probably be:

if (++(counts[idx]) > counts[max_count]) {
    max_count = idx;
}

And finally the extraction of the color can be done by:

primecolor.R = max_count >> 16 & 0xFF;
primecolor.G = max_count >> 8 & 0xFF;
primecolor.B = max_count & 0xFF;

With this changes it works for me. The only thing is that I get BGR. But this may result from a different image...

Complete code:

#include <stdio.h>
#include <math.h>

typedef unsigned long dword;
typedef unsigned short word;
typedef unsigned char BYTE;

typedef struct
{
    BYTE R;
    BYTE G;
    BYTE B;
} RGB;

void main (void)
{
    RGB primecolor;
    BYTE rgb[3];
    dword *counts;
    dword max_count = 0;
    FILE* fp = fopen("c:/tmp/test.bmp", "rb");
    counts = calloc((dword)pow(256.0, 3.0), sizeof(dword));
    fseek(fp, 54, SEEK_SET);
    while (fread (rgb, sizeof(BYTE), 3, fp) == 3)
    {
        BYTE r = rgb[0];
        BYTE g = rgb[1];
        BYTE b = rgb[2];
        dword idx = (((dword)rgb[0]) << 16) | (((dword)rgb[1]) << 8) | (dword)rgb[2];
        if (++(counts[idx]) > counts[max_count]) {
            max_count = idx;
        }
    }
    primecolor.R = max_count >> 16 & 0xFF;
    primecolor.G = max_count >> 8 & 0xFF;
    primecolor.B = max_count & 0xFF;
    free(counts);
    fclose(fp);
    printf("%d %d %d ",primecolor.R,primecolor.G,primecolor.B);
}

This prints out 229 20 35 for an image create with gimp filled with color (RGB): 35,20,229.

In addition to issues mentioned in comments (not checking return values, invalid fread return value check, possibly assuming incorrect file format...), there is an issue with max_count :

if (++counts[idx] > max_count) max_count = idx;

In condition you assume that max_count is a counter. However, in assigment you treat max_count as index value (which is equivalent of color). Which one it's supposed to be?

rgb[max_count] at the end of program makes no sense either. Again you are treating max_count as index (again color). rgb is supposed to be latest read color, thus it shouldn't be used used at all.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM