生产者消费者模式最佳实践

测试环境:ubuntu18.04+opencv4.2+Qt

        一个生产者-消费者模式下的视频处理框架。基础结构:视频读取类线程不断读取视频帧,处理类线程对图像进行处理,之后通过信号与槽机制在主线程中显示。特点:视频读取、处理为独立线程,主线程中只做显示调度。

应用一、显示主线程等待处理线程处理完后显示图像。

首先需要定义全局变量:

g_base.h文件:

#ifndef G_BASE_H
#define G_BASE_H

#include <QMutex>
#include <QWaitCondition>
#include <opencv2/opencv.hpp>

extern cv::Mat g_img; // 全局图像
extern int g_fNum; // 全局图像帧计数器

extern QMutex g_m_reader;
extern QWaitCondition g_wc_reader;

extern QMutex g_m_proc;
extern QWaitCondition g_wc_proc;

#endif // G_BASE_H

g_base.cpp文件:

#include g_base.h

cv::Mat g_img; // 全局图像
int g_fNum = 0; // 全局图像帧计数器

QMutex g_m_reader;
QWaitCondition g_wc_reader;

QMutex g_m_proc;
QWaitCondition g_wc_proc;

        .cpp中定义全局变量并初始化,.h中做导出。其它文件引用时,只需加载头文件,而不必担心重复引用问题。

qreader.cpp读取类关键代码:

while(true)
{
    g_m_reader.lock(); // 加锁,保护下面代码
    
    qDebug()<< "QReader run(), tid :" << QThread::currentThreadId();
    cap.read(g_img);
    if (g_img.empty())
    {
        qDebug() << "===> image empty.";
        continue;
    }
    
    g_m_reader.unlock(); // 解锁
    g_wc_reader.wakeAll(); // g_wc_reader信号量唤醒
}

qprocess.cpp视频处理类关键代码:

while(true)
{
    g_m_proc.lock(); // 加锁,保护下面代码
    g_wc_reader.wait(&g_m_proc); // 等待视频解码信号量g_wc_reader唤醒

    qDebug()<< "QProcess run(), tid :" << QThread::currentThreadId();
    msleep(60);
    qDebug()<< "process nFrame =" << nFrame;
    emit processed(g_img, nFrame); // 发送信号。此处未做线程同步,需深拷贝数据

    g_m_proc.unlock(); // 解锁
    g_wc_proc.wakeAll();  // g_wc_proc信号量唤醒,本示例未使用
}

manager.cpp管理类关键代码:

// 信号与槽关系,线程开启初始化
QManager::QManager(const std::string& url):m_reader(url)
{
    connect(&m_timer, SIGNAL(timeout()), this, SLOT(onTimeOut()));
    connect(&m_proc, SIGNAL(processed(cv::Mat &, int)), this, SLOT(onImageProcessed(cv::Mat &, int)));

    m_t1 = new QThread();
    m_reader.moveToThread(m_t1);
    QObject::connect(m_t1, &QThread::started, &m_reader, &QReader::run);

    m_t2 = new QThread();
    m_proc.moveToThread(m_t2);
    QObject::connect(m_t2, &QThread::started, &m_proc, &QProcess::run);

    m_t1->start();
    m_t2->start();
}

// GUI线程显示槽函数
void QManager::onImageProcessed(cv::Mat &img, int ret)
{
    double t = (double)cv::getTickCount();
    qDebug()<< "QManager onImageProcessed(), tid :" << QThread::currentThreadId();
    int nFrame = ret;
    cv::Mat frame = img;
    cv::putText(frame, frame  + std::to_string(nFrame), cv::Point(int(200), int(100)),
                cv::FONT_HERSHEY_PLAIN, 3, cv::Scalar(255, 0, 0), 2, 8, 0);
    cv::imshow(video, frame);
    t = ((double)cv::getTickCount() - t) * 1000 / cv::getTickFrequency();
    qDebug() << "nFrame =" << nFrame << ", Show time for a frame :" << t << "ms.";
}

结果:

 应用二、视频读取类等待处理类处理完毕后再读取下一帧。

qreader.cpp读取类改动部分:

bool is_start = false;
while(true)
{
    g_m_reader.lock(); // 加锁,保护下面代码
    if (is_start) // 第一次读取,开启循环,之后等待处理结果后才解码。
        g_wc_proc.wait(&g_m_reader);
     else
        is_start = true;
    ...

应用三、固定帧率显示图像结果。

        将主线程显示改为Timer定时拿取结果显示。

manager.h头文件定义:

#include <QTimer>
... 

QTimer m_timer; // 定时器

manager.cpp添加: 

QManager::QManager(const std::string& url):m_reader(url)
{
    connect(&m_timer, SIGNAL(timeout()), this, SLOT(onTimeOut()));
 	 ...
    m_timer.start(40); // 定时器40ms读取一次数据
}

// 定时器到时处理槽函数
void QManager::onTimeOut()
{
    double t = (double)cv::getTickCount();
    qDebug()<< "QManager onTimeOut(), tid :" << QThread::currentThreadId();
    int nFrame = g_fNum;
    cv::Mat frame = g_img;
    cv::putText(frame, frame  + std::to_string(nFrame), cv::Point(int(200), int(100)),
                cv::FONT_HERSHEY_PLAIN, 3, cv::Scalar(255, 0, 0), 2, 8, 0);
    cv::imshow(video, frame);
    t = ((double)cv::getTickCount() - t) * 1000 / cv::getTickFrequency();
    qDebug() << "nFrame =" << nFrame << ", Show time for a frame :" << t << "ms.";
}

 传送门qt多线程系列文章目录 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jingbo1801

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值