一、思路:
(1)主函数 (main
):
- 设置日志级别以隐藏日志信息。
- 定义是否使用摄像头或视频文件的标志,以及视频文件路径。
- 创建并打开视频输入,根据标志选择使用摄像头或视频文件。
- 检查视频是否成功打开,如果失败则打印错误信息并退出。
- 初始化帧计数器、直方图参数和直方图数组。
- 循环读取视频帧,对于每一帧,转换为灰度图像,并更新所选像素的直方图。
- 绘制并显示直方图和当前帧。
- 等待用户按键,如果用户按下ESC键,则退出循环并结束程序。
(2)鼠标响应函数 (on_mouse
):
- 处理鼠标点击事件,当前只处理鼠标左键按下事件。
- 更新全局变量
vP
为鼠标点击的坐标。 - 在鼠标点击的位置上绘制标记,并更新显示图像。
(3)绘制直方图函数 (drawHist
):
- 创建一个用于绘制直方图的空白图像。
- 初始化直方图图像为白色背景。
- 计算直方图数组中的最大值,用于归一化。
- 遍历所有灰度级别,根据直方图数组中的值绘制每个bin的高度。
- 绘制每个bin的矩形,并填充颜色。
(4)创建视频输入函数 (createInput
):
- 根据传入的标志决定是打开摄像头还是视频文件。
- 使用
VideoCapture
类创建视频输入,并返回创建的对象。
二、示例代码:
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/objdetect.hpp>
#include <opencv2/highgui/highgui_c.h>
#include <iostream>
using namespace cv;
using namespace std;
cv::Point vP;
string wName = "鼠标左键点击选择像素,选择后按任意键开始处理";
VideoCapture createInput(bool useCamera, std::string videoPath);
int drawHist(cv::Mat& histMat, float* srcHist, int bin_width, int bin_heght);
void on_mouse(int EVENT, int x, int y, int flags, void* userdata);
// 主函数入口点
int main() {
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);
// 定义是否使用摄像头或视频文件的标志
bool useCamera = 0; // 0 表示使用视频文件
// 定义视频文件的路径
string videoPath = "C:\\Users\\86173\\Desktop\\TI\\cat.mp4";
// 创建并打开视频输入,根据 useCamera 标志选择摄像头或视频文件
VideoCapture capVideo = createInput(useCamera, videoPath);
// 检查视频是否成功打开
if (!capVideo.isOpened()) {
cout << "Unable to open video!" << endl;
return -1; // 如果无法打开视频,返回错误码
}
// 初始化帧计数器
int cnt = 0;
// 初始化直方图每个bin的宽度和高度
int bin_width = 1;
int bin_heght = 256;
// 初始化直方图数组,所有值设为0
float histgram[256] = { 0 };
// 声明用于显示直方图的Mat对象
Mat histMat;
// 循环读取视频帧
while (1) {
// 声明帧和灰度图像的Mat对象
Mat frame;
Mat grayMat;
// 读取下一帧
capVideo >> frame;
// 如果帧为空,表示视频读取完成
if (frame.empty()) {
cout << "视频读取完成!" << endl;
cout << "视频帧数:" << endl;
cout << cnt << endl;
return -1; // 返回错误码
}
// 如果是第一帧,复制帧到 selectMat,并设置鼠标回调函数
if (cnt == 0) {
Mat selectMat;
frame.copyTo(selectMat);
namedWindow(wName); // 创建窗口
imshow(wName, selectMat); // 显示帧
setMouseCallback(wName, on_mouse, &selectMat); // 设置鼠标回调
waitKey(0); // 等待用户点击
destroyAllWindows(); // 销毁所有窗口
}
// 将当前帧从BGR颜色空间转换到灰度空间
cvtColor(frame, grayMat, COLOR_BGR2GRAY);
// 如果 vP 未定义,这里需要定义 vP 并初始化
// 获得鼠标点击选择的像素的灰度值
int index = grayMat.at<uchar>(vP.y, vP.x);
// 直方图相应的bin加1
histgram[index]++;
// 绘制直方图
drawHist(histMat, histgram, bin_width, bin_heght);
// 绘制标记
drawMarker(frame, vP, Scalar(255, 255, 255));
// 显示帧和直方图
imshow("frame", frame);
imshow("histMat", histMat);
// 延时30ms以显示图像,等待键盘响应,按下ESC键退出
if (waitKey(30) == 27) {
destroyAllWindows(); // 销毁所有窗口
break; // 退出循环
}
cnt++; // 增加帧计数器
}
return 0; // 正常退出程序
}
// 鼠标响应函数,用于处理鼠标事件
void on_mouse(int EVENT, int x, int y, int flags, void* userdata) {
// 声明一个 Mat 对象,用于存储鼠标响应时的用户数据
Mat zh_x;
// 从 userdata 中获取 Mat 对象的引用,并赋值给 zh_x
zh_x = *(Mat*)userdata;
// 根据鼠标事件类型执行不同的操作
switch (EVENT) {
// 鼠标左键按下事件
case EVENT_LBUTTONDOWN:
{
// 更新全局变量 vP.x 和 vP.y 为鼠标点击的坐标
vP.x = x;
vP.y = y;
// 在图像 zh_x 上绘制标记,例如一个圆圈,位置在鼠标点击的坐标
drawMarker(zh_x, vP, Scalar(255, 255, 255));
// 注释掉的代码是另一种绘制圆圈的方式,已被 drawMarker 替代
//circle(zh_x, vP, 4, cvScalar(255, 255, 255), -1);
// 在窗口 wName 中显示更新后的图像
imshow(wName, zh_x);
// 事件处理完毕,退出函数
return;
}
// 结束左键按下事件的处理
break;
}
// 其他事件类型的处理可以继续添加在这里
}
// 请注意,这段代码中有几个问题需要解决:
// 1. 全局变量 vP 需要在使用前声明。
// 2. 变量 wName 需要在使用前声明或定义。
// 3. drawMarker 函数需要在其他地方定义,这
// 绘制直方图的函数
int drawHist(Mat& histMat, float* srcHist, int bin_width, int bin_heght) {
// 使用 create 函数创建一个用于绘制直方图的空白图像
// bin_heght 是直方图的高度,256 * bin_width 是直方图的总宽度
histMat.create(bin_heght, 256 * bin_width, CV_8UC3);
// 将直方图图像初始化为白色背景
histMat = Scalar(255, 255, 255);
// 计算 srcHist 数组中的最大值,用于后续归一化直方图的高
float maxVal = *std::max_element(srcHist, srcHist + 256);
// 遍历 0 到 255,代表不同的灰度级
for (int i = 0; i < 256; i++) {
// 定义一个矩形,用于绘制直方图的一个 bin
Rect binRect;
// 计算 bin 的 x 坐标,每个 bin 的宽度为 bin_width
binRect.x = i * bin_width;
// 根据归一化的最大值计算当前 bin 的高度
float height_i = (float)bin_heght * srcHist[i] / maxVal;
// 将计算出的高度转换为整数
binRect.height = (int)height_i;
// 计算 bin 的 y 坐标,从直方图的底部开始绘制
binRect.y = bin_heght - binRect.height;
// 设置 bin 的宽度
binRect.width = bin_width;
// 在直方图图像上绘制矩形,使用红色,-1 表示填充整个矩形
rectangle(histMat, binRect, CV_RGB(255, 0, 0), -1);
}
// 函数执行成功,返回 0
return 0;
}
//选择视频或者摄像头,1摄像头,0视频。
VideoCapture createInput(bool useCamera, string videoPath)
{
//选择输入
VideoCapture capVideo;
if (useCamera) {
capVideo.open(0);
}
else {
capVideo.open(videoPath);
}
return capVideo;
}
三、运行结果: