1
votes

I am developping on Qt creator, with opencv.

I have to developp a program that does the histogram equalization of an image. My images are 16bits grayscale images so I cannot use the opencv function "equalizeHist" because it only works with 8bit grayscale images.

The code I wrote to do it is the following :

void equalizeHist_16U(Mat* img)
{
    long hist[65535] = {0};
    double ratio;
    int i, j;

    assert(img->channels() == 1);
    assert(img->type() == CV_16U);

    ratio = 65535.0 / (img->cols*img->rows);

    //Cumulative histogram calculation
    compute_hist_16U(img, hist, true);

    for(i=0 ; i<img->cols ; i++)
    {
        for(j=0 ; j<img->rows ; j++)
        {
            img->at<unsigned short>(j,i) = ratio*hist[img->at<unsigned short>(j,i)];
        }
    }

}

long compute_hist_16U (Mat* img, long* hist, bool cumul)
{
    unsigned short i, j, k;
    long* b;
    long max = 0;

    //is the image 16bits grayscale ?
    assert(img->channels() == 1);
    assert(CV_16U == img->type());

    //histogram calculation
    for(i=0 ; i<img->cols ; i++)
    {
        for(j=0 ; j<img->rows ; j++)
        {
            hist[img->at<unsigned short>(j,i)]++;
            if(hist[img->at<unsigned short>(j,i)] > max)
                max = hist[img->at<unsigned short>(j,i)];
        }
    }

    //Cumulative histogram calculation (if cumul=true)
    if(cumul)
    {
        for(b=hist ; b<hist+65535 ; b++)
        {
            *(b+1) += *b;
        }
    }
    return (cumul ? hist[65535] : max);
}

It does what I expected, now I want to do the histogram equialization of my image but only on a specified part of the image. I added x1,x2,y1,y2 parameters to my function and changed the bounds of my "for" like this (the lines of code i changed have arrows):

---->void equalizeHist_16U(Mat* img, int x1, int x2, int y1, int y2)
{
    long hist[65535] = {0};
    double ratio;
    int i, j;

    assert(img->channels() == 1);
    assert(img->type() == CV_16U);

    ratio = 65535.0 / (img->cols*img->rows);

    //Cumulative histogram calculation
    compute_hist_16U(img, hist, true);

    ---->for(i=x1 ; i<=x2 ; i++)
    {
        ---->for(j=y1 ; j<=y2 ; j++)
        {
            img->at<unsigned short>(j,i) = ratio*hist[img->at<unsigned short>(j,i)];
        }
    }

}

---->long compute_hist_16U (Mat* img, long* hist, bool cumul, int x1, int x2, int y1, int y2)
{
    unsigned short i, j, k;
    long* b;
    long max = 0;

    //is the image 16bits grayscale ?
    assert(img->channels() == 1);
    assert(CV_16U == img->type());

    //histogram calculation
    ---->for(i=x1 ; i<=x2  ; i++)
    {
        ---->for(j=y1 ; j<=y2 ; j++)
        {
            hist[img->at<unsigned short>(j,i)]++;
            if(hist[img->at<unsigned short>(j,i)] > max)
                max = hist[img->at<unsigned short>(j,i)];
        }
    }

    //Cumulative histogram calculation (if cumul=true)
    if(cumul)
    {
        for(b=hist ; b<hist+65535 ; b++)
        {
            *(b+1) += *b;
        }
    }
    return (cumul ? hist[65535] : max);
}

But it does not work as expected, my image is not equalized, I do not have extrem values (clear white and dark black) on my image. If I try

equalizeHist_16U(&img, 0, 50, 0, 50)

The image i get is very very bright And if I try

equalizeHist(&img, 300, 319, 220, 239)

The image I get is very very dark

I think I have made a mistake in the loops bounds but I can't find where ! Maybe you have an idea ?

Thank you in advance

2

2 Answers

1
votes

Preliminary:
Did you notice you are not using at all your second version of the cumulative histogram function?

void equalizeHist_16U(Mat* img, int x1, int x2, int y1, int y2)

is calling

compute_hist_16U(img, hist, true);

and not:

long compute_hist_16U (Mat* img, long* hist, bool cumul, int x1, int x2, int y1, int y2)

