前言
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();
}
}