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