Qt与OpenCV编程:在子线程打开摄像头用主线程显示

10 篇文章 2 订阅

前言

1.在做图像处理开发中,比例做目标跟踪识别的时候,用OpenCV一直在处理摄像头传入的数据,有时候会出现界面卡死或者未响应的状态,这是因为事件循环一直等待处理函数的返回而导致阻塞事件循环,这样一来GUI线程所有的绘制和交互都被阻塞在事件队列中,无法执行重绘等事件,整个程序就失去响应了。
2.在这种状态下,为了保证程序的正常运行,最好的方法是把费时的数据处理函数放到别一个线程,处理完成之后再把结果返回给主线程。
在Qt界面中,主线程只要做界面的相关绘制就可以了。
3.在Qt里面,开多线程有几种方法,其中一种是继承QThread类之后重写run函数,还有一种是把继承于QObject的类转移到一个Thread里,后面这种是Qt官方在Qt4.8之后推荐的用法。
4.我这里使用QThread打开一个摄像头,之后用信号把每一帧图像传回主线程显示。

代码

CameraThread.h

#ifndef CAMERATHREAD_H
#define CAMERATHREAD_H
#include <QThread>
#include <QDebug>
#include <vector>
#include <QString>
#include <opencv2/opencv.hpp>

class CameraThread : public QThread
{
    Q_OBJECT

public:
    void stop();
    explicit CameraThread(QObject *parent = 0);

    cv::VideoCapture cv_cap;
	int camera_index;
    cv::Mat cv_src;

protected:
    void run();

private:
    volatile bool stopped;

signals:
    void getImage(const cv::Mat&);

public slots:

};

#endif // CAMERATHREAD_H

CameraThread.cpp

#include "CameraThread.h"

CameraThread::CameraThread(QObject *parent) :
    QThread(parent)
{
    stopped = false;
}

void CameraThread::run()
{
	qDebug() << "Current thread:" << QThread::currentThreadId();
	if (!cv_cap.isOpened())
	{
		cv_cap.open(0);
	}

    while (!stopped)
    {
        cv_cap >> cv_src;

        if(!cv_src.data)
        {
            continue;
        }

        emit getImage(cv_src);
        cv_src.release();
    }
    cv_cap.release();
}

void CameraThread::stop()
{
    stopped = true;   
}

调用代码:
Camera.cpp

#include "Camera.h"

Camera::Camera(QWidget *parent)
	: QMainWindow(parent)
{
	ui.setupUi(this);
	scene = new QGraphicsScene;
	//不是QT的类型要注册信号
	qRegisterMetaType<cv::Mat>("cv::Mat");
	connect(ui.actionCamera, SIGNAL(triggered()), this, SLOT(openCamera()));
	connect(ui.actionClose, SIGNAL(triggered()), this, SLOT(closeCamera()));
}

void Camera::openCamera()
{
	thread = new CameraThread();
	connect(thread, SIGNAL(getImage(cv::Mat)), this, SLOT(getImage(cv::Mat)));
	thread->start();
}

void Camera::closeCamera()
{
	if (thread->isRunning())
	{
		thread->stop();      
		thread->destroyed();
	}
	ui.DisplayLabel->close();
}

void Camera::getImage(cv::Mat image)
{
	qt_image = MatImageToQimage(image);
	qt_pixmap = QPixmap::fromImage(qt_image);
	ui.DisplayLabel->setPixmap(qt_pixmap);
	displayImage(ui.DisplayLabel, qt_pixmap);
	ui.DisplayLabel->show();
}


