##圖像的直方圖
>直方圖(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();
~~~

這是一幅亮度不足的圖片,這里提供三種提高對比度的方法增強圖像的方法:
一、使用查找表增強圖像
首先在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函數的第二個參數選取需要根據不同圖像進行調整,若參數選取不當,將造成圖像細節的丟失,以下給出兩種不同參數的輸出結果,效果差別還是挺大的:?


二、使用直方圖均衡方法增強圖像
通過拉伸直方圖的方法可以簡單有效地提升圖像質量,但是在多數情況下,一些圖像的缺陷并不是因為使用過窄的強度范圍,而是由于某些顏色值出現的概率遠高于其他值。因此直方圖均衡成為一種常用的手段,它的思想是試圖使一幅圖像平均地使用所有像素的強度值,后面還給出一種改進的直方圖均衡方法。?
普通的直方圖均衡方法在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函數即可,測試下效果~

結果表明,圖像經直方圖均衡后,對比度有所提升,但其中其實隱含了一些問題,繼續測試:

問題很明顯,直方圖均衡化帶來了對比度的提升,但由于該算法使用了像素值的映射關系,造成了灰度級的減少,這對于很多圖像來說意味著細節的丟失。因此尋找一種改進的直方圖均衡化算法很有必要。
三、圖像增強之限制對比度自適應直方圖均衡
這里介紹一種有用的、同時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通過在計算累積直方圖函數前用預先定義的閾值來裁剪直方圖以達到限制放大幅度的目的。這限制了累積直方圖函數的斜度因此,也限制了變換函數的斜度。直方圖被裁剪的值,也就是所謂的裁剪限幅,取決于直方圖的分布因此也取決于領域大小的取值。
通常,直接忽略掉那些超出直方圖裁剪限幅的部分是不好的,而應該將這些裁剪掉的部分均勻的分布到直方圖的其他部分。如下圖所示。
~~~

事實上,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();
}
~~~
結果大大改善~



對于彩色圖像,有幾種較為主流的直方圖均衡方法:基于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圖像即可。該方法運行效率較高,處理后的圖像效果也更佳!
歡迎轉載或分享,但請務必聲明文章出處~
- 前言
- Win8.1下OpenCV2.4.9+Qt5.3.2開發環境搭建
- OpenCV2學習筆記(一)
- OpenCV2學習筆記(二)
- OpenCV2學習筆記(三)
- OpenCV2學習筆記(四)
- OpenCV2學習筆記(五)
- OpenCV2學習筆記(六)
- OpenCV2學習筆記(七)
- OpenCV2學習筆記(八)
- OpenCV2學習筆記(九)
- OpenCV2學習筆記(十)
- OpenCV2學習筆記(十一)
- OpenCV2學習筆記(十二)
- OpenCV2學習筆記(十三)
- OpenCV2學習筆記(十四)
- OpenCV2學習筆記(十五)
- OpenCV2學習筆記(十六)
- OpenCV2學習筆記(十七)
- OpenCV2學習筆記(十八)
- OpenCV2學習筆記(十九)
- OpenCV2學習筆記(二十)
- OpenCV2學習筆記(二十一)
- OpenCV2學習筆記(二十二)