如何在QGraphicsView中嵌入QLabel实现可缩放的视频播放?
Qt可缩放监控视频播放问题解决方案
1. 为何QLabel嵌入QGraphicsView后视频弹出独立窗口?
- 若使用
QMediaPlayer+QVideoWidget:QVideoWidget本身带有顶层窗口属性(默认Qt::Window),即使通过QGraphicsProxyWidget嵌入QGraphicsScene,系统仍会为其创建独立窗口,而非在场景内渲染。 - 若使用OpenCV时误用
cv::imshow:之前用QLabel显示正常是因为将帧转成QPixmap赋值给QLabel,但嵌入场景后如果误调用cv::imshow,会触发OpenCV自带的独立窗口。
2. 如何实现场景内视频播放+鼠标滚轮缩放?
核心是避免使用带顶层窗口属性的视频控件,直接将视频帧转为QPixmap/QImage,在QGraphicsScene的元素中渲染。以下是两种可行方案:
方案一:OpenCV + QGraphicsPixmapItem(灵活易扩展)
跳过QLabel,直接用QGraphicsPixmapItem承载视频帧,配合QGraphicsView实现缩放和拖拽:
#include <QApplication> #include <QGraphicsView> #include <QGraphicsScene> #include <QGraphicsPixmapItem> #include <QTimer> #include <opencv2/opencv.hpp> #include <opencv2/imgproc/imgproc.hpp> class VideoView : public QGraphicsView { Q_OBJECT public: VideoView(QWidget *parent = nullptr) : QGraphicsView(parent) { scene = new QGraphicsScene(this); pixmapItem = new QGraphicsPixmapItem(); scene->addItem(pixmapItem); setScene(scene); setDragMode(QGraphicsView::ScrollHandDrag); // 支持拖拽平移 // 打开摄像头或视频文件(替换为你的路径) cap.open(0); if (!cap.isOpened()) return; timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &VideoView::updateFrame); timer->start(30); // 约30fps } protected: void wheelEvent(QWheelEvent *event) override { // 以光标为中心缩放 const qreal scaleFactor = 1.15; if (event->angleDelta().y() > 0) { scale(scaleFactor, scaleFactor); } else { scale(1/scaleFactor, 1/scaleFactor); } // 调整视图中心到光标对应场景位置 QPointF scenePos = mapToScene(event->position().toPoint()); centerOn(scenePos); event->accept(); } private slots: void updateFrame() { cv::Mat frame; cap >> frame; if (frame.empty()) return; // OpenCV帧转QPixmap cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB); QImage img(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888); pixmapItem->setPixmap(QPixmap::fromImage(img)); // 首次显示适配视图大小 static bool firstShow = true; if (firstShow) { fitInView(pixmapItem, Qt::KeepAspectRatio); firstShow = false; } } private: QGraphicsScene *scene; QGraphicsPixmapItem *pixmapItem; QTimer *timer; cv::VideoCapture cap; }; int main(int argc, char *argv[]) { QApplication a(argc, argv); VideoView view; view.resize(800, 600); view.show(); return a.exec(); } #include "main.moc"
方案二:QMediaPlayer + QVideoSink(纯Qt原生方案)
利用QVideoSink捕获视频帧,转成QPixmap后渲染到场景,避免QVideoWidget的独立窗口问题:
#include <QApplication> #include <QGraphicsView> #include <QGraphicsScene> #include <QGraphicsPixmapItem> #include <QMediaPlayer> #include <QVideoSink> #include <QUrl> class VideoView : public QGraphicsView { Q_OBJECT public: VideoView(QWidget *parent = nullptr) : QGraphicsView(parent) { scene = new QGraphicsScene(this); pixmapItem = new QGraphicsPixmapItem(); scene->addItem(pixmapItem); setScene(scene); setDragMode(QGraphicsView::ScrollHandDrag); // 初始化媒体播放器 player = new QMediaPlayer(this); videoSink = new QVideoSink(this); player->setVideoSink(videoSink); connect(videoSink, &QVideoSink::videoFrameChanged, this, &VideoView::updateFrame); // 加载视频(替换为你的视频路径/流媒体地址) player->setSource(QUrl::fromLocalFile("your_video.mp4")); player->play(); } protected: void wheelEvent(QWheelEvent *event) override { const qreal scaleFactor = 1.15; if (event->angleDelta().y() > 0) { scale(scaleFactor, scaleFactor); } else { scale(1/scaleFactor, 1/scaleFactor); } QPointF scenePos = mapToScene(event->position().toPoint()); centerOn(scenePos); event->accept(); } private slots: void updateFrame(const QVideoFrame &frame) { if (!frame.isValid()) return; // 视频帧转QPixmap QImage img = frame.toImage(); pixmapItem->setPixmap(QPixmap::fromImage(img)); static bool firstShow = true; if (firstShow) { fitInView(pixmapItem, Qt::KeepAspectRatio); firstShow = false; } } private: QGraphicsScene *scene; QGraphicsPixmapItem *pixmapItem; QMediaPlayer *player; QVideoSink *videoSink; }; int main(int argc, char *argv[]) { QApplication a(argc, argv); VideoView view; view.resize(800, 600); view.show(); return a.exec(); } #include "main.moc"
3. Qt中可缩放视频播放的更优方案
- 自定义QWidget绘制:若不需要QGraphicsScene的额外功能,直接继承QWidget重写
paintEvent绘制视频帧,自行处理缩放和鼠标事件,减少层级开销,性能更优。核心逻辑示例:
void VideoWidget::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setRenderHint(QPainter::SmoothPixmapTransform); // 计算缩放后保持比例的绘制区域 QRectF targetRect = calculateScaledRect(); painter.drawPixmap(targetRect, currentPixmap, currentPixmap.rect()); } void VideoWidget::wheelEvent(QWheelEvent *event) { qreal scaleDelta = event->angleDelta().y() > 0 ? 1.1 : 0.9; scale = qBound(0.1, scale * scaleDelta, 5.0); // 限制缩放范围 // 调整偏移量,保证光标位置对应帧的同一坐标 QPointF mousePos = event->position(); QPointF scenePos = (mousePos - offset) / scale; offset = mousePos - scenePos * scale; update(); }
- Qt Quick方案:若为现代Qt项目,使用Qt Quick的
Video组件配合PinchArea实现缩放,代码更简洁,触摸/鼠标交互更流畅,适合跨平台场景。
内容的提问来源于stack exchange,提问作者Ling Yun
相关产品推荐
相关产品推荐

