当前位置: 首页 > news >正文

在 Qt C++ 中利用 OpenCV 实现视频处理技术详解

前言

在当今的计算机视觉领域,视频处理技术有着广泛的应用,从安防监控到自动驾驶,从视频编辑到人工智能交互,都离不开对视频的有效处理。 OpenCV 作为一个开源的计算机视觉库,包含了大量用于图像处理和视频分析的函数和算法。能够高效地实现各种复杂的视频处理功能。
本文将详细介绍在 Qt C++ 中使用 OpenCV 库实现视频处理技术,包括视频 I/O 类 VideoCapture/VideoWriter(编解码设置)、运动分析中的背景减除(MOG2/KNN)以及实时目标跟踪中的 KCF 算法(核相关滤波)等内容,旨在为初学者和有一定基础的开发者提供全面且易懂的指导。​

一、Qt 与 OpenCV 环境搭建​

在开始进行视频处理之前,我们首先需要搭建好 Qt 与 OpenCV 的开发环境。这是后续所有开发工作的基础,只有环境配置正确,才能顺利地调用相关库函数进行开发。​

1.1 OpenCV 的安装

OpenCV 的安装和配置相对复杂一些,需要将其库文件和头文件正确地集成到 Qt 项目中。​
下载 OpenCV:访问 OpenCV 官方网站(https://opencv.org/),下载适合自己操作系统的 OpenCV 版本。对于 Windows 系统,通常下载.exe 安装文件,运行后会解压出 OpenCV 的库文件和头文件。​

1.2 配置环境变量

将 OpenCV 的 bin 目录添加到系统环境变量中。例如,如果 OpenCV 解压到了 “D:\opencv” 目录,那么需要将 “D:\opencv\build\x64\vc15\bin” 添加到系统的 PATH 环境变量中。这样,在运行程序时,系统才能找到 OpenCV 的动态链接库。​
在 Qt 项目中配置 OpenCV:打开 Qt Creator,创建一个新的 Qt C++ 项目。然后,在项目的.pro 文件中添加以下内容,指定 OpenCV 的头文件和库文件路径:

INCLUDEPATH += D:\opencv\build\include
LIBS += -LD:\opencv\build\x64\vc15\lib \
-lopencv_core455d \
-lopencv_highgui455d \
-lopencv_imgproc455d \
-lopencv_video455d \
-lopencv_videoio455d

其中,“D:\opencv” 是 OpenCV 的安装路径,“455d” 是 OpenCV 的版本号,根据实际情况进行修改。“d” 表示 debug 版本,如果需要发布程序,还需要链接 release 版本的库文件(去掉 “d”)。​
配置完成后,可以编写一个简单的程序测试 OpenCV 是否配置成功。例如,读取一张图片并显示:

#include <opencv2/opencv.hpp>
#include <QApplication>
#include <QLabel>
#include <QImage>int main(int argc, char *argv[])
{QApplication a(argc, argv);// 读取图片cv::Mat image = cv::imread("test.jpg");if (image.empty()) {qDebug() << "无法读取图片";return -1;}// 将OpenCV的Mat格式转换为Qt的QImage格式cv::cvtColor(image, image, cv::COLOR_BGR2RGB);QImage qImage(image.data, image.cols, image.rows, image.step, QImage::Format_RGB888);// 显示图片QLabel label;label.setPixmap(QPixmap::fromImage(qImage));label.show();return a.exec();
}

如果程序能够成功编译并显示图片,说明 OpenCV 配置成功。​

二、视频 I/O 类 VideoCapture/VideoWriter 及编解码设置​

视频的输入输出是视频处理的基础,OpenCV 提供了 VideoCapture 和 VideoWriter 两个类来实现视频的读取和写入功能。同时,编解码设置对于视频的处理和存储也非常重要,不同的编解码器会影响视频的质量、大小和兼容性。​

2.1 VideoCapture 类​

VideoCapture 类用于从视频文件、摄像头或其他视频源中读取视频帧。它支持多种视频格式和设备,使用起来非常灵活。​

2.1.1 打开视频源:

可以通过构造函数或 open () 方法打开视频源。例如:

// 打开视频文件
cv::VideoCapture cap("test.mp4");// 打开摄像头(0表示默认摄像头)
cv::VideoCapture cap(0);

如果打开成功,isOpened () 方法会返回 true;否则,返回 false。​

2.1.2 读取视频帧:

使用 read () 方法或>> 运算符读取视频帧,读取到的视频帧存储在 Mat 对象中。例如:

cv::Mat frame;
cap.read(frame); // 读取一帧视频
// 或者
cap >> frame;

如果读取失败(例如到达视频末尾),read () 方法会返回 false。​

2.1.3 获取视频属性:

可以使用 get () 方法获取视频的各种属性,如帧率、宽度、高度等。例如:

double fps = cap.get(cv::CAP_PROP_FPS); // 获取帧率
int frameWidth = cap.get(cv::CAP_PROP_FRAME_WIDTH); // 获取帧宽度
int frameHeight = cap.get(cv::CAP_PROP_FRAME_HEIGHT); // 获取帧高度
int totalFrames = cap.get(cv::CAP_PROP_FRAME_COUNT); // 获取总帧数
2.1.4 设置视频属性:

使用 set () 方法可以设置视频的一些属性,如当前播放位置等。例如:

cap.set(cv::CAP_PROP_POS_FRAMES, 100); // 设置当前播放位置为第100帧

2.2 VideoWriter 类​

VideoWriter 类用于将处理后的视频帧写入到视频文件中。它需要指定输出视频的文件名、编解码器、帧率、帧大小等参数。​

2.2.1 构造 VideoWriter 对象:

在构造 VideoWriter 对象时,需要指定输出视频的文件名、编解码器、帧率、帧大小等参数。例如:

cv::VideoWriter writer;
int fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G'); // 指定编解码器为MJPG
double fps = 30.0;
cv::Size frameSize(640, 480);
writer.open("output.avi", fourcc, fps, frameSize, true); // true表示彩色视频

其中,fourcc 是一个 4 字节的代码,用于指定视频的编解码器。不同的编解码器有不同的 fourcc 代码,例如:​
MJPG:‘M’, ‘J’, ‘P’, 'G’​
XVID:‘X’, ‘V’, ‘I’, 'D’​
H.264:需要安装相应的编码器,fourcc 代码为 ‘AVC1’ 或 'H264’​

2.2.2 写入视频帧:

使用 write () 方法或 << 运算符将视频帧写入到输出文件中。例如:

cv::Mat frame;
// 处理视频帧...
writer.write(frame); // 写入一帧视频
// 或者
writer << frame;
2.2.3 释放资源:

在完成视频写入后,需要调用 release () 方法释放资源。例如:

writer.release();

2.3 编解码设置​

编解码器是视频处理中非常重要的一部分,它决定了视频的压缩方式和存储格式。不同的编解码器有不同的特点,在选择时需要根据实际需求进行权衡。​

2.3.1 常见的编解码器:​
  • MJPG(Motion JPEG):一种基于 JPEG 的视频编解码器,压缩率较低,但兼容性好,适合存储高质量视频。​
  • XVID:一种基于 MPEG-4 的编解码器,压缩率较高,视频质量较好,广泛应用于各种视频格式。​
  • H.264(AVC):一种高效的视频编解码器,压缩率高,视频质量好,是目前主流的视频编解码标准之一,广泛应用于网络视频、高清电视等领域。​
  • H.265(HEVC):H.264 的升级版,压缩率更高,但解码复杂度也更高,适合存储超高清视频。​
2.3.2 在 OpenCV 中设置编解码器:

如前所述,在构造 VideoWriter 对象时,需要通过 fourcc 代码指定编解码器。如果指定的编解码器不可用,OpenCV 会尝试使用默认的编解码器。在实际开发中,可能需要根据系统中安装的编解码器来选择合适的 fourcc 代码。​

2.3.3 编解码器的安装:

有些编解码器可能需要单独安装,例如 H.264 编解码器。在 Windows 系统中,可以安装 K-Lite Codec Pack 等编解码器包来获得更多的编解码器支持。​

2.4 Qt 中使用 VideoCapture 和 VideoWriter 的示例​

下面是一个在 Qt 中使用 VideoCapture 读取视频并使用 VideoWriter 写入处理后视频的示例程序:

#include <QApplication>
#include <QMainWindow>
#include <QLabel>
#include <opencv2/opencv.hpp>class VideoProcessor : public QMainWindow
{Q_OBJECTpublic:VideoProcessor(QWidget *parent = nullptr) : QMainWindow(parent){// 创建显示标签label = new QLabel(this);setCentralWidget(label);// 打开视频文件cap.open("input.mp4");if (!cap.isOpened()) {qDebug() << "无法打开视频文件";return;}// 获取视频属性double fps = cap.get(cv::CAP_PROP_FPS);int frameWidth = cap.get(cv::CAP_PROP_FRAME_WIDTH);int frameHeight = cap.get(cv::CAP_PROP_FRAME_HEIGHT);// 创建VideoWriter对象int fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');writer.open("output.avi", fourcc, fps, cv::Size(frameWidth, frameHeight), true);// 启动定时器,定时读取视频帧timer = new QTimer(this);connect(timer, &QTimer::timeout, this, &VideoProcessor::processFrame);timer->start(1000 / fps); // 根据帧率设置定时器间隔}~VideoProcessor(){cap.release();writer.release();}private slots:void processFrame(){cv::Mat frame;cap >> frame;if (frame.empty()) {timer->stop();return;}// 对视频帧进行简单处理(例如转为灰度图)cv::Mat grayFrame;cv::cvtColor(frame, grayFrame, cv::COLOR_BGR2GRAY);cv::cvtColor(grayFrame, frame, cv::COLOR_GRAY2BGR); // 转回BGR格式以便写入// 写入处理后的视频帧writer << frame;// 显示视频帧cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB);QImage qImage(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888);label->setPixmap(QPixmap::fromImage(qImage.scaled(label->size(), Qt::KeepAspectRatio)));}private:cv::VideoCapture cap;cv::VideoWriter writer;QLabel *label;QTimer *timer;
};int main(int argc, char *argv[])
{QApplication a(argc, argv);VideoProcessor w;w.show();return a.exec();
}#include "main.moc"

2.5 编解码设置​

编解码器是视频处理中非常重要的一部分,它决定了视频的压缩方式和存储格式。不同的编解码器有不同的特点,在选择时需要根据实际需求进行权衡。​

常见的编解码器:​
  • MJPG(Motion JPEG):一种基于 JPEG 的视频编解码器,压缩率较低,但兼容性好,适合存储高质量视频。​
  • XVID:一种基于 MPEG-4 的编解码器,压缩率较高,视频质量较好,广泛应用于各种视频格式。​
  • H.264(AVC):一种高效的视频编解码器,压缩率高,视频质量好,是目前主流的视频编解码标准之一,广泛应用于网络视频、高清电视等领域。​
  • H.265(HEVC):H.264 的升级版,压缩率更高,但解码复杂度也更高,适合存储超高清视频。​

在 OpenCV 中设置编解码器:如前所述,在构造 VideoWriter 对象时,需要通过 fourcc 代码指定编解码器。如果指定的编解码器不可用,OpenCV 会尝试使用默认的编解码器。在实际开发中,可能需要根据系统中安装的编解码器来选择合适的 fourcc 代码。​

编解码器的安装:有些编解码器可能需要单独安装,例如 H.264 编解码器。在 Windows 系统中,可以安装 K-Lite Codec Pack 等编解码器包来获得更多的编解码器支持。​

2.6 Qt 中使用 VideoCapture 和 VideoWriter 的示例​

下面是一个在 Qt 中使用 VideoCapture 读取视频并使用 VideoWriter 写入处理后视频的示例程序:​

#include <QApplication>
#include <QMainWindow>
#include <QLabel>
#include <opencv2/opencv.hpp>class VideoProcessor : public QMainWindow
{Q_OBJECTpublic:VideoProcessor(QWidget *parent = nullptr) : QMainWindow(parent){// 创建显示标签label = new QLabel(this);setCentralWidget(label);// 打开视频文件cap.open("input.mp4");if (!cap.isOpened()) {qDebug() << "无法打开视频文件";return;}// 获取视频属性double fps = cap.get(cv::CAP_PROP_FPS);int frameWidth = cap.get(cv::CAP_PROP_FRAME_WIDTH);int frameHeight = cap.get(cv::CAP_PROP_FRAME_HEIGHT);// 创建VideoWriter对象int fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');writer.open("output.avi", fourcc, fps, cv::Size(frameWidth, frameHeight), true);// 启动定时器,定时读取视频帧timer = new QTimer(this);connect(timer, &QTimer::timeout, this, &VideoProcessor::processFrame);timer->start(1000 / fps); // 根据帧率设置定时器间隔}~VideoProcessor(){cap.release();writer.release();}private slots:void processFrame(){cv::Mat frame;cap >> frame;if (frame.empty()) {timer->stop();return;}// 对视频帧进行简单处理(例如转为灰度图)cv::Mat grayFrame;cv::cvtColor(frame, grayFrame, cv::COLOR_BGR2GRAY);cv::cvtColor(grayFrame, frame, cv::COLOR_GRAY2BGR); // 转回BGR格式以便写入// 写入处理后的视频帧writer << frame;// 显示视频帧cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB);QImage qImage(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888);label->setPixmap(QPixmap::fromImage(qImage.scaled(label->size(), Qt::KeepAspectRatio)));}private:cv::VideoCapture cap;cv::VideoWriter writer;QLabel *label;QTimer *timer;
};int main(int argc, char *argv[])
{QApplication a(argc, argv);VideoProcessor w;w.show();return a.exec();
}#include "main.moc"


在这个示例中,我们创建了一个 VideoProcessor 类,它继承自 QMainWindow。在构造函数中,我们打开视频文件,获取视频属性,创建 VideoWriter 对象,并启动定时器定时读取和处理视频帧。在 processFrame 槽函数中,我们读取视频帧,将其转为灰度图,然后写入到输出文件中,并在界面上显示处理后的视频帧。​

三、运动分析:背景减除(MOG2/KNN)​

背景减除是一种常用的运动分析技术,它通过从视频序列中减去背景模型,来检测出前景目标(即运动的物体)。OpenCV 提供了多种背景减除算法,其中 MOG2 和 KNN 是两种比较常用的算法。​

3.1 背景减除的基本原理​

背景减除的基本思想是:在视频序列中,背景通常是相对稳定的,而前景目标是运动的。因此,可以通过建立一个背景模型,然后将当前帧与背景模型进行比较,差异部分即为前景目标。​
具体来说,背景减除的步骤如下:​

  • 建立背景模型:通过对视频序列的初始帧或多帧进行分析,建立一个背景模型。背景模型可以是一个单帧图像,也可以是一个统计模型。​
  • 前景检测:将当前帧与背景模型进行比较,计算它们之间的差异。通常使用阈值化的方法来判断哪些像素属于前景,哪些属于背景。​
  • 背景更新:由于背景可能会随着时间的推移而发生缓慢变化(如光照变化、物体移动等),因此需要对背景模型进行动态更新,以适应背景的变化。​

3.2 MOG2 算法​

MOG2(Mixture of Gaussians)算法是一种基于高斯混合模型的背景减除算法。它假设每个像素的颜色值在背景中服从多个高斯分布的混合,通过对这些高斯分布的参数进行估计和更新,来建立背景模型。​
MOG2 算法的特点:​

  • 能够适应背景的动态变化,如光照变化、缓慢移动的背景物体等。​
  • 对噪声有一定的抑制能力。​
  • 可以检测出阴影,并将阴影从前景中分离出来。​

在 OpenCV 中使用 MOG2 算法:OpenCV 提供了 cv::createBackgroundSubtractorMOG2 () 函数来创建 MOG2 背景减除器。例如:

cv::Ptr<cv::BackgroundSubtractor> pMOG2 = cv::createBackgroundSubtractorMOG2();

然后,使用 apply () 方法对当前帧进行前景检测:

cv::Mat frame, fgMask;
cap >> frame;
pMOG2->apply(frame, fgMask);

其中,fgMask 是输出的前景掩码,掩码中前景像素的值为 255,背景像素的值为 0,阴影像素的值为 127(可以通过设置参数来关闭阴影检测)。​

3.3 KNN 算法​

KNN(K-Nearest Neighbors)背景减除算法是一种基于 K 近邻分类的背景减除算法。它将每个像素的历史颜色值作为样本,通过计算当前像素与这些样本的距离,来判断当前像素是属于前景还是背景。​

3.3.1 KNN 算法的特点:​
  • 对复杂背景的适应能力较强。​
  • 能够较好地处理光照变化和动态背景。​
  • 前景检测的精度较高。​
3.3.2 在 OpenCV 中使用 KNN 算法:

OpenCV 提供了 cv::createBackgroundSubtractorKNN () 函数来创建 KNN 背景减除器。例如:

cv::Ptr<cv::BackgroundSubtractor> pKNN = cv::createBackgroundSubtractorKNN();

同样,使用 apply () 方法进行前景检测:

cv::Mat frame, fgMask;
cap >> frame;
pKNN->apply(frame, fgMask);

KNN 算法的前景掩码与 MOG2 类似,前景像素为 255,背景像素为 0,阴影像素为 127。​

3.4 Qt 中使用背景减除算法的示例​

下面是一个在 Qt 中使用 MOG2 和 KNN 算法进行背景减除的示例程序:

#include <QApplication>
#include <QMainWindow>
#include <QTabWidget>
#include <QLabel>
#include <opencv2/opencv.hpp>class BackgroundSubtractorDemo : public QMainWindow
{Q_OBJECTpublic:BackgroundSubtractorDemo(QWidget *parent = nullptr) : QMainWindow(parent){// 创建标签页控件QTabWidget *tabWidget = new QTabWidget(this);setCentralWidget(tabWidget);// 创建显示标签originalLabel = new QLabel(tabWidget);mog2Label = new QLabel(tabWidget);knnLabel = new QLabel(tabWidget);tabWidget->addTab(originalLabel, "原始视频");tabWidget->addTab(mog2Label, "MOG2前景");tabWidget->addTab(knnLabel, "KNN前景");// 打开摄像头cap.open(0);if (!cap.isOpened()) {qDebug() << "无法打开摄像头";return;}// 创建背景减除器pMOG2 = cv::createBackgroundSubtractorMOG2();pKNN = cv::createBackgroundSubtractorKNN();// 启动定时器timer = new QTimer(this);connect(timer, &QTimer::timeout, this, &BackgroundSubtractorDemo::processFrame);timer->start(30); // 大约33fps}~BackgroundSubtractorDemo(){cap.release();}private slots:void processFrame(){cv::Mat frame;cap >> frame;if (frame.empty()) {timer->stop();return;}// 显示原始视频cv::Mat originalFrame;cv::cvtColor(frame, originalFrame, cv::COLOR_BGR2RGB);QImage originalQImage(originalFrame.data, originalFrame.cols, originalFrame.rows, originalFrame.step, QImage::Format_RGB888);originalLabel->setPixmap(QPixmap::fromImage(originalQImage.scaled(originalLabel->size(), Qt::KeepAspectRatio)));// MOG2背景减除cv::Mat mog2FgMask;pMOG2->apply(frame, mog2FgMask);// 对前景掩码进行后处理(如腐蚀和膨胀,去除噪声)cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3));cv::erode(mog2FgMask, mog2FgMask, kernel);cv::dilate(mog2FgMask, mog2FgMask, kernel);// 显示前景掩码QImage mog2QImage(mog2FgMask.data, mog2FgMask.cols, mog2FgMask.rows, mog2FgMask.step, QImage::Format_Grayscale8);mog2Label->setPixmap(QPixmap::fromImage(mog2QImage.scaled(mog2Label->size(), Qt::KeepAspectRatio)));// KNN背景减除cv::Mat knnFgMask;pKNN->apply(frame, knnFgMask);// 后处理cv::erode(knnFgMask, knnFgMask, kernel);cv::dilate(knnFgMask, knnFgMask, kernel);// 显示前景掩码QImage knnQImage(knnFgMask.data, knnFgMask.cols, knnFgMask.rows, knnFgMask.step, QImage::Format_Grayscale8);knnLabel->setPixmap(QPixmap::fromImage(knnQImage.scaled(knnLabel->size(), Qt::KeepAspectRatio)));}private:cv::VideoCapture cap;cv::Ptr<cv::BackgroundSubtractor> pMOG2;cv::Ptr<cv::BackgroundSubtractor> pKNN;QLabel *originalLabel;QLabel *mog2Label;QLabel *knnLabel;QTimer *timer;
};int main(int argc, char *argv[])
{QApplication a(argc, argv);BackgroundSubtractorDemo w;w.show();return a.exec();
}#include "main.moc"

在这个示例中,我们创建了一个 BackgroundSubtractorDemo 类,它使用两个背景减除器(MOG2 和 KNN)对摄像头捕获的视频进行前景检测。程序界面使用 QTabWidget 分为三个标签页,分别显示原始视频、MOG2 算法检测到的前景和 KNN 算法检测到的前景。为了去除前景掩码中的噪声,我们对掩码进行了腐蚀和膨胀的后处理操作。​

四、实时目标跟踪:KCF 算法(核相关滤波)​

实时目标跟踪是计算机视觉中的一个重要研究方向,它旨在在视频序列中实时地跟踪指定的目标。KCF(Kernelized Correlation Filters)算法是一种高效的实时目标跟踪算法,它基于相关滤波和核方法,具有跟踪速度快、精度高的特点。​

4.1 KCF 算法的基本原理​

KCF 算法的核心思想是利用相关滤波来学习一个目标的外观模型,然后在后续帧中通过计算与该模型的相关性来找到目标的位置。​

  • 相关滤波:相关滤波是一种基于模板匹配的方法,它通过计算目标模板与图像区域的相关性来确定目标的位置。在频域中,相关性可以通过快速傅里叶变换(FFT)来高效计算,从而提高跟踪速度。​
  • 核方法:KCF 算法引入了核方法,将线性相关滤波扩展到非线性情况。通过核函数,可以将低维特征映射到高维特征空间,从而更好地处理目标的非线性变化。​
  • 循环移位:KCF 算法利用循环移位生成大量的训练样本,这些样本可以通过快速傅里叶变换高效地进行处理,从而提高模型的学习效率和跟踪精度。​

4.2 KCF 算法的特点​

  • 实时性好:KCF 算法在频域中进行计算,利用了 FFT 的高效性,使得跟踪速度可以达到每秒数百帧,能够满足实时跟踪的需求。​
  • 跟踪精度高:通过核方法和相关滤波的结合,KCF 算法能够较好地处理目标的尺度变化、旋转和部分遮挡等情况。​
  • 计算量小:相比其他复杂的跟踪算法,KCF 算法的计算量较小,适合在嵌入式设备等资源受限的平台上运行。​

4.3 在 OpenCV 中使用 KCF 算法​

OpenCV 的 contrib 模块中提供了 KCF 跟踪算法的实现,我们可以通过 cv::TrackerKCF::create () 函数来创建 KCF 跟踪器。​

4.3.1 初始化跟踪器:

首先需要在第一帧中指定目标的初始位置,然后初始化跟踪器。例如:

cv::Mat frame;
cap >> frame;
cv::Rect2d bbox(100, 100, 200, 200); // 目标初始位置(x, y, width, height)
cv::Ptr<cv::Tracker> tracker = cv::TrackerKCF::create();
tracker->init(frame, bbox);
4.3.2 更新跟踪器:

在后续帧中,使用 update () 方法更新跟踪器,获取目标的新位置。例如:

cap >> frame;
bool ok = tracker->update(frame, bbox);
if (ok) {// 目标跟踪成功,绘制目标框cv::rectangle(frame, bbox, cv::Scalar(255, 0, 0), 2, 1);
} else {// 目标跟踪失败cv::putText(frame, "Tracking failure detected", cv::Point(100, 80), cv::FONT_HERSHEY_SIMPLEX, 0.75, cv::Scalar(0, 0, 255), 2);
}

4.4 Qt 中使用 KCF 算法进行实时目标跟踪的示例​

下面是一个在 Qt 中使用 KCF 算法进行实时目标跟踪的示例程序:

#include <QApplication>
#include <QMainWindow>
#include <QLabel>
#include <QMouseEvent>
#include <opencv2/opencv.hpp>
#include <opencv2/tracking.hpp>class KCFTrackerDemo : public QMainWindow
{Q_OBJECTpublic:KCFTrackerDemo(QWidget *parent = nullptr) : QMainWindow(parent), isSelecting(false){// 创建显示标签label = new QLabel(this);setCentralWidget(label);// 打开摄像头cap.open(0);if (!cap.isOpened()) {qDebug() << "无法打开摄像头";return;}// 创建KCF跟踪器tracker = cv::TrackerKCF::create();// 启动定时器timer = new QTimer(this);connect(timer, &QTimer::timeout, this, &KCFTrackerDemo::processFrame);timer->start(30);}~KCFTrackerDemo(){cap.release();}protected:void mousePressEvent(QMouseEvent *event) override{if (event->button() == Qt::LeftButton) {// 开始选择目标isSelecting = true;startPoint = event->pos();}}void mouseReleaseEvent(QMouseEvent *event) override{if (event->button() == Qt::LeftButton && isSelecting) {// 结束选择目标isSelecting = false;endPoint = event->pos();// 计算目标框在图像中的位置QRect qRect = QRect(startPoint, endPoint).normalized();cv::Rect2d bbox(qRect.x(), qRect.y(), qRect.width(), qRect.height());// 初始化跟踪器if (!frame.empty()) {tracker->init(frame, bbox);currentBbox = bbox;isTracking = true;}}}void mouseMoveEvent(QMouseEvent *event) override{if (isSelecting) {// 更新选择框endPoint = event->pos();}}private slots:void processFrame(){cap >> frame;if (frame.empty()) {timer->stop();return;}// 如果正在跟踪,更新跟踪器if (isTracking) {bool ok = tracker->update(frame, currentBbox);if (ok) {// 绘制目标框cv::rectangle(frame, currentBbox, cv::Scalar(255, 0, 0), 2, 1);} else {// 跟踪失败cv::putText(frame, "Tracking failure", cv::Point(50, 50), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);isTracking = false;}}// 如果正在选择目标,绘制选择框if (isSelecting) {QRect qRect = QRect(startPoint, endPoint).normalized();cv::rectangle(frame, cv::Rect(qRect.x(), qRect.y(), qRect.width(), qRect.height()), cv::Scalar(0, 255, 0), 2, 1);}// 显示视频帧cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB);QImage qImage(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888);label->setPixmap(QPixmap::fromImage(qImage.scaled(label->size(), Qt::KeepAspectRatio)));}private:cv::VideoCapture cap;cv::Mat frame;cv::Ptr<cv::Tracker> tracker;cv::Rect2d currentBbox;bool isTracking = false;QLabel *label;QTimer *timer;// 目标选择相关变量bool isSelecting;QPoint startPoint;QPoint endPoint;
};int main(int argc, char *argv[])
{QApplication a(argc, argv);KCFTrackerDemo w;w.show();return a.exec();
}#include "main.moc"

在这个示例中,我们创建了一个 KCFTrackerDemo 类,它使用 KCF 算法对摄像头捕获的视频进行实时目标跟踪。用户可以通过鼠标拖动来选择要跟踪的目标,然后跟踪器会自动在后续帧中跟踪该目标。如果跟踪失败,程序会显示 “Tracking failure” 信息。​

五、综合应用案例​

为了更好地理解和运用前面介绍的视频处理技术,我们可以将它们结合起来,实现一个综合的视频处理应用。例如,一个基于 Qt 和 OpenCV 的视频监控系统,该系统能够实现视频的采集、显示、运动目标检测和跟踪等功能。​

5.1 功能设计​

  • 视频采集:从摄像头或视频文件中采集视频帧。​
  • 视频显示:在 Qt 界面上实时显示采集到的视频。​
  • 运动目标检测:使用背景减除算法(如 MOG2 或 KNN)检测视频中的运动目标。​
  • 目标跟踪:对检测到的运动目标使用 KCF 算法进行实时跟踪。​
  • 视频录制:将处理后的视频(包含运动目标检测和跟踪结果)录制到文件中。​

5.2 实现代码

#include <QApplication>
#include <QMainWindow>
#include <QTabWidget>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QWidget>
#include <opencv2/opencv.hpp>
#include <opencv2/tracking.hpp>
#include <vector>class VideoMonitoringSystem : public QMainWindow
{Q_OBJECTpublic:VideoMonitoringSystem(QWidget *parent = nullptr) : QMainWindow(parent){// 创建主窗口部件QWidget *centralWidget = new QWidget(this);setCentralWidget(centralWidget);// 创建布局QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);QHBoxLayout *buttonLayout = new QHBoxLayout();// 创建按钮startBtn = new QPushButton("开始");stopBtn = new QPushButton("停止");recordBtn = new QPushButton("录制");buttonLayout->addWidget(startBtn);buttonLayout->addWidget(stopBtn);buttonLayout->addWidget(recordBtn);// 创建标签页控件QTabWidget *tabWidget = new QTabWidget(this);originalLabel = new QLabel(tabWidget);fgLabel = new QLabel(tabWidget);trackingLabel = new QLabel(tabWidget);tabWidget->addTab(originalLabel, "原始视频");tabWidget->addTab(fgLabel, "运动目标");tabWidget->addTab(trackingLabel, "目标跟踪");mainLayout->addLayout(buttonLayout);mainLayout->addWidget(tabWidget);// 初始化变量isRunning = false;isRecording = false;// 打开摄像头cap.open(0);if (!cap.isOpened()) {qDebug() << "无法打开摄像头";return;}// 创建背景减除器和跟踪器pMOG2 = cv::createBackgroundSubtractorMOG2();tracker = cv::TrackerKCF::create();// 连接信号和槽connect(startBtn, &QPushButton::clicked, this, &VideoMonitoringSystem::startProcessing);connect(stopBtn, &QPushButton::clicked, this, &VideoMonitoringSystem::stopProcessing);connect(recordBtn, &QPushButton::clicked, this, &VideoMonitoringSystem::toggleRecording);// 启动定时器timer = new QTimer(this);connect(timer, &QTimer::timeout, this, &VideoMonitoringSystem::processFrame);}~VideoMonitoringSystem(){cap.release();if (isRecording) {writer.release();}}private slots:void startProcessing(){if (!isRunning) {isRunning = true;timer->start(30);}}void stopProcessing(){if (isRunning) {isRunning = false;timer->stop();trackers.clear();bboxes.clear();}}void toggleRecording(){if (isRecording) {// 停止录制isRecording = false;writer.release();recordBtn->setText("录制");} else {// 开始录制if (!frame.empty()) {int fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');double fps = 30.0;cv::Size frameSize(frame.cols, frame.rows);writer.open("monitoring.avi", fourcc, fps, frameSize, true);if (writer.isOpened()) {isRecording = true;recordBtn->setText("停止录制");}}}}void processFrame(){cap >> frame;if (frame.empty()) {stopProcessing();return;}cv::Mat originalFrame = frame.clone();cv::Mat fgMask;// 运动目标检测pMOG2->apply(frame, fgMask);// 后处理cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5, 5));cv::erode(fgMask, fgMask, kernel);cv::dilate(fgMask, fgMask, kernel);// 查找轮廓,获取运动目标std::vector<std::vector<cv::Point>> contours;cv::findContours(fgMask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);std::vector<cv::Rect2d> newBboxes;for (const auto &contour : contours) {// 过滤小轮廓if (cv::contourArea(contour) < 500) {continue;}// 获取目标边界框cv::Rect2d bbox = cv::boundingRect(contour);newBboxes.push_back(bbox);}// 更新跟踪器std::vector<cv::Ptr<cv::Tracker>> newTrackers;std::vector<cv::Rect2d> trackedBboxes;for (const auto &newBbox : newBboxes) {bool matched = false;// 与现有跟踪器匹配for (size_t i = 0; i < trackers.size(); ++i) {cv::Rect2d trackedBbox;if (trackers[i]->update(frame, trackedBbox)) {// 计算IOU(交并比)double iou = calculateIOU(newBbox, trackedBbox);if (iou > 0.3) { // IOU阈值// 匹配成功,更新跟踪器trackers[i]->init(frame, newBbox);newTrackers.push_back(trackers[i]);trackedBboxes.push_back(newBbox);matched = true;break;}}}if (!matched) {// 未匹配,创建新的跟踪器cv::Ptr<cv::Tracker> newTracker = cv::TrackerKCF::create();newTracker->init(frame, newBbox);newTrackers.push_back(newTracker);trackedBboxes.push_back(newBbox);}}trackers = newTrackers;bboxes = trackedBboxes;// 绘制跟踪框cv::Mat trackingFrame = originalFrame.clone();for (const auto &bbox : bboxes) {cv::rectangle(trackingFrame, bbox, cv::Scalar(0, 255, 0), 2, 1);}// 录制视频if (isRecording) {writer << trackingFrame;}// 显示视频showImage(originalFrame, originalLabel);showImage(fgMask, fgLabel);showImage(trackingFrame, trackingLabel);}private:// 计算两个边界框的交并比double calculateIOU(const cv::Rect2d &bbox1, const cv::Rect2d &bbox2){double x1 = std::max(bbox1.x, bbox2.x);double y1 = std::max(bbox1.y, bbox2.y);double x2 = std::min(bbox1.x + bbox1.width, bbox2.x + bbox2.width);double y2 = std::min(bbox1.y + bbox1.height, bbox2.y + bbox2.height);double intersectionArea = std::max(0.0, x2 - x1) * std::max(0.0, y2 - y1);double area1 = bbox1.width * bbox1.height;double area2 = bbox2.width * bbox2.height;double unionArea = area1 + area2 - intersectionArea;return unionArea > 0 ? intersectionArea / unionArea : 0;}// 显示图像void showImage(const cv::Mat &image, QLabel *label){cv::Mat displayImage;if (image.channels() == 1) {cv::cvtColor(image, displayImage, cv::COLOR_GRAY2RGB);} else {cv::cvtColor(image, displayImage, cv::COLOR_BGR2RGB);}QImage qImage(displayImage.data, displayImage.cols, displayImage.rows, displayImage.step, QImage::Format_RGB888);label->setPixmap(QPixmap::fromImage(qImage.scaled(label->size(), Qt::KeepAspectRatio)));}cv::VideoCapture cap;cv::VideoWriter writer;cv::Mat frame;cv::Ptr<cv::BackgroundSubtractor> pMOG2;std::vector<cv::Ptr<cv::Tracker>> trackers;std::vector<cv::Rect2d> bboxes;QLabel *originalLabel;QLabel *fgLabel;QLabel *trackingLabel;QPushButton *startBtn;QPushButton *stopBtn;QPushButton *recordBtn;QTimer *timer;bool isRunning;bool isRecording;
};int main(int argc, char *argv[])
{QApplication a(argc, argv);VideoMonitoringSystem w;w.show();return a.exec();
}#include "main.moc"

在这个综合应用案例中,我们实现了一个视频监控系统。该系统通过摄像头采集视频,使用 MOG2 算法检测运动目标,然后对每个运动目标创建 KCF 跟踪器进行跟踪。用户可以通过按钮控制系统的开始、停止和录制功能。系统界面分为三个标签页,分别显示原始视频、运动目标检测结果和目标跟踪结果。​

六、总结

本文详细介绍了在 Qt C++ 中使用 OpenCV 库实现视频处理技术的相关内容,包括 Qt 与 OpenCV 环境搭建、视频 I/O 类 VideoCapture/VideoWriter 及编解码设置、运动分析中的背景减除(MOG2/KNN)、实时目标跟踪中的 KCF 算法以及一个综合应用案例。通过这些内容的学习,了解基本的视频处理技术,并能够将它们应用到实际的项目开发中。​
同时,在实际开发中,还需要注意视频处理的实时性、稳定性和兼容性等问题。根据不同的应用场景和需求,选择合适的算法和技术,进行优化和改进,以提高系统的性能和用户体验。​

http://www.dtcms.com/a/339054.html

相关文章:

  • 尝试Claude Code的安装
  • 学习笔记分享——基于STM32的平衡车项目
  • Mac调试ios的safari浏览器打开的页面
  • 电子电气架构 --- 软件项目成本估算
  • 技术攻坚全链铸盾 锁定12月济南第26届食品农产品安全高峰论坛
  • 任务十二 我的页面及添加歌曲功能开发
  • Typescript入门-对象讲解
  • Python量化交易:结合爬虫与TA-Lib技术指标分析
  • Matplotlib数据可视化实战:Matplotlib子图布局与管理入门
  • Ansible 角色管理指南
  • Pandas数据处理与分析实战:Pandas数据处理与Matplotlib可视化入门
  • 0819 使用IP多路复用实现TCP并发服务器
  • Tomcat 的核心脚本catalina.sh 和 startup.sh的关系
  • 陪诊小程序系统开发:开启智慧就医新时代
  • CNN 在故障诊断中的应用:原理、案例与优势
  • BEV:隐式相机视角转换-----BEVFormer
  • 简单实现监听redis的Key过期事件
  • Shopee本土店账号安全运营:规避封禁风险的多维策略
  • 微服务-08.微服务拆分-拆分商品服务
  • 什么是强化学习
  • JMeter高级性能测试训练营 – 从入门到企业级实战
  • pytest高级用法之插件开发
  • Quartus Prime 18.1网盘资源下载与安装指南
  • 从线性回归到神经网络到自注意力机制 —— 激活函数与参数的演进
  • Berry Material React TypeScript 管理后台使用教程 v0.1.0
  • 手写C++ string类实现详解
  • React 新拟态登录页面使用教程
  • 星图云开发者平台新功能速递 | 微服务管理器:无缝整合异构服务,释放云原生开发潜能
  • C++入门自学Day14-- Stack和Queue的自实现(适配器)
  • [Android] 显示的内容被导航栏这挡住