(I suppose you wanted to post the last one, otherwise I wouldn't see why you posted the code :) )


Actual answer:

Everything can become much easier if you use cv::Mat rois, through the operator ().

Your function would become as following:

void equalizeHist_16U(Mat* img, int x1, int x2, int y1, int y2) {

   //Here you should check you have x2 > x1 and y2 > y1 and y1,x1 >0 and x2 <= img->width and y2 <= img->height

   cv::Rect roi(x1,y1,x2-x1,y2-y1); 
   //To reproduce exactly the behaviour you seem to target,
   //it should be x2-x2+1 and y2-y1+1. 
   //But you should get used on the fact that extremes are,
   //as a convention, excluded

   cv::Mat& temp = *img; //Otherwise using operator() is complicated
   cv::Mat roiMat = temp(roi); //This doesn't do any memory copy, just creates a new header!
   void equalizeHist_16U(&roiMat); //Your original function!!

}

And that's it! If this doesn't work, then it means your original function that process the whole image has a bug that you couldn't notice before.

When I'll have a bit of time I'll post a couple of suggestions to make your function faster (e.g., you should avoid using .at, you should compute the max value in your histogram at the end of the histogram computation, you should create a look up table of short where you multiply your histogram by ratio, so that applying the histogram becomes much faster; and instead of the ratio variable which causes floating point conversion, you could simply divide your histogram elements by the constant (img->width*img->height)) and more neat (you should pass Mat by reference, not using a pointer, that's C-style, not C++)

Furthermore:

  • Why do you return a value from compute_hist_16U?
  • long hist[65535] should long hist[65536], so that index 65535 is valid. First of all, 65535 is the white value in your image. Furthermore, you use it in your cycle, when you have b+1 with b=hist+65534 (the last cycle)

    for(b=hist ; b<hist+65535 ; b++)
    {
        *(b+1) += *b;
    }
    
0
votes

thanks for your answer.

Preliminary I think I have made a mistake when pasting my code, I forgot to change the line you're noticed, this line should be the last one you wrote.

Actual answer Your technique works very well, I do have extrem values for my pixels (clear white and dark black), regardless of the selected area. The only problem (I did not mention it in my question so you couldn't have known) is that it only keeps the selected area, the rest of the image is unchanged. In fact I want to do the histogram calculation on a specified part of the image, and apply that histogram to all my image.

I also removed the returned value from compute_hist_16U and changed long hist[65535] to long hist[65536] I changed the way I pass the image and deleted the variable ratio. I used .at because it is the way to access pixel values that is described in the documentation and on stackoverflow when I searched "how to acces pixel value opencv mat" And I have never seen how to create a lookup table so I may look into this when my program will be totally functionnal

My new functions are :

void compute_hist_16U (Mat &img, long* hist)
{
    unsigned short i, j;
    long* b;

    assert(img.channels() == 1);
    assert(CV_16U == img.type());

    for(i=0 ; i<=img.cols-1  ; i++)
    {
        for(j=0 ; j<=img.rows-1 ; j++)
        {
            hist[img.at<unsigned short>(j,i)]++;
        }
    }

    //Calcul de l'histogramme cumulé
    for(b=hist ; b<hist+65535 ; b++)
    {
        *(b+1) += *b;
    }
}

void equalizeHist_16U(Mat &img, int x1, int x2, int y1, int y2)
{
    long hist[65536] = {0};
    double ratio;
    int i,j;

    assert(img.channels() == 1);
    assert(img.type() == CV_16U);
    assert(x1>=0 && y1>=0 && x2>x1 && y2>y1);
    assert(y2<img.rows && x2<img.cols);

   cv::Rect roi(x1,y1,x2-x1+1,y2-y1+1);
   cv::Mat& temp = img;
   cv::Mat roiMat = temp(roi);

   compute_hist_16U(roiMat, hist);

   for(i=0 ; i<=img.cols-1 ; i++)
   {
       for(j=0 ; j<=img.rows-1 ; j++)
       {
           img.at<unsigned short>(j,i) = 65536.0*hist[img.at<unsigned short>(j,i)] / (roiMat.cols*roiMat.rows);
       }
   }
}

void equalizeHist_16U(Mat &img)
{
    equalizeHist_16U(img, 0, img.cols-1, 0, img.rows-1);
}

I think it works, if I select a bright part of my image, I have extrem values on this part (bright white and dark black) and the rest of the image is pretty dark. For example, if I select the building on the right : http://www.noelshack.com/2015-20-1431349774-result-ok.png

But the result is weird sometimes, for example if I select the blackest clouds : http://www.noelshack.com/2015-20-1431349774-result-nok.png