使用QImage::scanLine
迫使深拷貝,所以至少,你應該使用constScanLine
,或者更好的是,改變槽的簽名:
void widget::set_image(const QImage & image);
當然,你的問題就變成別的東西:在QImage實例指向一個存在於另一個線程中的框架的數據,並且可以(並且將會)隨時改變。
有一個解決方案:需要使用分配在堆上的新鮮幀,並且需要在QImage
內捕獲幀。 QScopedPointer
用於防止內存泄漏,直到QImage
取得該幀的所有權。
static void matDeleter(void* mat) { delete static_cast<cv::Mat*>(mat); }
class capture {
Q_OBJECT
bool m_enable;
...
public:
Q_SIGNAL void image_ready(const QImage &);
...
};
void capture::start_process() {
m_enable = true;
while(m_enable) {
QScopedPointer<cv::Mat> frame(new cv::Mat);
if (!m_video_handle->read(*frame)) {
break;
}
cv::cvtColor(*frame, *frame, CV_BGR2RGB);
// Here the image instance takes ownership of the frame.
const QImage image(frame->data, frame->cols, frame->rows, frame->step,
QImage::Format_RGB888, matDeleter, frame.take());
emit image_ready(image);
cv::waitKey(30);
}
}
當然,因爲Qt提供本地信息調度和默認Qt的事件循環在QThread
,它的使用QObject
用於捕獲過程一件簡單的事情。以下是一個完整的測試示例。
捕獲,轉換和查看器都在自己的線程中運行。由於cv::Mat
是一個帶原子,線程安全訪問的隱式共享類,因此它被使用。
轉換器有一個不處理過時幀的選項 - 如果轉換僅用於顯示目的,該選項很有用。
查看器在gui線程中運行並正確刪除過時的幀。觀衆沒有理由處理陳舊的畫面。
如果您要收集數據以保存到磁盤,則應該以高優先級運行捕獲線程。您還應該檢查openCV apis,看看是否有方法將本機相機數據轉儲到磁盤。
爲了加速轉換,您可以在openCV中使用gpu加速類。
// https://github.com/KubaO/stackoverflown/tree/master/questions/opencv-21246766
#include <QtWidgets>
#include <opencv2/opencv.hpp>
Q_DECLARE_METATYPE(cv::Mat)
class Capture : public QObject {
Q_OBJECT
QBasicTimer m_timer;
QScopedPointer<cv::VideoCapture> m_videoCapture;
public:
Capture(QObject * parent = {}) : QObject(parent) {}
Q_SIGNAL void started();
Q_SLOT void start(int cam = {}) {
if (!m_videoCapture)
m_videoCapture.reset(new cv::VideoCapture(cam));
if (m_videoCapture->isOpened()) {
m_timer.start(0, this);
emit started();
}
}
Q_SLOT void stop() { m_timer.stop(); }
Q_SIGNAL void matReady(const cv::Mat &);
private:
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() != m_timer.timerId()) return;
cv::Mat frame;
if (!m_videoCapture->read(frame)) { // Blocks until a new frame is ready
m_timer.stop();
return;
}
emit matReady(frame);
}
};
class Converter : public QObject {
Q_OBJECT
QBasicTimer m_timer;
cv::Mat m_frame;
bool m_processAll = true;
static void matDeleter(void* mat) { delete static_cast<cv::Mat*>(mat); }
void queue(const cv::Mat & frame) {
if (!m_frame.empty()) qDebug() << "Converter dropped frame!";
m_frame = frame;
if (! m_timer.isActive()) m_timer.start(0, this);
}
void process(cv::Mat frame) {
cv::resize(frame, frame, cv::Size(), 0.3, 0.3, cv::INTER_AREA);
cv::cvtColor(frame, frame, CV_BGR2RGB);
const QImage image(frame.data, frame.cols, frame.rows, frame.step,
QImage::Format_RGB888, &matDeleter, new cv::Mat(frame));
Q_ASSERT(image.constBits() == frame.data);
emit imageReady(image);
}
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() != m_timer.timerId()) return;
process(m_frame);
m_frame.release();
m_timer.stop();
}
public:
explicit Converter(QObject * parent = nullptr) : QObject(parent) {}
void setProcessAll(bool all) { m_processAll = all; }
Q_SIGNAL void imageReady(const QImage &);
Q_SLOT void processFrame(const cv::Mat & frame) {
if (m_processAll) process(frame); else queue(frame);
}
};
class ImageViewer : public QWidget {
Q_OBJECT
QImage m_img;
void paintEvent(QPaintEvent *) {
QPainter p(this);
p.drawImage(0, 0, m_img);
m_img = {};
}
public:
ImageViewer(QWidget * parent = nullptr) : QWidget(parent) {
setAttribute(Qt::WA_OpaquePaintEvent);
}
Q_SLOT void setImage(const QImage & img) {
if (!m_img.isNull()) qDebug() << "Viewer dropped frame!";
m_img = img;
if (m_img.size() != size()) setFixedSize(m_img.size());
update();
}
};
class Thread final : public QThread { public: ~Thread() { quit(); wait(); } };
int main(int argc, char *argv[])
{
qRegisterMetaType<cv::Mat>();
QApplication app(argc, argv);
ImageViewer view;
Capture capture;
Converter converter;
Thread captureThread, converterThread;
// Everything runs at the same priority as the gui, so it won't supply useless frames.
converter.setProcessAll(false);
captureThread.start();
converterThread.start();
capture.moveToThread(&captureThread);
converter.moveToThread(&converterThread);
QObject::connect(&capture, &Capture::matReady, &converter, &Converter::processFrame);
QObject::connect(&converter, &Converter::imageReady, &view, &ImageViewer::setImage);
view.show();
QObject::connect(&capture, &Capture::started, [](){ qDebug() << "capture started"; });
QMetaObject::invokeMethod(&capture, "start");
return app.exec();
}
#include "main.moc"
您是否嘗試忽略QImage並直接使用數據數組作爲紋理來渲染QGLWidget?我想這應該會更快。專業解決方案可能使用專用硬件,但這也只是一種猜測。 – Micka