选择性直方图均衡化(仅在图像的指定区域)

Selective histogram equalization (only on a specified area of the image)

我正在使用 opencv 在 Qt creator 上进行开发。

我必须开发一个程序来对图像进行直方图均衡。 我的图像是 16 位灰度图像,所以我不能使用 opencv 函数 "equalizeHist" 因为它只适用于 8 位灰度图像。

我为此编写的代码如下:

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

它达到了我的预期,现在我想对我的图像进行直方图均衡化,但只对图像的指定部分进行均衡化。 我将 x1,x2,y1,y2 参数添加到我的函数中,并像这样更改了 "for" 的边界(我更改的代码行带有箭头):

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

但它没有按预期工作,我的图像没有均衡,我的图像上没有极端值(清晰的白色和深黑色)。 如果我尝试

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

我得到的图像非常非常明亮 如果我尝试

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

我得到的图像非常非常暗

我想我在循环边界上犯了一个错误,但我找不到哪里! 也许你有想法?

提前致谢

初步:
您是否注意到您根本没有使用累积直方图函数的第二个版本?

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

正在通话

compute_hist_16U(img, hist, true);

而不是:

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

(我想你想 post 最后一个,否则我不明白你为什么 post 编辑了代码:))


实际答案:

如果您使用 cv::Mat rois,通过 operator ().

,一切都会变得容易得多

你的函数将变成如下:

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!!

}

就是这样!如果这不起作用,则意味着您处理整个图像的原始函数存在您之前没有注意到的错误。

当我有时间的时候,我会 post 一些建议来让你的函数更快(例如,你应该避免使用 .at,你应该计算最大值在直方图计算结束时的直方图中,您应该创建 short 的 table 查找,将直方图乘以比率,以便应用直方图变得更快;而不是 ratio 导致浮点转换的变量,你可以简单地将你的直方图元素除以常量 (img->width*img->height))并且更整洁(你应该通过引用传递 Mat,而不是使用指针,那是 C-风格,不是 C++)

此外:

  • 为什么 return 来自 compute_hist_16U 的值?
  • long hist[65535] 应该 long hist[65536],这样索引 65535 才有效。首先,65535 是图像中的白色值。此外,当你有 b+1b=hist+65534 (最后一个周期)

    时,你会在你的周期中使用它
    for(b=hist ; b<hist+65535 ; b++)
    {
        *(b+1) += *b;
    }
    

感谢您的回答。

初步 我想我在粘贴我的代码时犯了一个错误,我忘记更改你注意到的那一行,这一行应该是你写的最后一行。

实际回答 您的技术非常有效,无论 selected 区域如何,我的像素(清晰的白色和深黑色)都有极值。 唯一的问题(我没有在我的问题中提到它,所以你不可能知道)是它只保留 selected 区域,图像的其余部分没有变化。 事实上,我想对图像的指定部分进行直方图计算,并将该直方图应用于我的所有图像。

我也把compute_hist_16U的返回值去掉,把long hist[65535]改成了long hist[65536] 我改变了传递图像的方式并删除了变量ratio。 我使用 .at 是因为它是访问文档中描述的像素值的方式,以及当我搜索 "how to acces pixel value opencv mat" 时在 Whosebug 上描述的方式 而且我从未见过如何创建查找 table 所以当我的程序完全正常运行时我可能会研究这个

我的新功能是:

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

我认为它有效,如果我 select 我的图像的一个明亮部分,我在这部分有极值(亮白色和深黑色)并且图像的其余部分非常暗。例如,如果我select右边的建筑物:http://www.noelshack.com/2015-20-1431349774-result-ok.png

但有时结果很奇怪,例如如果我 select 最黑的云 : http://www.noelshack.com/2015-20-1431349774-result-nok.png