图像由不同颜色值的像素组成,像素值在图像中的分布情况是图像的一个重要特征。
一、计算图像的直方图
直方图是一个简单的表,它给出了图像中各个像素值的数目(归一化后则为相应的比例),在OpenCV中可以使用cv::calcHist计算图像的直方图,这是一个通用函数,可以计算一张或一组任意像素类型的多通道图像。
void calcHist(const Mat* arrays, int narrays, const int* channels, InputArray mask, OutputArray
hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate=
false );
参数说明:
- arrays: 要计算的图像或一组图像
- narrays: 图像数量
- channels: 要计算的通道
- mask: 掩码(掩码0对应的像素会被忽略)
- hist: 计算出的直方图
- dims: 直方图的维数
- histSize: 在每一维上直方图的个数。简单把直方图看作一个个的竖条的话,就是每一维上竖条的个数
- ranges: 每一维的范围
- uniform: 是否归一化
- accumulate: 是否累加(用于计算多张图时)
示例:计算一张灰度图像的直方图
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>
using namespace cv;
const int histSize[1] = {256};//彩色图histSize[3] = {256,256,256};
MatND getHistogram(const Mat &image);
Mat getHistImage(const Mat &image);
int main()
{
Mat image = imread("1.jpg",CV_LOAD_IMAGE_GRAYSCALE);
namedWindow("image");
imshow("image",image);
Mat histImg = getHistImage(image);
namedWindow("image1");
imshow("image1",histImg);
waitKey(0);
}
MatND getHistogram(const Mat &image)
{
float hranges[2] = {0.0,255.0};
//彩色图ranges[3]={hranges,hranges,hranges};
const float *ranges[1] = {hranges};
//彩色图channels[3] = {0,1,2};
int channels[1] = {0};
MatND hist;
calcHist(&image,1,channels,Mat(),hist,1,histSize,ranges);
return hist;
}
Mat getHistImage(const Mat &image)
{
const
MatND hist = getHistogram(image);
double minVal = 0;
double maxVal = 0;
//获取最大值和最小值
minMaxLoc(hist,&minVal,&maxVal,0,0);
Mat histImg(histSize[0],histSize[0],CV_8U,Scalar(255));
//设置最高点大小为90%
int hpt = static_cast<int>(0.9 * histSize[0]);
for(int i = 0;i<histSize[0];i++)
{
float binVal = hist.at<float>(i);
int intensity =static_cast<int>(binVal * hpt / maxVal);
//两点之间画一条线
line(histImg,Point(i,histSize[0]),Point(i,histSize[0]-intensity),Scalar::all(0));
}
return histImg;
}
二、使用查找表修改图像
查找表是一个一对一(或多对一)的函数,定义了如何将像素值转换为新的值,它本质上是一个数组,对于灰度图像有256个条目。即:
newIntensity = lookup[oldIntensity];
OpenCV的cv::LUT对图像应用查找表以生成新图像。
两个例子:
1、创建图像的反向查找表,生成图像的负片。
Mat applyLookUp(const Mat &image,const Mat &lookup)
{
Mat result;
LUT(image,lookup,result);
return result;
}
int dim(256);
Mat lut(1, &dim, CV_8U);
for(int i = 0; i < 256; i++)
{
lut.at<uchar>(i) = 255 - i;
}
Mat result = applyLookUp(image, lut);
2、提高图像对比度
通常,忽略不含任何数值或者微不足道(小于某个给个值)的条目对提升图像对比度有很大的好处。即通过拉伸直方图,去掉一些像素然后再重新映射剩余像素的强度值。重新映射方式为:
255.0 * (i - imin) / (imax - imin) + 0.5;
//imin,imax为直方图中非零项的最低和最高强度值
Mat stretch(const Mat &image,int minValue)
{
MatND hist = getHistogram(image);
//寻找直方图的左右端
int imin = 0;
for(;imin<histSize[0];imin++)
{
std::cout<<hist.at<float>(imin)<<std::endl;
if(hist.at<float>(imin) > minValue)
break;
}
int imax = histSize[0] - 1;
for(;imax >= 0;imax--)
{
if(hist.at<float>(imax) > minValue)
break;
}
//创建查找表
int dim(256);
Mat lookup(1,&dim,CV_8U);
for(int i = 0;i<histSize[0];i++)
{
if(i < imin)
lookup.at<uchar>(i) = 0;
else if(i > imax)
lookup.at<uchar>(i) = 255;
else
lookup.at<uchar>(i) = static_cast<uchar>(255.0 * (i - imin) / (imax - imin) + 0.5);
}
Mat result;
result = applyLookUp(image,lookup);
return result;
}
三、直方图均衡化
想像一下如果一副图像中的大多数像素点都集中在一个像素值范围内会怎么样?例如,如果一张图片整体很亮,那大多数的像素值应该都会很高。但是一张高质量的图像的像素值分布应该很广泛,所以应该把它的直方图做一个横向拉伸,这就是直方图均衡化要做的事情,通常情况下这也会改善图像的对比度。
OpenCV已经为我们提供了一个函数用于直方图均衡化:
Mat result;
cv::equalizeHist(image,result);
一般而言,直方图均衡化能大幅改善图像。然而,最终结果的质量往往依赖于图像本身,有时这样做的效果并不好。例如,下面是原图和上面方法进行直方图均衡化后的输出图像:![这里写图片描述](https://img-blog.csdn.net/20170321200459476)
可以看到图像的对比度的确改变了,但是对比两幅图中的雕像部分发现轮廓变得不清晰了,也就是丢失了很多信息。造成这种结果的根本原因在于这幅图像的直方图并不是很集中在某一个区域内。
为了解决这个问题,我们可以使用自适应的直方图均衡化。这种方式下,图像会被会成很多小块(这些小块称为tiles,OpenCV中tiles的大小默认是8x8),然后再对每个小块分别进行直方图均衡化。这样在每一个区域中,直方图会集中在某一个小的区域(除非有噪声干扰)。如果有噪声的话,噪声会被放大,为了避免这种情况,可以使用对比度限制:对于每个小块,如果直方图中的bin超过对比度上限的话,就把其中的像素均匀分散到其他的bin中,然后再进行直方图均衡化。最后,使用双线性差值,对小块进行缝合,去除每个小块之间的人造边界。
Ptr<CLAHE> clahe = createCLAHE();
// 直方图的柱子高度大于计算后的ClipLimit的部分被裁剪掉,然后将其平均分配给整张直方图,从而提升整个图像
clahe->setClipLimit(4.); // (int)(4.*(8*8)/256)
Mat dst;
clahe ->apply(src,dst);