c++image-processingvectorhistogram

Add 1 to vector<unsigned char> value - Histogram in C++


I guess it's such an easy question (I'm coming from Java), but I can't figure out how it works.

I simply want to increment an vector element by one. The reason for this is, that I want to compute a histogram out of image values. But whatever I try I just can accomplish to assign a value to the vector. But not to increment it by one!

This is my histogram function:

void histogram(unsigned char** image, int height,
        int width, vector<unsigned char>& histogramArray) {

    for (int i = 0; i < width; i++) {
        for (int j = 0; j < height; j++) {


//          histogramArray[1] = (int)histogramArray[1] + (int)1;
// add histogram position by one if greylevel occured
            histogramArray[(int)image[i][j]]++;
        }
    }
// display output
    for (int i = 0; i < 256; i++) {
        cout << "Position: " << i << endl;
        cout << "Histogram Value: " << (int)histogramArray[i] << endl;
    }
}

But whatever I try to add one to the histogramArray position, it leads to just 0 in the output. I'm only allowed to assign concrete values like:

histogramArray[1] = 2;

Is there any simple and easy way? I though iterators are hopefully not necesarry at this point, because I know the exakt index position where I want to increment something.

EDIT: I'm so sorry, I should have been more precise with my question, thank you for your help so far! The code above is working, but it shows a different mean value out of the histogram (difference of around 90) than it should. Also the histogram values are way different than in a graphic program - even though the image values are exactly the same! Thats why I investigated the function and found out if I set the histogram to zeros and then just try to increase one element, nothing happens! This is the commented code above:

for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                histogramArray[1]++;
    // add histogram position by one if greylevel occured
                // histogramArray[(int)image[i][j]]++;
            }
        }

So the position 1 remains 0, instead of having the value height*width. Because of this, I think the correct calculation histogramArray[image[i][j]]++; is also not working properly.

Do you have any explanation for this? This was my main question, I'm sorry.

Just for completeness, this is my mean function for the histogram:

unsigned char meanHistogram(vector<unsigned char>& histogram) {
    int allOccurences = 0;
    int allValues = 0;
    for (int i = 0; i < 256; i++) {
        allOccurences += histogram[i] * i;
        allValues += histogram[i];
    }
    return (allOccurences / (float) allValues) + 0.5f;
}

And I initialize the image like this:

unsigned char** image= new unsigned char*[width];
for (int i = 0; i < width; i++) {
image[i] = new unsigned char[height];
}

But there shouldn't be any problem with the initialization code, since all other computations work perfectly and I am able to manipulate and safe the original image. But it's true, that I should change width and height - since I had only square images it didn't matter so far. The Histogram is created like this and then the function is called like that:

vector<unsigned char> histogramArray(256);
histogram(array, adaptedHeight, adaptedWidth, histogramArray);

So do you have any clue why this part histogramArray[1]++; don't increases my histogram? histogramArray[1] remains 0 all the time! histogramArray[1] = 2; is working perfectly. Also histogramArray[(int)image[i][j]]++; seems to calculate something, but as I said, I think it's wrongly calculating. I appreciate any help very much! The reason why I used a 2D Array is simply because it is asked for. I like the 1D version also much more, because it's way simpler!


Solution

  • You see, the current problem in your code is not incrementing a value versus assigning to it; it's the way you index your image. The way you've written your histogram function and the image access part puts very fine restrictions on how you need to allocate your images for this code to work.

    For example, assuming your histogram function is as you've written it above, none of these image allocation strategies will work: (I've used char instead of unsigned char for brevity.)

    char image [width * height]; // Obvious; "char[]" != "char **"
    char * image = new char [width * height]; // "char*" != "char **"
    char image [height][width]; // Most surprisingly, this won't work either.
    

    The reason why the third case won't work is tough to explain simply. Suffice it to say that a 2D array like this will not implicitly decay into a pointer to pointer, and if it did, it would be meaningless. Contrary to what you might read in some books or hear from some people, in C/C++, arrays and pointers are not the same thing!

    Anyway, for your histogram function to work correctly, you have to allocate your image like this:

    char** image = new char* [height];
    for (int i = 0; i < height; ++i)
        image[i] = new char [width];
    

    Now you can fill the image, for example:

    for (int i = 0; i < height; ++i)
        for (int j = 0; j < width; ++j)
            image[i][j] = rand() % 256; // Or whatever...
    

    On an image allocated like this, you can call your histogram function and it will work. After you're done with this image, you have to free it like this:

    for (int i = 0; i < height; ++i)
        delete[] image[i];
    delete[] image;
    

    For now, that's enough about allocation. I'll come back to it later.

    In addition to the above, it is vital to note the order of iteration over your image. The way you've written it, you iterate over your columns on the outside, and your inner loop walks over the rows. Most (all?) image file formats and many (most?) image processing applications I've seen do it the other way around. The memory allocations I've shown above also assume that the first index is for the row, and the second is for the column. I suggest you do this too, unless you've very good reasons not to.

    No matter which layout you choose for your images (the recommended row-major, or your current column-major,) it is in issue that you should always keep in your mind and take notice of.

    Now, on to my recommended way of allocating and accessing images and calculating histograms.

    I suggest that you allocate and free images like this:

    // Allocate:
    char * image = new char [height * width];
    
    // Free:
    delete[] image;
    

    That's it; no nasty (de)allocation loops, and every image is one contiguous block of memory. When you want to access row i and column j (note which is which) you do it like this:

    image[i * width + j] = 42;
    char x = image[i * width + j];
    

    And you'd calculate the histogram like this:

    void histogram (
        unsigned char * image, int height, int width,
         // Note that the elements here are pixel-counts, not colors!
        vector<unsigned> & histogram
    ) {
        // Make sure histogram has enough room; you can do this outside as well.
        if (histogram.size() < 256)
            histogram.resize (256, 0);
    
        int pixels = height * width;
        for (int i = 0; i < pixels; ++i)
            histogram[image[i]]++;
    }
    

    I've eliminated the printing code, which should not be there anyway. Note that I've used a single loop to go through the whole image; this is another advantage of allocating a 1D array. Also, for this particular function, it doesn't matter whether your images are row-major or column major, since it doesn't matter in what order we go through the pixels; it only matters that we go through all the pixels and nothing more.

    UPDATE: After the question update, I think all of the above discussion is moot and notwithstanding! I believe the problem could be in the declaration of the histogram vector. It should be a vector of unsigned ints, not single bytes. Your problem seems to be that the value of the vector elements seem to stay at zero when your simplify the code and increment just one element, and are off from the values they need to be when you run the actual code. Well, this could be a symptom of numeric wrap-around. If the number of pixels in your image are a a multiple of 256 (e.g. 32x32 or 1024x1024 image) then it is natural that the sum of their number would be 0 mod 256.

    I've already alluded to this point in my original answer. If you read my implementation of the histogram function, you see in the signature that I've declared my vector as vector<unsigned> and have put a comment above it that says this victor counts pixels, so its data type should be suitable.

    I guess I should have made it bolder and clearer! I hope this solves your problem.