OpenCV4入门教程088:视频直方图

索引地址:系列索引

本文实现截取视频中画面,计算直方图,并比较两幅图相似度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//---------------------------------【头文件、命名空间包含部分】-----------------------------
// 描述:包含程序所使用的头文件和命名空间
//-------------------------------------------------------------------------------------------------
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <stdio.h>
using namespace cv;
using namespace std;

//-----------------------------------【全局变量声明部分】---------------------------------------
// 描述:声明全局变量
//---------------------------------------------------------------------------------------------------
int nFrame_num = 0;
bool pause = false;
bool tracking = false;
Rect preselectROI, mySelectROI; //用于存放手选的矩形
bool comp = true;

Mat rhist, ghist, bhist;
int channels[] = {0, 1, 2};
// const int histsize[]={256,256,256};
const int histsize[] = {16, 16, 16};
const int histsize1 = 16;
float rranges[] = {0, 255};
float granges[] = {0, 255};
float branges[] = {0, 255};
float range[] = {0, 255};
const float *ranges1 = {range}; //这里的ranges就相当于一个双指针了
const float *ranges[] = {
rranges, granges,
branges}; // ranges是个双指针,且前面一定要用const,即不可改变常量,提高程序的可读性和稳健性
// const float *ranges[]={{0,255},{0,255},{0,255}};

//-----------------------------------【onMouse( )函数】--------------------------------------
// 描述:鼠标回调函数
//-------------------------------------------------------------------------------------------------
void onMouse(int event, int x, int y, int, void *) {
if (event == cv::EVENT_LBUTTONDOWN) {
mySelectROI.x = x;
mySelectROI.y = y;
tracking = false;
} else if (event == cv::EVENT_LBUTTONUP) {
mySelectROI.width = x - mySelectROI.x;
mySelectROI.height = y - mySelectROI.y;
tracking = true;
comp = true;
nFrame_num++; //选定后才算真正意义上的第一帧
if (nFrame_num >= 10)
nFrame_num = 10; //防止nFrame_num溢出
}
}

//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始
//-------------------------------------------------------------------------------------------------
int main(int argc, const char *argv[]) {
Mat frame, img;
Mat staRoiHist;
MatND RoiHist;
int DRAW_H = 400, DRAW_W = 400;
Mat draw(DRAW_W, DRAW_H, CV_8UC3, Scalar(0, 0, 0)); //建立一个显示直方图的图片,背景为纯黑色
int DRAW_BIN_W = cvRound(DRAW_W / histsize1);

//打开摄像头
VideoCapture cam(0);
if (!cam.isOpened())
return -1;

cout << "\n\n\n\n\t程序运行成功,请用鼠标在窗口中两次框选需要计算直方图的区域。" << endl;
//鼠标捕捉
namedWindow("camera", 1);
namedWindow("rgb_hist", 1);
setMouseCallback("camera", onMouse,
0); //这里用的是面向对象的思想,只要有鼠标动作就会调用鼠标响应函数

while (1) {
if (!pause) //暂停按钮只需控制视频的读取
{
cam >> frame;
if (frame.empty())
break; // break此处跳出的是while语句,一般是跳出for或while语句,不要理解为跳出if语句
}

if (tracking) {
Mat RoiImage(frame, mySelectROI);

//-------------------------------------------------------------------------------------------------------------------
// calcHist():计算图像块的直方图矩阵
// calcHist(),第1个参数为原数组区域列表;第二个参数为有计算几个原数组;参数3为需要统计的通道索引数;参数4为操作掩码
// 第5个参数为存放目标直方图矩阵;参数6为需要计算的直方图的维数;参数7为每一维的bin的个数;参数8为每一维数值的取值范围
// 参数10为每个bin的大小是否相同的标志,默认为1,即bin的大小都相同;参数11为直方图建立时清除内存痕迹标志,默认为0,即清除
//-------------------------------------------------------------------------------------------------------------------
calcHist(&RoiImage, 1, channels, Mat(), RoiHist, 3, histsize,
ranges); //原数组区域RoiImage,1个源,需要统计的通道索引为{0,1,2},
//目标直方图RoiHist,3维,每一维的bin数histsize,取值范围为
// ranges,实际上计算出的目标矩阵类似一维矩阵。

//---------------------------------------------------------------------------------------------------------------------
// normalize():根据某种范数或者数值范围归一化数组
// normalize(),参数1表示需要归一化的数组;参数2为归一化后的目的数组;参数3表示输出数值的最小值/最大值或者输出数值的范数;
// 参数4表示输出数值的最小值/最大值;参数5表示归一化数组使用的归一化类型,默认值为使用L2范数;参数6为对应元素的掩膜矩阵
// 默认值为空,即不采用掩膜操作
//---------------------------------------------------------------------------------------------------------------------
normalize(RoiHist, RoiHist); //使用L2范数将RoiHist直方图原地归一化

vector<Mat> rgb_planes; //这里的vector为向量,向量的数据类型为Mat结构体,向量的长度为3
split(RoiImage, rgb_planes); //将rgb图分解到rgb_planes各个分量中
calcHist(&rgb_planes[ 0 ], 1, 0, Mat(), rhist, 1, &histsize1, &ranges1);
normalize(rhist, rhist, 0, DRAW_H, NORM_MINMAX); //进行最大最小值归一化
calcHist(&rgb_planes[ 1 ], 1, 0, Mat(), ghist, 1, &histsize1, &ranges1);
normalize(ghist, ghist, 0, DRAW_H, NORM_MINMAX);
calcHist(&rgb_planes[ 2 ], 1, 0, Mat(), bhist, 1, &histsize1, &ranges1);
normalize(bhist, bhist, 0, DRAW_H, NORM_MINMAX);
if (nFrame_num == 1) {
// preselectROI=mySelectROI;
preselectROI.x = mySelectROI.x;
preselectROI.y = mySelectROI.y;
preselectROI.width = mySelectROI.width;
preselectROI.height = mySelectROI.height;
staRoiHist = RoiHist.clone(); //第一次选定目标,作为标准模板目标
} else if (nFrame_num > 1 && comp == true) {
//---------------------------------------------------------------------------------------------------------------------
// compareHist():比较2个直方图的相似度
// compareHist(),参数1为比较相似度的直方图1;参数2为比较相似度的直方图2;参数3为相似度的计算方式。有四种,
// 分别为CV_COMP_CORREL,CV_COMP_CHISQR,CV_COMP_INTERSECT,CV_COMP_BHATTACHARYYA
//---------------------------------------------------------------------------------------------------------------------
double distence = compareHist(
staRoiHist, RoiHist,
cv::HISTCMP_INTERSECT); //计算后面选定的与这次选定的相似度,使用INTERSECT,值越大越相似
printf("\n\t>与第1次选定的图像区域相似度为:%f\n", distence); //数组越大,相似度越大

//显示直方图
for (int i = 1; i < histsize1; i++) {
//画直线中要注意2点,因为图片的原点在左上角,而直方图坐标系的原点在左下角,所以高度值都需要被直方图图纸高度减掉,另外取一维直方图时只能用at运算符
line(draw,
Point(DRAW_BIN_W * (i - 1), DRAW_H - cvRound(rhist.at<float>((i - 1)))),
Point(DRAW_BIN_W * (i), DRAW_H - cvRound(rhist.at<float>(i))),
Scalar(255, 0, 0), 2, 8, 0);
line(draw,
Point(DRAW_BIN_W * (i - 1), DRAW_H - cvRound(ghist.at<float>((i - 1)))),
Point(DRAW_BIN_W * (i), DRAW_H - cvRound(ghist.at<float>(i))),
Scalar(0, 255, 0), 2, 8, 0);
line(draw,
Point(DRAW_BIN_W * (i - 1), DRAW_H - cvRound(bhist.at<float>((i - 1)))),
Point(DRAW_BIN_W * (i), DRAW_H - cvRound(bhist.at<float>(i))),
Scalar(0, 0, 255), 2, 8, 0);
}
imshow("rgb_hist", draw);
draw = Mat::zeros(DRAW_W, DRAW_H, CV_8UC3); //每画完一次直方图后都进行一次清0操作
comp = false;
}
rectangle(frame, mySelectROI, Scalar(0, 255, 0), 2, 8); //手动选定一次就显示一次
} // end tracking
rectangle(frame, preselectROI, Scalar(0, 0, 255), 5, 8); //初始的选定目标一直不变
imshow("camera", frame);

//键盘响应
char c = (char)waitKey(10);
if (c == 27)
break;
switch (c) {
case 'p': //暂停键
pause = !pause;
break;
default:;
}
} // end while;
return 0;
}

效果为

vh


OpenCV4入门教程088:视频直方图
https://blog.jackeylea.com/opencv/opencv-video-histogram/
作者
JackeyLea
发布于
2020年11月10日
许可协议