直方图操作
直方图归一化normalize()
为了让数据可以完整的展示在图像中,绘制时会将统计数据缩小
另外,由于像素值统计数目与图像的尺寸具有直接关系,因此,如果以灰度值数目作为最终统计结果
那么一幅图像缩放后的直方图将有巨大的差异
理论上来说,通过对同一图像缩放之后的两幅图像的直方图应该具有大致相同的直方图分布特征
因此用灰度值的数目作为统计结果具有一定的局限性
1.图像的像素灰度值统计结果可以用灰度值像素的数目占一幅图像的比例来表示(即将统计结果/图像像素个数)实现归一化
但是,在CV_8U类型的图像中,灰度值有256个等级,平均每个像素的灰度值所占比例为0.39%,比例非常低
因此,为了更直观地绘制直方图,经常需要将比例扩大一定倍数再绘制直方图
2.寻找统计结果中的最大值,把所有结果除于最大值,实现所有数据归一化 0~1
对于上面两种归一化方法,OpenCV4提供了normalize()函数实现多种形式归一化功能
normalize()函数原型:
void normalize(InoutArray src,
InputOutputArray dst,
double alpha = 1,
double beta = 0,
int norm_type = NORM_L2,
InputArray mask = noArray()
)
- src:输入数组矩阵
- dst:输入与src相同大小的数组矩阵
- alpha:在范围归一化的情况下,归一化到下限边界的标准值
- beta:范围归一化时的上限范围,不用于标准规范化
- norm_type:归一化过程中数据范数种类标志
- dtype:输出数据类型标志,如果为-1,那么输出数据于src拥有相同的类型,否则于src具有相同的通道数,但数据类型不同
- mask:掩码矩阵
该函数输入一个存放数据的矩阵,通过参数alpha设置将数据缩放到最大范围,然后通过norm_type参数选择计算范数的种类
之后将输入矩阵中的每个数据分别除以求取的范数数值,最后得到缩放的结果,输出结果是一个CV_32F类型矩阵
可以将输入矩阵作为输出矩阵,或者重新定义一个新的矩阵用于存放输出结果
NORM_L1标志:输出结果为每个灰度值所占的比例
NORM_INF标志:输出结果为除以数据最大值,将所有数据归一化为0~1
normalize()函数归一化常用标志参数
| 标志参数 | 简记 | 作用 |
|---|---|---|
| NORM_INF | 1 | 无穷范数,向量最大值 |
| NORM_L1 | 2 | L1范数,绝对值之和 |
| NORM_L2 | 4 | L2范数及模长归一化,平方和之根 |
| NORM_L2SQR | 5 | L2范数平方 |
| NORM_MINMAX | 32 | 偏移归一化 |
示例:
#include <iostream>
#include <opencv2/opencv.hpp>
#include <vector>
using namespace std;
using namespace cv;
int main()
{
vector<double> positiveData = {2.0,8.0,10.0};
vector<double> normalized_L1,normalized_L2,normalized_Inf,normalized_MINMAX;
//测试不同归一化方法
normalize(positiveData,normalized_L1,1.0,0.0,NORM_L1); //绝对值求和归一化
cout <<"normalized_L1=[" << normalized_L1[0] <<", "
<< normalized_L1[1] << ", " << normalized_L1[2] << "]" << endl;
normalize(positiveData,normalized_L2,1.0,0.0,NORM_L2); //模长求和归一化
cout <<"normalized_L2=[" << normalized_L2[0] <<", "
<< normalized_L2[1] << ", " << normalized_L2[2] << "]" << endl;
normalize(positiveData,normalized_Inf,1.0,0.0,NORM_INF); //最大值求和归一化
cout <<"normalized_Inf=[" << normalized_Inf[0] <<", "
<< normalized_Inf[1] << ", " << normalized_Inf[2] << "]" << endl;
normalize(positiveData,normalized_MINMAX,1.0,0.0,NORM_MINMAX); //偏移求和归一化
cout <<"normalized_MINMAX=[" << normalized_MINMAX[0] <<", "
<< normalized_MINMAX[1] << ", " << normalized_MINMAX[2] << "]" << endl;
//将图像直方图归一化
Mat img = imread("E:\\CLion\\opencv_xin\\SG\\SG_0.jpg");
if (img.empty()) {
cout << "Error" << endl;
return -1;
}
Mat gray,hist;
cvtColor(img,gray,COLOR_BGR2GRAY);
const int channels[1] = { 0 }; //通道索引
float inRanges[2] = {0,255};
const float * ranges[1] = { inRanges }; //像素灰度值范围
const int bins[1] = { 256 }; //直方图的维度,其实就是像素灰度值的最大值
calcHist(&gray,1,channels,Mat(),hist,1,bins,ranges); //计算图像直方图
//绘制直方图
int hist_w = 512;
int hist_h = 400;
int width = 2;
Mat histImage_L1 = Mat::zeros(hist_h,hist_w,CV_8UC3);
Mat histImage_Inf = Mat::zeros(hist_h,hist_w,CV_8UC3);
Mat hist_L1,hist_Inf;
normalize(hist, hist_L1, 1, 0, NORM_L1, -1, Mat());
for (int i = 1; i <= hist_L1.rows; ++i)
{
rectangle(histImage_L1,
Point(width*(i-1),hist_h - 1),
Point(width*i - 1,hist_h - cvRound(30*hist_h*hist_L1.at<float>(i-1))-1),
Scalar(255,255,255),
-1
);
}
normalize(hist, hist_Inf, 1, 0, NORM_INF, -1, Mat());
for (int i = 1; i <= hist_Inf.rows; ++i)
{
rectangle(histImage_Inf,
Point(width*(i-1),hist_h - 1),
Point(width*i - 1,hist_h - cvRound(hist_h*hist_Inf.at<float>(i-1))-1 ),
Scalar(255,255,255),
-1
);
}
imshow("histImage_L1",histImage_L1);
imshow("histImage_Inf",histImage_Inf);
waitKey(0);
return 0;
}
直方图比较compareHist()
由于图像的直方图表示图像的像素灰度值的统计特性,因此可以通过两幅图像的直方图特性比较两幅图像的相似程度
虽然两幅图像的直方图分布相似不代表两幅图像相似
但是两幅图像相似则两幅图像的直方图一定相似
例如,对图像进行缩放之后,虽然图像的直方图不会完全一致,但是具有高度的相似性
因此,可以通过比较两幅图像的直方图分布相似性对图像进行初步的筛选和识别
OpenCV4提供了比较两个图像直方图相似性的compareHist()函数
compareHist()函数原型:
double compareHist(InputArray H1,
InputArray H2,
int method
)
- H1:第一幅图像直方图
- H2:第二幅图像直方图,与H2具有相同的尺寸
- method:比较方法标志
该函数前两个参数为需要比较的相似性的图像直方图,由于不同尺寸的图像的像素数目可能不同
为了能够得到两个图像直方图正确的相似性,需要输入同一种方式归一化后图像直方图
并且要求两个图像直方图具有相同的尺寸,第三个参数为比较相似性的方法
compareHist()函数比较直方图方法的可选择标志参数
| 标志参数 | 简记 | 名域或作用 |
|---|---|---|
| HISTCMP_CORREL | 0 | 相关法 |
| HISTCMP_CHISQR | 1 | 卡方法 |
| HISTCMP_INTERSECT | 2 | 直方图相交法 |
| HISTCMP_BHATTACHARYYA | 3 | 巴氏距离法 |
| HISTCMP_HELLINGER | 3 | 与HISTCMP_BHATTACHARYYA方法相同 |
| HISTCMP_CHISQR_ALT | 4 | 替代法方法 |
| HISTCMP_KL_DIV | 5 | 相对熵法 |
1.HISTCMP_CORREL
相关法,如果两个直方图完全相同,那么计算数值为1,如果完全不相关,计算值为0
2.HISTCMP_CHISQR
卡方法,如果两个直方图完全相同,那么计算数值为0,相似性越小,计算数值越大
3.HISTCMP_INTERSECT
直方图相交法,该方法不会将计算结果归一化,因此,即使是完全一致的图像直方图,来自不同图像,也会有不同的数值
例如:两个图像分别缩放之后的直方图相似性结果可能不一样,但是任意图像的直方图与一副图像的直方图相比较,数值越大
相似性越高,数值越小,相似性越低
4.HISTCMP_BHATTACHARYYA
巴塔恰里雅距离(巴氏距离)法,如果两个直方图完全相同,那么计算数值为0,相似性越小,计算数值越大
5.HISTCMP_CHISQR_ALT
替代法方法,相似性比较方法与巴氏距离法相同,常用于替代巴氏距离法用于纹理比较
6.HISTCMP_KL_DIV
相对熵法,又名Kullback-Leibler散度法,如果两个直方图完全相同,那么计算数值为0,相似性越小,计算数值越大
示例:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void drawHist(Mat &hist, int type, string name) //归一化并绘制直方图函数
{
int hist_w = 512;
int hist_h = 400;
int width = 2;
Mat histImage = Mat::zeros(hist_h,hist_w,CV_8UC3);
normalize(hist, hist, 1, 0, type, -1, Mat());
for (int i = 1; i <= hist.rows; ++i)
{
rectangle(histImage,
Point(width*(i-1),hist_h - 1),
Point(width*i - 1,hist_h - cvRound(hist_h*hist.at<float>(i-1))-1),
Scalar(255,255,255),
-1
);
}
imshow(name,histImage);
}
int main() {
Mat img = imread("E:\\CLion\\opencv_xin\\SG\\SG_0.jpg");
if (img.empty()) {
cout << "Error" << endl;
return -1;
}
Mat gray,hist,gray2,hist2,gray3,hist3;
cvtColor(img,gray,COLOR_BGR2GRAY);
resize(gray,gray2,Size(),0.5,0.5);
gray3 = imread("E:\\CLion\\opencv_xin\\SG\\SG_1.jpg",IMREAD_GRAYSCALE);
const int channels[1] = { 0 }; //通道索引
float inRanges[2] = {0,255};
const float * ranges[1] = { inRanges }; //像素灰度值范围
const int bins[1] = { 256 }; //直方图的维度,其实就是像素灰度值的最大值
//计算图像直方图
calcHist(&gray,1,channels,Mat(),hist,1,bins,ranges);
calcHist(&gray2,1,channels,Mat(),hist2,1,bins,ranges);
calcHist(&gray3,1,channels,Mat(),hist3,1,bins,ranges);
drawHist(hist,NORM_INF,"hist");
drawHist(hist2,NORM_INF,"hist2");
drawHist(hist3,NORM_INF,"hist3");
//原图直方图与原图直方图的相关系数
double hist_hist = compareHist(hist,hist,HISTCMP_CORREL);
cout << "apple_apple=" << hist_hist << endl;
//原图直方图与缩小原图直方图的相关系数
double hist_hist2 = compareHist(hist,hist2,HISTCMP_CORREL);
cout << "apple_apple256=" << hist_hist2 << endl;
//两幅不同图像直方图相关系数
double hist_hist3 = compareHist(hist,hist3,HISTCMP_CORREL);
cout << "apple_apple=" << hist_hist3 << endl;
waitKey(0);
return 0;
}