如何在Qt中高效地显示OpenCVvideo?
我在OpenCV的帮助下从networking摄像机捕获多个stream。 当我尝试从OpenCV窗口( cv::namedWindow(...)
)显示这些stream,它的工作原理没有任何问题(迄今为止我已经尝试了4个stream)。
当我尝试在Qt小部件中显示这些stream时出现问题。 由于捕获是在另一个线程完成的,我必须使用信号槽机制来更新QWidget(在主线程中)。
基本上,我从捕获线程发出新捕获的帧,GUI线程中的一个插槽捕获它。 当我打开4个stream,我不能像以前一样顺利地显示video。
这是发射器:
void capture::start_process() { m_enable = true; cv::Mat frame; while(m_enable) { if (!m_video_handle->read(frame)) { break; } cv::cvtColor(frame, frame,CV_BGR2RGB); qDebug() << "FRAME : " << frame.data; emit image_ready(QImage(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888)); cv::waitKey(30); } }
这是我的插槽:
void widget::set_image(QImage image) { img = image; qDebug() << "PARAMETER IMAGE: " << image.scanLine(0); qDebug() << "MEMBER IMAGE: " << img.scanLine(0); }
这个问题似乎是不断复制QImages的开销。 虽然QImage使用隐式共享,当我通过qDebug()
消息比较图像的数据指针,我看到不同的地址。
1-有什么方法可以将OpenCV窗口直接embedded到QWidget中?
2-什么是处理显示多个video最有效的方法? 例如,videopipe理系统如何在同一时间显示多达32台摄像机?
3-什么是一定要走的路?
使用QImage::scanLine
强制进行深层复制,所以至less应该使用constScanLine
,或者更好的方法是将槽的签名更改为:
void widget::set_image(const QImage & image);
当然,你的问题会变成另外一件事情:QImage实例指向一个存在于另一个线程中的框架的数据,并且可以随时(也将会)改变。
有一个解决scheme:需要使用在堆上分配的新鲜帧,并且需要在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在QThread
默认提供本地消息分派和 Qt事件循环,因此使用QObject
进行捕获是一件简单的事情。 以下是一个完整的testing示例。
捕获,转换和查看器都在自己的线程中运行。 由于cv::Mat
是一个含primefaces,线程安全访问的隐式共享类,因此它被使用。
转换器有一个不处理过时帧的选项 – 如果转换只用于显示目的,这个选项很有用。
查看器在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"