OpenCV2学习笔记(二)
最后更新于:2022-04-01 06:35:49
##图像的直方图
>直方图(Histogram)又称质量分布图。是一种统计报告图,由一系列高度不等的纵向条纹或线段表示数据分布的情况。一般用横轴表示数据类型,纵轴表示分布情况。众所周知,一幅图像是由不同颜色值的像素组成,因此像素值在图像中的分布情况是这幅图像的一个重要特征,因此直方图广泛应用在数字图像处理中。
拍照是现实生活中必不可少的一部分,由于环境亮度、图像拍摄过程中透视光圈设置错误等影响,经常会拍出一些“过暗”的照片,此时美图、PS等美化工具可以派上用场。但是这些工具的算法通常都是不公开的,鉴于研究所需,这里利用图像直方图的一些性质来探索一些图像增强方法。开发平台为OpenCV2.4.9+Qt5.3.2。
一、灰度图像的直方图
在一幅单通道的灰度图像中,每个像素的取值区间为[0,255],因此灰度图像的直方图包含256个容器,各个容器给出当前值的像素个数。简单来说,直方图统计出每种像素取值的个数,它可以被归一化,即算出图像的像素总数,然后将各容器的像素个数除去总数,归一化后所有项的和为1。
在OpenCV中,使用cv::calcHist函数以计算直方图,这里创建一个Qt控制台应用并定义一个类(注意直方图中的数据类型为float):
histgram1d.h:
~~~
#ifndef HISTOGRAM1D_H
#define HISTOGRAM1D_H
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <cv.hpp>
class Histogram1D
{
public:
Histogram1D()
{
// 准备直方图的相关参数
histSize[0] = 256;
hranges[0] = 0.0; // 灰度值范围为0到255,float类型
hranges[1] = 255.0;
ranges[0] = hranges;
channels[0] = 0;
}
cv::MatND getHistogram(const cv::Mat &image); // 计算灰度图像的直方图
cv::Mat getHistogramImage(const cv::Mat &image); // 计算灰度直方图并返回图像
private:
int histSize[1]; // 容器的数量
float hranges[2]; // 像素的最小值和最大值
const float *ranges[1];
int channels[1]; // 仅使用一个通道
};
~~~
histgram1d.cpp:
~~~
#include "histogram1d.h"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <cv.hpp>
#include <QDebug>
#include <opencv2/imgproc/imgproc.hpp>
cv::MatND Histogram1D::getHistogram(const cv::Mat &image)
{
cv::MatND hist;
cv::calcHist(&image, // 输入图像
1, // 计算1张图片的直方图
channels, // 图像的通道数
cv::Mat(), // 不实用图像作为掩码
hist, // 返回的直方图
1, // 1D的直方图
histSize, // 容器的数量
ranges); // 像素值的范围
return hist;
}
cv::Mat Histogram1D::getHistogramImage(const cv::Mat &image)
{
// 先计算直方图
cv::MatND hist = getHistogram(image);
// 获取直方图中的最大值和最小值
double minVal = 0;
double maxVal = 0;
cv::minMaxLoc(hist, &minVal, &maxVal, 0, 0);
// 以下步骤实现显示直方图的图像
cv::Mat hist_img(histSize[0], histSize[0], CV_8U, cv::Scalar(255));
// 设置图像的最高点
int highpoint = static_cast<int>(0.9*histSize[0]);
// 对每个条目都绘制一条垂直线
for(int h = 0; h < histSize[0]; h++)
{
float binVal = hist.at<float>(h);
int intensity = static_cast<int>(binVal*highpoint/maxVal);
// 两点之间绘制一条线
cv::line(hist_img, cv::Point(h, histSize[0]),
cv::Point(h, histSize[0]-intensity),
cv::Scalar::all(0));
}
return hist_img;
}
~~~
注意直方图数据为浮点类型!接着修改main函数:
~~~
#include "histogram1d.h"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <cv.hpp>
#include <QCoreApplication>
#include <opencv2/imgproc/imgproc.hpp>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 读取图片
cv::Mat image = cvLoadImage("c:/Fig6.35(5).jpg", -1); // 允许单通道和三通道的图像读取
// 创建histogram对象
Histogram1D h;
cv::MatND histo = h.getHistogram(imt);
for(int i=0;i<256;i++)
{ // 输出各像素值的个数
std::cout << "Value" << i << "=" << histo.at<float>(i) << std::endl;
}
cv::imshow("Image",imt);
cv::imshow("Histgram", h.getHistogramImage(imt));
return a.exec();
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-30_5683a748251a2.jpg)
这是一幅亮度不足的图片,这里提供三种提高对比度的方法增强图像的方法:
一、使用查找表增强图像
首先在histgram1d.h中添加:
~~~
public:
cv::Mat stretch(const cv::Mat &image, int); // 基于查找表的图像拉伸,提高图像对比度
~~~
然后在histgram1d.cpp中编写该函数如下:
~~~
cv::Mat Histogram1D::stretch(const cv::Mat &image, int minValue = 0)
{
// 先计算直方图
cv::MatND hist = getHistogram(image);
// 寻找直方图的左方
int imin = 0;
for(;imin < 256; imin++)
{
if(hist.at<float>(imin) > minValue)
break;
}
// 寻找直方图的右方
int imax = 255;
for(;imax >= 0 ;imax--)
{
if(hist.at<float>(imax) > minValue)
break;
}
std::cout<< imin << ";" << imax << std::endl;
// 创建查找表
cv::Mat lookup(1, 256, CV_8U);
// 以下循环生成查找表
for(int i=0; i<256; i++)
{
// 确保数值位于imin与imax之间
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));
}
// 应用查找表,输出图像result
cv::Mat result;
cv::LUT(image, lookup, result);
return result;
}
~~~
接着修改main函数:
~~~
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 直方图均衡
cv::Mat image = cvLoadImage("c:/Fig2.19(a).jpg", -1); // 允许单通道和三通道的图像读取
if(image.channels()==1)
{
// 创建histogram对象
Histogram1D h;
cv::Mat imt ;
imt = h.stretch(image, 4500); // 对图像进行拉伸,4500定义了一个阈值,即在像素点个数达到4500时,默认该点为图像的灰度最大值
cv::MatND histo = h.getHistogram(imt);
for(int i=0;i<256;i++)
{ // 打印出拉伸后的各像素值个数
std::cout << "Value" << i << "=" << histo.at<float>(i) << std::endl;
}
cv::imshow("Image", image);
cv::imshow("Stretched Image", imt);
cv::imshow("Histgram", h.getHistogramImage(image));
cv::imshow("Histgram1", h.getHistogramImage(imt));
else qDebug() << "error!" ;
return a.exec();
~~~
输出效果图如下,该方法的优点是算法复杂度低,设计简单,同时缺点也比较明显,那就是stretch函数的第二个参数选取需要根据不同图像进行调整,若参数选取不当,将造成图像细节的丢失,以下给出两种不同参数的输出结果,效果差别还是挺大的:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-30_5683a7484090c.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-30_5683a748627f0.jpg)
二、使用直方图均衡方法增强图像
通过拉伸直方图的方法可以简单有效地提升图像质量,但是在多数情况下,一些图像的缺陷并不是因为使用过窄的强度范围,而是由于某些颜色值出现的概率远高于其他值。因此直方图均衡成为一种常用的手段,它的思想是试图使一幅图像平均地使用所有像素的强度值,后面还给出一种改进的直方图均衡方法。
普通的直方图均衡方法在OpenCV中十分常用,原则上直方图均衡方法仅适用于灰度图像,但事实上使用直方图处理彩色图像同样可行,只是可能需要一些额外的处理。下面给出直方图均衡算法调用的基本步骤:
1、在histgram1d.h中public: 下添加:cv::Mat equalize(const cv::Mat &image); //使用OpenCV自带的直方图均衡算法
2、在histgram1d.cpp中添加:
~~~
cv::Mat equalize(const cv::Mat &image)
{
cv::Mat result;
cv::equalizeHist(image, result);
return result;
}
~~~
最后简单修改main函数即可,测试下效果~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-30_5683a7488624d.jpg)
结果表明,图像经直方图均衡后,对比度有所提升,但其中其实隐含了一些问题,继续测试:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-30_5683a748b96a7.jpg)
问题很明显,直方图均衡化带来了对比度的提升,但由于该算法使用了像素值的映射关系,造成了灰度级的减少,这对于很多图像来说意味着细节的丢失。因此寻找一种改进的直方图均衡化算法很有必要。
三、图像增强之限制对比度自适应直方图均衡
这里介绍一种有用的、同时OpenCV已有的新算法,即限制对比度自适应直方图均衡(Contrast Limited Adaptive histgram equalization/CLAHE)。这个博客 [http://www.cnblogs.com/Imageshop/archive/2013/04/07/3006334.html](http://www.cnblogs.com/Imageshop/archive/2013/04/07/3006334.html) 给出了该算法的详细解释,我也是从中深受启发,下面是关于CLAHE的简单解释:
~~~
CLAHE同普通的直方图均衡不同的地方主要是其对比度限幅。在CLAHE中,对将要进行处理的图像进行区域划分,并对每个小区域使用对比度限幅。CLAHE主要是用来克服传统自适应直方图均衡化算法的过度放大噪音和丢失细节的问题。
这主要是通过限制自适应直方图均衡化算法的对比提高程度来达到的。在指定的像素值周边的对比度放大主要是由变换函数的斜度决定的。这个斜度和领域的累积直方图的斜度成比例。CLAHE通过在计算累积直方图函数前用预先定义的阈值来裁剪直方图以达到限制放大幅度的目的。这限制了累积直方图函数的斜度因此,也限制了变换函数的斜度。直方图被裁剪的值,也就是所谓的裁剪限幅,取决于直方图的分布因此也取决于领域大小的取值。
通常,直接忽略掉那些超出直方图裁剪限幅的部分是不好的,而应该将这些裁剪掉的部分均匀的分布到直方图的其他部分。如下图所示。
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-30_5683a748e3d85.jpg)
事实上,CLAHE算法能够从整体上改善输入图像的灰度分布范围, 提供更佳的视觉效果,以下给出一个例子,在这里加上了对彩色图像的直方图均衡化处理。
直接修改main函数:
~~~
#include "histogram1d.h"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <cv.hpp>
#include <QCoreApplication>
#include <opencv2/imgproc/imgproc.hpp>
#include <QDebug>
#include <iostream>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// CLAHE直方图均衡
cv::Mat image = cvLoadImage("c:/Fig6.35(5).jpg", -1); // 允许单通道和三通道的图像读取
if(image.channels()==1) // 若为灰度图像,直接进行CLAHE处理
{
// 创建histogram对象
Histogram1D h;
cv::Mat imt;
cv::MatND histo = h.getHistogram(imt);
for(int i=0;i<256;i++)
{
std::cout << "Value" << i << "=" << histo.at<float>(i) << std::endl;
}
// 进行CLAHE算法
cv::Mat imt1;
cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE();
clahe->setClipLimit(3);
clahe->setTilesGridSize(cv::Size(8,8));
clahe -> apply(image, imt1);
cv::imshow("Input Histogram", h.getHistogramImage(image));
cv::imshow("Input Image",image);
cv::imshow("Output Histogram", h.getHistogramImage(imt1));
cv::imshow("CLAHE Output", imt1);
}
else if(image.channels()==3) // 若输入为彩色图片
{
// 彩色直方图均衡三步骤:RGB转换为YUV,对亮度通道
// 进行处理,之后YUV转换为RGB
std::vector<cv::Mat> out;
cv::Mat kk;
cv::cvtColor(image, kk, CV_RGB2YUV);
cv::split(kk, out);
// 彩色直方图均衡过程
cv::Mat colorimt;
cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE();
clahe->setClipLimit(3);
clahe->setTilesGridSize(cv::Size(8,8));
// 对YUV中亮度通道进行直方图均衡
kk = out[0];
// colorimt为CLAHE处理后的亮度通道
clahe -> apply(kk, colorimt);
out.at(0) = colorimt;
cv::Mat cc;
cv::merge(out, cc); // 将Y,U,V三个通道合并
// cv::imshow("YUV",cc); //YUV图
// 需要把YUV格式转化回RGB格式
cv::cvtColor(cc, colorimt, CV_YUV2RGB);
cv::imshow("Input",image); // 原图
cv::imshow("Output",colorimt); // 直方图均衡后
}
else qDebug() << "error!" ;
return a.exec();
}
~~~
结果大大改善~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-30_5683a748f1e5d.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-30_5683a7492ea4d.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-30_5683a749668ee.jpg)
对于彩色图像,有几种较为主流的直方图均衡方法:基于RGB颜色通道的独立均衡方法、基于RGB三通道联合概率的均衡方法、基于HSI空间的I分量均衡法和基于YUV空间的Y分量均衡法。这篇论文:[http://www.docin.com/p-350697910.html](http://www.docin.com/p-350697910.html) 介绍了各方法及其优缺点。这里使用的是基于YUV空间的Y分量均衡法,基本步骤是将输入的RGB图像转换到YUV空间,取其亮度通道进行直方图均衡,再转换回RGB图像即可。该方法运行效率较高,处理后的图像效果也更佳!
欢迎转载或分享,但请务必声明文章出处~