第八节 直方图
OpenCV库提供了直方图统计、直方图比较、直方图均衡化、直方图反向投影等API。
1、cv::calcHist
计算一组数组的直方图。
void cv::calcHist(const Mat * images,int nimages,const int * channels,InputArray mask,OutputArray hist,int dims,const int * histSize,const float ** ranges,bool uniform = true,bool accumulate = false)
函数cv :: calcHist计算一个或多个数组的直方图。 用于增加直方图bin的元组的元素取自同一位置的相应输入数组。
参数如下:
参数名称 | 参数描述 |
---|---|
images | 输入图像数组。它们都应具有相同的深度CV_8U,CV_16U或CV_32F和相同的大小。 它们每个都可以具有任意数量的通道。 |
nimages | 输入图像数量 |
channels | 输入图像通道数组 |
mask | 可靠掩膜,如果矩阵不为空,则它必须是大小与images [i]相同的8位数组。 非零掩码元素标记在直方图中计数的数组元素。 |
hist | 输出直方图,它是密集或稀疏的暗维数组。 |
dims | 直方图维数必须为正且不大于CV_MAX_DIMS(在当前OpenCV版本中等于32)。 |
histSize | 每个维度中的直方图大小数组。 |
ranges | 每个维度中直方图bin边界的dims数组。 当直方图是均匀的时(uniform = true),则对于每个维度i,足以指定第0个直方图bin的下(包含)边界 L 0 L0 L0和上界(专有)边界$UhistSize [i] -1 $最后一个直方图bin h i s t S i z e [ i ] − 1 histSize [i] -1 histSize[i]−1。 即,在均匀的直方图的情况下, r a n g e [ i ] range [i] range[i]中的每个都是2个元素的数组。 当直方图不一致时(uniform= false),则每个 r a n g e [ i ] range [i] range[i]都包含 h i s t S i z e [ i ] + 1 histSize [i] +1 histSize[i]+1个元素: L 0 , U 0 = L 1 , U 1 = L 2 , . . . , U h i s t S i z e [ i ] − 2 = L h i s t S i z e [ i ] − 1 , U h i s t S i z e [ i ] − 1 L0,U0 = L1,U1 = L2,...,UhistSize [i] -2 = LhistSize [i] -1,UhistSize [i] -1 L0,U0=L1,U1=L2,...,UhistSize[i]−2=LhistSize[i]−1,UhistSize[i]−1。 不在 L 0 L0 L0和 U h i s t S i z e [ i ] − 1 UhistSize [i] -1 UhistSize[i]−1之间的数组元素不计入直方图。 |
uniform | 指示直方图是否均匀的标志(请参见上文)。 |
accumulate | 累积标志。 如果已设置,则分配直方图时不会在开始时清除它。 此功能使您可以从几组数组中计算单个直方图,或者及时更新直方图。 |
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
// 读取图像
cv::Mat src = cv::imread("images/bird-2.jpg");
if(src.empty()){
cerr << "cannot read image.\n";
return EXIT_FAILURE;
}
cv::resize(src,src,cv::Size(src.cols/2,src.rows/2));
// 设置直方图参数
int bins = 256;
int histsize []= {bins};
float range[] = {0,256};
const float* histRange = {range};
// 通道分离
vector<cv::Mat> channels;
cv::split(src,channels);
// 计算直方图
cv::Mat rHist,gHist,bHist;
cv::calcHist(&channels[0],1,0,cv::Mat(),bHist,1,histsize,&histRange,true,false);
cv::calcHist(&channels[1],1,0,cv::Mat(),gHist,1,histsize,&histRange,true,false);
cv::calcHist(&channels[2],1,0,cv::Mat(),rHist,1,histsize,&histRange,true,false);
// 设置绘制直方图参数
int histHeight = 360;
int histWidth = bins * 3;
int binWidth = cvRound((double)histWidth / bins);
cv::Mat histImage = cv::Mat::zeros(histHeight,histWidth,CV_8UC3);
// 归一化直方图
cv::normalize(rHist, rHist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat()); //B-通道
cv::normalize(gHist, gHist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat()); //G-通道
cv::normalize(bHist, bHist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat()); //R-通道
// 绘制直方图
for(int i = 0;i < bins;i++){
cv::line(histImage, cv::Point(binWidth*(i - 1), histHeight - cvRound(bHist.at<float>(i - 1))),
cv::Point(binWidth*(i), histHeight - cvRound(bHist.at<float>(i))),
cv::Scalar(255, 0, 0), 1, 8, 0);
cv::line(histImage, cv::Point(binWidth*(i - 1), histHeight - cvRound(gHist.at<float>(i - 1))),
cv::Point(binWidth*(i), histHeight - cvRound(gHist.at<float>(i))),
cv::Scalar(0, 255, 0), 1, 8, 0);
cv::line(histImage, cv::Point(binWidth*(i - 1), histHeight - cvRound(rHist.at<float>(i - 1))),
cv::Point(binWidth*(i), histHeight - cvRound(rHist.at<float>(i))),
cv::Scalar(0, 0, 255), 1, 8, 0);
}
cv::imshow("src",src);
cv::imshow("histogram",histImage);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}
cv::calcHist函数还有如下重载形式:
- void cv::calcHist(const Mat * images,int nimages,const int * channels,InputArray mask,SparseMat & hist,int dims,const int * histSize,const float ** ranges,bool uniform = true,bool accumulate = false)
- void cv::calcHist(InputArrayOfArrays images,const std::vector< int > & channels,InputArray mask,OutputArray hist,const std::vector< int > & histSize,const std::vector< float > & ranges,bool accumulate = false)
2、cv::calcBackProject
计算直方图的反投影。
void cv::calcBackProject(const Mat * images,int nimages,const int * channels,InputArray hist,OutputArray backProject,const float ** ranges,double scale = 1,bool uniform = true)
函数cv :: calcBackProject计算直方图的反向投影。 也就是说,类似于calcHist,该函数在每个位置(x,y)收集输入图像中所选通道的值,并找到对应的直方图bin。 但是该函数不是增加它,而是读取bin值,按比例缩放它,并存储在backProject(x,y)中。 在统计方面,该函数根据直方图表示的经验概率分布计算每个元素值的概率。 例如,查看如何在场景中查找和跟踪色彩鲜艳的对象:
- 跟踪之前,请向相机展示物体,以使其几乎覆盖整个画面。 计算色调直方图。 直方图可能具有很强的最大值,对应于对象中的主要颜色。
- 跟踪时,请使用该预先计算的直方图计算每个输入视频帧的色相平面的反投影。 限制背投阈值以抑制较弱的色彩。 抑制色彩饱和度不足且像素太暗或太亮的像素也可能是有意义的。
- 在结果图片中找到连接的组件,然后选择最大的组件。
与CamShift颜色对象跟踪器的近似算法。
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
// 读取图像
cv::Mat src = cv::imread("images/bird-2.jpg",0);
if(src.empty()){
cerr << "cannot read image.\n";
return EXIT_FAILURE;
}
cv::resize(src,src,cv::Size(src.cols/2,src.rows/2));
// 设置直方图参数
int bins = 256;
int histsize []= {bins};
float range[] = {0,256};
const float* histRange = {range};
vector<cv::Mat> channels;
channels.push_back(src);
cv::Mat hist;
// 计算直方图
cv::calcHist(&channels[0],1,0,cv::Mat(),hist,1,histsize,&histRange,true,false);
// 设置绘制直方图参数
int histHeight = 360;
int histWidth = bins * 3;
int binWidth = cvRound((double)histWidth / bins);
cv::Mat histImage = cv::Mat::zeros(histHeight,histWidth,CV_8UC3);
// 归一化直方图
cv::normalize(hist, hist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat()); //B-通道
// 绘制直方图
for(int i = 0;i < bins;i++){
cv::line(histImage, cv::Point(binWidth*(i - 1), histHeight - cvRound(hist.at<float>(i - 1))),
cv::Point(binWidth*(i), histHeight - cvRound(hist.at<float>(i))),
cv::Scalar(255, 0, 0), 1, 8, 0);
}
// 计算反射投影
cv::MatND backproj;
cv::calcBackProject(&channels[0],1,0,hist,backproj,&histRange,1,true);
// 显示
cv::imshow("src",src);
cv::imshow("histogram",histImage);
cv::imshow("backproject",backproj);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}
其重载函数如下:
- void cv::calcBackProject(const Mat * images,int nimages,const int * channels,const SparseMat & hist,OutputArray backProject,const float ** ranges,double scale = 1,bool uniform = true)
- void cv::calcBackProject(InputArrayOfArrays images,const std::vector< int > & channels,InputArray hist,OutputArray dst,const std::vector & ranges,double scale)
3、cv::compareHist
比较两个直方图。
double cv::compareHist(InputArray H1,InputArray H2,int method)
函数cv :: compareHist使用指定的方法比较两个稠密或两个稀疏直方图。返回 d ( H 1 , H 2 ) d(H1,H2) d(H1,H2)。
虽然该函数可以很好地处理1、2、3维密集直方图,但它可能不适用于高维稀疏直方图。 在这种直方图中,由于混叠和采样问题,非零直方图块的坐标可能会略有偏移。 要比较此类直方图或加权点的更一般的稀疏配置,请考虑使用EMD函数。
参数如下:
参数名称 | 参数描述 |
---|---|
H1 | 第一个直方图 |
H2 | 第二个直方图,与H1的大小相同 |
method | 比较方法,请参考HistCompMethods |
HistCompMethods的类型如下:
方法名称 | 方法描述 |
---|---|
HISTCMP_CORREL | 相关性: d ( H 1 , H 2 ) = ∑ I ( H 1 ( I ) − H 1 ˉ ) ( H 2 ( I ) − H 2 ˉ ) ∑ I ( H 1 ( I ) − H 1 ˉ ) 2 ∑ I ( H 2 ( I ) − H 2 ˉ ) 2 d(H_1,H_2) = \frac{\sum_I (H_1(I) - \bar{H_1}) (H_2(I) - \bar{H_2})}{\sqrt{\sum_I(H_1(I) - \bar{H_1})^2 \sum_I(H_2(I) - \bar{H_2})^2}} d(H1,H2)=∑I(H1(I)−H1ˉ)2∑I(H2(I)−H2ˉ)2∑I(H1(I)−H1ˉ)(H2(I)−H2ˉ),其中, H k ˉ = 1 N ∑ J H k ( J ) \bar{H_k} = \frac{1}{N} \sum _J H_k(J) Hkˉ=N1∑JHk(J),N是直方图块的总数。 |
HISTCMP_CHISQR | 卡方(Chi-Square): d ( H 1 , H 2 ) = ∑ I ( H 1 ( I ) − H 2 ( I ) ) 2 H 1 ( I ) d(H_1,H_2) = \sum _I \frac{\left(H_1(I)-H_2(I)\right)^2}{H_1(I)} d(H1,H2)=∑IH1(I)(H1(I)−H2(I))2 |
HISTCMP_INTERSECT | 交点: d ( H 1 , H 2 ) = ∑ I min ( H 1 ( I ) , H 2 ( I ) ) d(H_1,H_2) = \sum _I \min (H_1(I), H_2(I)) d(H1,H2)=∑Imin(H1(I),H2(I)) |
HISTCMP_BHATTACHARYYA | Bhattacharyya距离(实际上,OpenCV会计算Hellinger距离,该距离与Bhattacharyya系数有关): d ( H 1 , H 2 ) = 1 − 1 H 1 ˉ H 2 ˉ N 2 ∑ I H 1 ( I ) ⋅ H 2 ( I ) d(H_1,H_2) = \sqrt{1 - \frac{1}{\sqrt{\bar{H_1} \bar{H_2} N^2}} \sum_I \sqrt{H_1(I) \cdot H_2(I)}} d(H1,H2)=1−H1ˉH2ˉN21∑IH1(I)⋅H2(I) |
HISTCMP_HELLINGER | 与HISTCMP_BHATTACHARYYA相同 |
HISTCMP_CHISQR_ALT | 另类卡方(Alternative Chi-Square): d ( H 1 , H 2 ) = 2 ∗ ∑ I ( H 1 ( I ) − H 2 ( I ) ) 2 H 1 ( I ) + H 2 ( I ) d(H_1,H_2) = 2 * \sum _I \frac{\left(H_1(I)-H_2(I)\right)^2}{H_1(I)+H_2(I)} d(H1,H2)=2∗∑IH1(I)+H2(I)(H1(I)−H2(I))2 |
HISTCMP_KL_DIV | Kullback-Leibler散度: d ( H 1 , H 2 ) = ∑ I H 1 ( I ) log ( H 1 ( I ) H 2 ( I ) ) d(H_1,H_2) = \sum _I H_1(I) \log \left(\frac{H_1(I)}{H_2(I)}\right) d(H1,H2)=∑IH1(I)log(H2(I)H1(I)) |
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
//读取图片
cv::Mat src1 = cv::imread("images/apple-1.jpg");
if(src1.empty()){
cerr << "cannot read image.\n";
}
cv::Mat src2 = cv::imread("images/apple-2.jpg");
if(src2.empty()){
cerr << "cannot read image.\n";
}
// 转换颜色空间
cv::Mat hsv1,hsv2;
cv::cvtColor(src1,hsv1,cv::COLOR_BGR2HSV);
cv::cvtColor(src2,hsv2,cv::COLOR_BGR2HSV);
// 设置直方图计算参数
int h_bins = 50, s_bins = 60;
int histSize[] = { h_bins, s_bins };
// H、S通道的范围
float h_ranges[] = { 0, 180 };
float s_ranges[] = { 0, 256 };
const float* ranges[] = { h_ranges, s_ranges };
int channels[] = { 0, 1 };
// 计算直方图
cv::Mat hist_src1,hist_src2;
cv::calcHist( &hsv1, 1, channels, cv::Mat(),hist_src1, 2, histSize, ranges, true, false );
cv::calcHist( &hsv2, 1, channels, cv::Mat(),hist_src2, 2, histSize, ranges, true, false );
// 归一化处理
cv::normalize( hist_src1, hist_src1, 0, 1, cv::NORM_MINMAX, -1, cv::Mat());
cv::normalize( hist_src2, hist_src2, 0, 1, cv::NORM_MINMAX, -1, cv::Mat());
// 执行直方图比较
cv::imshow("src1",src1);
cv::imshow("src2",src2);
double result = cv::compareHist(hist_src1,hist_src2,cv::HISTCMP_BHATTACHARYYA);
cout << "use HISTCMP_BHATTACHARYYA:" << result << endl;
result = cv::compareHist(hist_src1,hist_src2,cv::HISTCMP_CHISQR);
cout << "use HISTCMP_CHISQR:" << result << endl;
result = cv::compareHist(hist_src1,hist_src2,cv::HISTCMP_CHISQR_ALT);
cout << "use HISTCMP_CHISQR_ALT:" << result << endl;
result = cv::compareHist(hist_src1,hist_src2,cv::HISTCMP_CORREL);
cout << "use HISTCMP_CORREL:" << result << endl;
result = cv::compareHist(hist_src1,hist_src2,cv::HISTCMP_HELLINGER);
cout << "use HISTCMP_HELLINGER:" << result << endl;
result = cv::compareHist(hist_src1,hist_src2,cv::HISTCMP_INTERSECT);
cout << "use HISTCMP_INTERSECT:" << result << endl;
result = cv::compareHist(hist_src1,hist_src2,cv::HISTCMP_KL_DIV);
cout << "use HISTCMP_KL_DIV:" << result << endl;
return 0;
}
4、cv::createCLAHE
创建对比度有限的自适应直方图均衡CLAHE对象。
Ptr cv::createCLAHE(double clipLimit = 40.0,Size tileGridSize = Size(8, 8))
参数如下:
参数名称 | 参数描述 |
---|---|
clipLimit | 限制对比度的阈值。 |
tileGridSize | 直方图均衡化的网格大小。 输入图像将被分成相等大小的矩形图块。 tileGridSize定义行和列中的切片数。 |
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
//读取图片
cv::Mat src = cv::imread("images/apple-1.jpg",0);
if(src.empty()){
cerr << "cannot read image.\n";
}
// 创建CLAHE对象
cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE();
// 对图像处理
cv::Mat dst;
clahe->apply(src,dst);
// 显示
cv::imshow("dst",dst);
cv::imshow("src",src);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}
5、cv::EMD、cv::wrapperEMD
1)cv::EMD:计算两个加权点配置之间的“最小功”距离。
float cv::EMD(InputArray signature1,InputArray signature2,int distType,InputArray cost = noArray(),float * lowerBound = 0,OutputArray flow = noArray())
该函数计算推土机距离和/或两个加权点配置之间的距离的下边界。一般用于图像检索的多维直方图比较。EMD是使用单形算法的某些修改解决的运输问题,因此,在最坏的情况下,复杂度是指数级的,但是平均而言,它要快得多。 在实数的情况下,下边界可以更快地计算(使用线性时间算法),并且可以用来大致确定两个签名是否足够远,以致它们不能与同一对象相关。
参数如下:
参数名称 | 参数描述 |
---|---|
signature1 | 第一个特征,为size1×dims + 1浮点矩阵。 每行存储点权重,后跟点坐标。 如果使用了用户定义的成本矩阵,则矩阵只能有一个列(仅权重)。 权重必须为非负数,并且至少具有一个非零值。 |
signature2 | 第二个特征,尽管行数可能不同,但第二个特征的格式与signature1相同。 总重量可能不同。 在这种情况下,将额外的“虚拟”点添加到特征1或特征2。 权重必须为非负数,并且至少具有一个非零值。 |
distType | 距离类型. See DistanceTypes. |
cost | 用户定义的size1×size2成本矩阵。 此外,如果使用成本矩阵,则由于需要度量函数,因此无法计算下边界lowerBound。 |
lowerBound | 可选的输入/输出参数:两个特征之间的距离的下边界,即质心之间的距离。 如果使用用户定义的成本矩阵,点配置的总权重不相等,或者签名仅由权重组成(签名矩阵只有一列),则可能无法计算下边界。 您必须**初始化* lowerBound。 如果计算出的重心之间的距离大于或等于* lowerBound(这意味着签名足够远),则该函数不会计算EMD。 在任何情况下,* lowerBound均设置为返回时质心之间的计算距离。 因此,如果要同时计算质心和EMD之间的距离,则* lowerBound应该设置为0。 |
flow | 结果size1×size2流矩阵:flowi,j是从特征1的第i个点到特征2的第j个点的流。 |
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
//读取图片
cv::Mat src1 = cv::imread("images/apple-1.jpg");
if(src1.empty()){
cerr << "cannot read image.\n";
}
cv::Mat src2 = cv::imread("images/apple-2.jpg");
if(src2.empty()){
cerr << "cannot read image.\n";
}
// 转换颜色空间
cv::Mat hsv1,hsv2;
cv::cvtColor(src1,hsv1,cv::COLOR_BGR2HSV);
cv::cvtColor(src2,hsv2,cv::COLOR_BGR2HSV);
// 设置直方图计算参数
int h_bins = 50, s_bins = 60;
int histSize[] = { h_bins, s_bins };
// H、S通道的范围
float h_ranges[] = { 0, 180 };
float s_ranges[] = { 0, 256 };
const float* ranges[] = { h_ranges, s_ranges };
int channels[] = { 0, 1 };
// 计算直方图
cv::Mat hist_src1,hist_src2;
cv::calcHist( &hsv1, 1, channels, cv::Mat(),hist_src1, 2, histSize, ranges, true, false );
cv::calcHist( &hsv2, 1, channels, cv::Mat(),hist_src2, 2, histSize, ranges, true, false );
// 归一化处理
cv::normalize( hist_src1, hist_src1, 0, 1, cv::NORM_MINMAX, -1, cv::Mat());
cv::normalize( hist_src2, hist_src2, 0, 1, cv::NORM_MINMAX, -1, cv::Mat());
// 生成signature
int numrows = h_bins * s_bins;
cv::Mat sig1(numrows, 3, CV_32FC1);
cv::Mat sig2(numrows, 3, CV_32FC1);
// 直充signature
for(int h=0; h< h_bins; h++)
{
for(int s=0; s< s_bins; ++s)
{
float binval = hist_src1.at< float>(h,s);
sig1.at< float>( h*s_bins + s, 0) = binval;
sig1.at< float>( h*s_bins + s, 1) = h;
sig1.at< float>( h*s_bins + s, 2) = s;
binval = hist_src2.at< float>(h,s);
sig2.at< float>( h*s_bins + s, 0) = binval;
sig2.at< float>( h*s_bins + s, 1) = h;
sig2.at< float>( h*s_bins + s, 2) = s;
}
}
//计算EMD,结果的值为0时,为最佳匹配。
float emd = cv::EMD(sig1, sig2, cv::DIST_L2);
cout << "emd:" << emd << endl;
printf("similarity %5.5f %%\n", (1-emd)*100 );
return 0;
}
6、cv::equalizeHist
灰度图像直方图均衡化。
void cv::equalizeHist(InputArray src,OutputArray dst)
该函数使用以下算法均衡输入图像的直方图:
-
计算src的直方图H
-
标准化直方图,以使直方图bin的总和为255。
-
计算直方图的积分:
H i ′ = ∑ 0 ≤ j < i H ( j ) H'_i = \sum _{0 \le j < i} H(j) Hi′=∑0≤j<iH(j)
-
使用H’作为查找表变换图像: d s t ( x , y ) = H ′ ( s r c ( x , y ) ) dst(x,y)= H'(src(x,y)) dst(x,y)=H′(src(x,y))
该算法对亮度进行归一化并增加图像的对比度。
参数 | 参数描述 |
---|---|
src | 输入图像,8位单通道灰度图像 |
dst | 输出图像,大小与src相同 |
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
//读取图片
cv::Mat src = cv::imread("images/apple-1.jpg",0);
if(src.empty()){
cerr << "cannot read image.\n";
}
// 计算灰度图像直方图均衡化
cv::Mat dst;
cv::equalizeHist(src,dst);
// 显示
cv::imshow("src",src);
cv::imshow("dst",dst);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}