//显示图片到label窗口
void Camera::displayImage(QLabel *label, QPixmap &pixmap)
{
	//对齐方式,水平与垂直
	label->setAlignment(Qt::AlignLeft);

	//图像自适应窗口大小
	QSize imageSize = pixmap.size();
	QSize labelSize = label->size();

	double widthRatio = 1.0*imageSize.width() / labelSize.width();
	double heightRatio = 1.0*imageSize.height() / labelSize.height();

	if (widthRatio > heightRatio)
	{
		pixmap = pixmap.scaledToWidth(labelSize.width());
	}
	else
	{
		pixmap = pixmap.scaledToHeight(labelSize.height());
	}
	//这个设置是整个图片跟着窗口改变,铺满
	label->setScaledContents(true);

	//label->resize(QSize(pixmap.width(),pixmap.height()));
	label->setPixmap(pixmap);
}
//Mat转成QImage
QImage Camera::MatImageToQimage(const cv::Mat &src)
{
	//CV_8UC1 8位无符号的单通道---灰度图片
	if (src.type() == CV_8UC1)
	{
		//使用给定的大小和格式构造图像
		//QImage(int width, int height, Format format)
		QImage qImage(src.cols, src.rows, QImage::Format_Indexed8);
		//扩展颜色表的颜色数目
		qImage.setColorCount(256);

		//在给定的索引设置颜色
		for (int i = 0; i < 256; i++)
		{
			//得到一个黑白图
			qImage.setColor(i, qRgb(i, i, i));
		}
		//复制输入图像,data数据段的首地址
		uchar *pSrc = src.data;
		//
		for (int row = 0; row < src.rows; row++)
		{
			//遍历像素指针
			uchar *pDest = qImage.scanLine(row);
			//从源src所指的内存地址的起始位置开始拷贝n个
			//字节到目标dest所指的内存地址的起始位置中
			memcmp(pDest, pSrc, src.cols);
			//图像层像素地址
			pSrc += src.step;
		}
		return qImage;
	}
	//为3通道的彩色图片
	else if (src.type() == CV_8UC3)
	{
		//得到图像的的首地址
		const uchar *pSrc = (const uchar*)src.data;
		//以src构造图片
		QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_RGB888);
		//在不改变实际图像数据的条件下,交换红蓝通道
		return qImage.rgbSwapped();
	}
	//四通道图片,带Alpha通道的RGB彩色图像
	else if (src.type() == CV_8UC4)
	{
		const uchar *pSrc = (const uchar*)src.data;
		QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_ARGB32);
		//返回图像的子区域作为一个新图像
		return qImage.copy();
	}
	else
	{
		return QImage();
	}
}
好的,这里给您提供一个 OpenCVQt线程池示例,主线程采集摄像头视频,两个子线程分别显示图像和保存图像: ```cpp #include <QCoreApplication> #include <QThread> #include <QDebug> #include <opencv2/opencv.hpp> using namespace cv; class DisplayThread : public QThread { Q_OBJECT public: DisplayThread(QObject *parent = nullptr) : QThread(parent) {} signals: void imageReady(QImage image); protected: void run() override { VideoCapture cap(0); if (!cap.isOpened()) { qDebug() << "Cannot open camera"; return; } while (true) { Mat frame; cap >> frame; if (frame.empty()) { qDebug() << "Capture frame failed"; break; } QImage image = QImage(frame.data, frame.cols, frame.rows, QImage::Format_RGB888).rgbSwapped(); emit imageReady(image); } } }; class SaveThread : public QThread { Q_OBJECT public: SaveThread(QObject *parent = nullptr) : QThread(parent) {} void setSavePath(const QString &path) { m_savePath = path; } signals: void saveDone(const QString &path); protected: void run() override { VideoCapture cap(0); if (!cap.isOpened()) { qDebug() << "Cannot open camera"; return; } int i = 0; while (true) { Mat frame; cap >> frame; if (frame.empty()) { qDebug() << "Capture frame failed"; break; } QString fileName = QString("%1/frame_%2.jpg").arg(m_savePath).arg(i++); imwrite(fileName.toStdString(), frame); emit saveDone(fileName); } } private: QString m_savePath; }; class VideoPlayer : public QObject { Q_OBJECT public: VideoPlayer(QObject *parent = nullptr) : QObject(parent) { m_displayThread = new DisplayThread(this); m_saveThread = new SaveThread(this); connect(m_displayThread, &DisplayThread::imageReady, this, &VideoPlayer::onImageReady); connect(m_saveThread, &SaveThread::saveDone, this, &VideoPlayer::onSaveDone); m_displayThread->start(); m_saveThread->start(); } ~VideoPlayer() { m_displayThread->quit(); m_displayThread->wait(); m_saveThread->quit(); m_saveThread->wait(); } signals: void imageReady(QImage image); void saveDone(const QString &path); private slots: void onImageReady(QImage image) { emit imageReady(image); } void onSaveDone(const QString &path) { emit saveDone(path); } private: DisplayThread *m_displayThread; SaveThread *m_saveThread; }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); VideoPlayer player; QObject::connect(&player, &VideoPlayer::imageReady, [](QImage image){ // 显示图像 }); QObject::connect(&player, &VideoPlayer::saveDone, [](const QString &path){ // 保存图像 }); return a.exec(); } #include "main.moc" ``` 在这个示例,我们创建了一个 `VideoPlayer` 类,它包含了一个 `DisplayThread` 和一个 `SaveThread` 线程。`DisplayThread` 用于采集摄像头视频,并将每一帧图像通过信号 `imageReady` 发送给主线程,主线程再将图像显示出来。`SaveThread` 用于采集摄像头视频,并将每一帧图像保存到硬盘上,每保存一帧图像就通过信号 `saveDone` 发送给主线程,主线程再进行相应的处理。 在 `main` 函数,我们创建了一个 `VideoPlayer` 对象,并连接了两个信号 `imageReady` 和 `saveDone`。当 `DisplayThread` 发送 `imageReady` 信号时,我们就在主线程显示图像;当 `SaveThread` 发送 `saveDone` 信号时,我们就在主线程进行相应的处理,比如更新 UI 界面等。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

知来者逆

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

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

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

打赏作者

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

抵扣说明:

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

余额充值