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

发布于:2025-08-20 ⋅ 阅读:(15) ⋅ 点赞:(0)

前言

在当今的计算机视觉领域,视频处理技术有着广泛的应用,从安防监控到自动驾驶,从视频编辑到人工智能交互,都离不开对视频的有效处理。 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_OBJECT

public:
    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_OBJECT

public:
    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_OBJECT

public:
    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_OBJECT

public:
    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_OBJECT

public:
    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 算法以及一个综合应用案例。通过这些内容的学习,了解基本的视频处理技术,并能够将它们应用到实际的项目开发中。​
同时,在实际开发中,还需要注意视频处理的实时性、稳定性和兼容性等问题。根据不同的应用场景和需求,选择合适的算法和技术,进行优化和改进,以提高系统的性能和用户体验。​


网站公告

今日签到

点亮在社区的每一天
去签到