OpenCV之直方图拉伸

来源:互联网 发布:分形设计软件 编辑:程序博客网 时间:2024/05/18 06:23
  1. 本文代码使用OpenCV版本:2.4.13
  2. 本文代码在Win10+Visual Studio 2013 Update 3下测试通过

上一个博客《OpenCV之图像直方图计算》讲述了图像直方图的计算。计算出图像的直方图后,我们不仅可以据此观察图像的像素分布情况,还可以利用它对图像进行增强,例如直方图拉伸(Histogram Stretching)。

比如,我们有一个灰度图像的直方图如下所示:

某灰度图像的直方图

可以看到,该直方图的左右两侧都有一部分区域,其高度近乎于0。这意味着,在该图像中,对应灰度级的像素出现的次数近乎于0。换句话说,图像所使用的灰度级集中在中部区域,从而使得图像具有较低的对比度。

如果我们忽略掉左右两侧高度近乎于0的区域,然后将直方图向左右两侧拉伸,则就可以使用到所有的灰度级,从而提高图像的对比度,这就是直方图拉伸的理念。拉伸后的直方图如下所示:

拉伸后的直方图

直方图拉伸的数学公式

假设决定忽略左右两侧高度小于等于minValue的区域,那么首先应该从最左侧向右寻找第一个高度大于minValue的灰度值,设为grayMin;然后从最右侧向左寻找第一个高度大于minValue的灰度值,设置为grayMax,从而得到将要进行拉伸的区域[grayMin, grayMax]。而要拉伸的目标是[0, 255]

所以对于原区域[grayMin, grayMax]中的任意灰度g,其拉伸后的结果应为:

g=(ggrayMin)255(grayMaxgrayMin)

另外,不要忘了被忽略的灰度级。对于左侧被忽略的灰度级,其对应的新的灰度应该为0;对于右侧被忽略的灰度级,其对应的新的灰度应该为255。

总结如下:

g=0,(ggrayMin)255(grayMaxgrayMin),255,if g<grayMinif grayMinggrayMaxif g>grayMax

下列代码展示了此计算过程:

////////////////////////////////////////////// 灰度直方图拉伸示例#include <iostream>#include "opencv2/core/core.hpp"#include "opencv2/highgui/highgui.hpp"#include "opencv2/imgproc/imgproc.hpp"// 使用Rect绘制直方图void drawHist_Rect(const cv::Mat& hist, cv::Mat& canvas, const cv::Scalar& color){    CV_Assert(!hist.empty() && hist.cols == 1);    CV_Assert(hist.depth() == CV_32F && hist.channels() == 1);    CV_Assert(!canvas.empty() && canvas.cols >= hist.rows);    const int width = canvas.cols;    const int height = canvas.rows;    // 获取最大值    double dMax = 0.0;    cv::minMaxLoc(hist, nullptr, &dMax);    // 计算直线的宽度    float thickness = float(width) / float(hist.rows);    // 绘制直方图    for (int i = 1; i < hist.rows; ++i)    {        double h = hist.at<float>(i, 0) / dMax * 0.9 * height; // 最高显示为画布的90%        cv::rectangle(canvas,            cv::Point(static_cast<int>((i - 1) * thickness), height),            cv::Point(static_cast<int>(i * thickness), static_cast<int>(height - h)),            color,            static_cast<int>(thickness));    }}// 直方图拉伸// grayImage - 要拉伸的单通道灰度图像// hist - grayImage的直方图// minValue - 忽略像数个数小于此值的灰度级void histStretch(cv::Mat& grayImage, const cv::Mat& hist, int minValue){    CV_Assert(!grayImage.empty() && grayImage.channels() == 1 && grayImage.depth() == CV_8U);    CV_Assert(!hist.empty() && hist.rows == 256 && hist.cols == 1 && hist.depth() == CV_32F);    CV_Assert(minValue >= 0);    // 求左边界    uchar grayMin = 0;    for (int i = 0; i < hist.rows; ++i)    {        if (hist.at<float>(i, 0) > minValue)        {            grayMin = static_cast<uchar>(i);            break;        }    }    // 求右边界    uchar grayMax = 0;    for (int i = hist.rows - 1; i >= 0; --i)    {        if (hist.at<float>(i, 0) > minValue)        {            grayMax = static_cast<uchar>(i);            break;        }    }    if (grayMin >= grayMax)    {        return;    }    const int w = grayImage.cols;    const int h = grayImage.rows;    for (int y = 0; y < h; ++y)    {        uchar* imageData = grayImage.ptr<uchar>(y);        for (int x = 0; x < w; ++x)        {            if (imageData[x] < grayMin)            {                imageData[x] = 0;            }            else if (imageData[x] > grayMax)            {                imageData[x] = 255;            }            else            {                imageData[x] = static_cast<uchar>(std::round((imageData[x] - grayMin) * 255.0 / (grayMax - grayMin)));            }        }    }}int main(){    // 读入图像,此时是3通道的RGB图像    cv::Mat image = cv::imread("G:/dataset/lena512.bmp");    if (image.empty())    {        return -1;    }    // 转换为单通道的灰度图    cv::Mat grayImage;    cv::cvtColor(image, grayImage, cv::COLOR_BGR2GRAY);    // 计算直方图并绘制    cv::Mat hist;    cv::Mat histCanvas(400, 512, CV_8UC3, cv::Scalar(255, 255, 255));    int channels[1] = { 0 };    int histSize = 256;    float range[2] = { 0, 256 };    const float* ranges[1] = { range };    cv::calcHist(&grayImage, 1, channels, cv::Mat(), hist, 1, &histSize, ranges);    drawHist_Rect(hist, histCanvas, cv::Scalar(255, 0, 0));    // 显示原始灰度图像及其直方图    cv::imshow("Gray image", grayImage);    cv::imshow("Gray image's histogram", histCanvas);    // 直方图拉伸    cv::Mat grayImageStretched = grayImage.clone();    histStretch(grayImageStretched, hist, 20);    // 计算直方图并绘制    cv::Mat histStretched;    cv::Mat histCanvasStretched(400, 512, CV_8UC3, cv::Scalar(255, 255, 255));    cv::calcHist(&grayImageStretched, 1, channels, cv::Mat(), histStretched, 1, &histSize, ranges);    drawHist_Rect(histStretched, histCanvasStretched, cv::Scalar(255, 0, 0));    // 显示拉伸后的灰度图像及其直方图    cv::imshow("Stretched image", grayImageStretched);    cv::imshow("Stretched image's histogram", histCanvasStretched);    cv::waitKey();    return 0;}

原始图像及拉伸后图像的直方图,上面已经展示过了。下面是原始图像及经过拉伸后的图像,可以看到,对比度有了明显的提升(头发更黑了):

原始图像拉伸后的图像

彩色直方图拉伸

彩色图像也可以进行直方图拉伸,方法是分别对R通道、G通道和B通道进行直方图拉伸。此处不再赘述,仅展示一下原图及拉伸效果(minValue设置为100;同样头发变得更黑了):

原始彩色图像拉伸后的彩色图像

参考链接

  1. 《OpenCV 2 视觉编程手册》4.3节
  2. 图像处理(一)全等级直方图灰度拉伸

(转载请保留作者信息)

原创粉丝点击