使用直方图均衡进行自动对比度和饱和度

1,190 阅读2分钟

概述

本篇博客主要记录如何使用OpenCV的直方图均衡改善图片的对比度和饱和度。下图的右边是使用了自动对比度和饱和度的结果 image.png

代码示例

例子的地址

例子为CMake项目,目前只在MacOS上运行。运行项目中的Target OpenCVAutoHist,可查看效果

什么是直方图

直方图可以理解为对于一组数据的统计,比如一张灰度图,像素值从0 到 255,直方图统计的就是值为0的像素有多少,值为1的像素有多少 ... 值为255的像素有多少。对示例中的图片的V和S通道计算直方图,可以看出来,分布基本都是在一侧,所以图片整体呈现很灰白。

image.png

直方图可视化的代码如下,接受单个通道的数据

void drawHist(const char *name, cv::Mat channel) {
  int histSize = 256;
  float range[] = {0, 256};
  const float *histRange = {range};

  cv::Mat vHist;
  cv::calcHist(&channel, 1, 0, cv::Mat(), vHist, 1, &histSize, &histRange, true, false);
  cv::normalize(vHist, vHist, 0, 1, cv::NORM_MINMAX, -1, cv::Mat());

  int histImgW = 512;
  int histImgH = 200;
  int wPerHistBin = histImgW / histSize;
  cv::Mat histImage(histImgH, histImgW, CV_8UC3, cv::Scalar(255,255,255));
  
  for (int i = 0; i < histSize; ++i) {
    float val = vHist.at<float>(i);
    float x = i * wPerHistBin;
    float y = histImgH - val * histImgH;
    cv::rectangle(histImage, cv::Rect(x, y, wPerHistBin, val * histImgH), cv::Scalar(0, 0, 255));
  }

  cv::imshow(name, histImage);
}

直方图均衡

直方图均衡就是经过处理后,图像的直方图呈现均匀分布非状态,OpenCV的API很简单

cv::equalizeHist(sChannel, sChannel);

经过了均衡的s和v通道直方图分布如下:

image.png

自动对比度和自动饱和度

整体思路如下

  • 分离图像的v和s通道数据
  • 对v和s通道数据进行直方图均衡
  • 合并通道数据
  • 使用addWeighted将均衡后的图与原图融合

分离图像的v和s通道数据

使用split方法即可

cv::cvtColor(originImage, hsvImage, cv::COLOR_BGR2HSV);
cv::split(hsvImage, hsvChannels);

对v和s通道数据进行直方图均衡

使用equalizeHist方法

cv::equalizeHist(vChannel, vChannel);
cv::equalizeHist(sChannel, sChannel);

合并通道数据

使用merge方法合并数据,并转回BGR格式

cv::Mat newImg;
cv::merge(newChannels, newImg);
cv::cvtColor(newImg, newImg, cv::COLOR_HSV2BGR);

使用addWeighted将均衡后的图与原图融合

这一步主要是为了避免均衡后的对比度过于强烈,通过权重可以控制均衡的影响

cv::Mat finalImg;
cv::addWeighted(newImg, weight, _originImg, 1.0 - weight, 0, finalImg);