Qt踩坑记001:无法在读取相机图像的取景器QVideoWidget/QGraphicsVideoItem上显示图片

问题的引出

想做一个界面用于平面标定,在一个类Widget控件中读取相机视频流,并在该控件上显示标定结果图片。查阅相关资料,得知Qt Multimedia模块可以用来实现多媒体应用,并且使用硬件加速进行解码和渲染(事实证明速度确实很快),节省了很多开发步骤。
于是根据通用教程在QVideoWidget取景器上成功运行起视频流,并实现视频流的捕获功能(简而言之就是①获取QVideoFrame帧并转换为QImage)。
用QVideoWidget读取摄像头图像
现在,我想要把处理后的图片再呈现到Widget控件中,简而言之就是②将QImage在Widget上进行可视化
这时候便出现了各种问题,想了多种办法都不成功,想到后续应用的相机传输不一定用USB接口,大概也用不到QMediaCaptureSession类机制,遂放弃,但发帖记录一下这次失败经历。
(PS:博主用的版本是Qt 6.2.4,安装完了才知道Qt 6中的Qt Multimedia模块进行了大的删改,是有史以来Qt的现有模块改动最大的一次,很多函数都没了或被合并到其它类当中了,于是翻官方英文文档啃了好久······想死的心都有了。)

第一次尝试:重写QVideoWidget控件的paint()函数

查阅资料,QVideoWidget控件不提供绘图渲染机制,得知在父类QWidget中有如下的绘图事件的触发机制,可以在Widget的背景上进行绘图

paintEvent(QPaintEvent *)

于是构建了QVideoWidget的一种子类“QVideosWidget”(PS:不喜欢起名字,索性一字之差haha),并将我的Widget控件提升为QVideosWidget。对paintEvent(QPaintEvent *)进行重写代码如下:

void QVideosWidget::paintEvent(QPaintEvent *)//画笔事件,更新Widget里面的图片
{
    if(g_bIsShowImg)//标志位,确认此时显示图片而非其它
    {
        QPainter painter(this);
        QRect rect(0,0,this->width(),this->height());
        painter.drawImage(rect,g_imgReceive);
        painter.end();
        g_bIsShowImg=0;//画完后重置回0
    }
}

但是在重写后,原来QVideoWidget背景由全黑变成了透明,且视频和图片都无法显示。这说明,QVideoWidget的视频流可视化功能被写入了paintEvent函数,如果我这时候去重写它,会丢失原有的视频流可视化功能
为了解决这个问题,尝试改变背景等属性都失败,发现有其它用户出现了和我相似的问题:透明窗口里面 QVideoWidget视频显示不出图像,但依然没有合适的处理方法。

第二次尝试:通过QVideoFrame向QVideoWidget读入一帧图片

Qt 6 中QVideoFrame类用于封装视频帧的像素数据和关于该帧的信息。
仔细想了想,根据QVideoWidget的封闭性,既然我们无法向QVideoWidget直接读入一张图片,那么能否通过QVideoFrame类向相关的视频流内容类(主要是QMediaCaptureSession和QMediaPlayer)写入一帧图片,然后在QVideoWidget输出?
于是翻阅了QMediaCaptureSessionQMediaPlayer的官方文档,没有发现读取单帧QVideoFrame的接口。(PS:Qt 6大改真是绝了,连Qt 5中QMediaPlayList这么好用的类都没了)
QVideoFrame类文档中发现了一个绘图触发事件

void QVideoFrame::paint(QPainter *painter, const QRectF &rect, const QVideoFrame::PaintOptions &options)
使用QPainter绘制器painter将此QVideoFrame渲染至矩形rect窗口。PaintOptions是一个结构体,可用于指定背景颜色以及视频填充矩形的方式,具体可翻阅<QtMultimedia/qvideoframeformat.h>。

该事件有可能是QVideoFrame绘图的底层触发,但是官方文档给的指导只有上述这两句话,博主自己尝试多个地方调用后无果。

第三次尝试:在QVideoWidget控件上浮动一个QLabel控件

经历了第一、二次尝试失败后,想到既然无法在QVideoWidget控件上show一个图片,那我在QVideoWidget上方浮动一个QLabel,把图片绘入QLabel不就行了?但尝试了多次,发现QVideoWidget都会把QLabel给覆盖掉,类似问题也有用户遇到了:Qt疑难杂症:无法QVideoWidget播放器上浮控件,没有合适的解决方案。

2023/01/13 更新
引用于博客:“Qt 小例子学习36 将按钮添加到 QVideoWidget”
Qt 小例子学习36 将按钮添加到 QVideoWidget。后续看到上述文章中博主用布局layout实现了我设想的方法,可信度较高,改天试试。但第三次尝试的这个方法终究不是最优解,后续开发的很多功能不好通过这个控件去实现,例如捕获鼠标坐标等。

第四次尝试:在QGraphicsSence上通过QGraphicsVideoItem类取景器渲染视频流,同时将图片渲染在QGraphicsSence上

QGraphicsVideoItem是官方文档中提示的除了QVideoWidget以外的另一种取景器。于是博主想到可以通过场景(Sence)将视频流(Video)的项目(Item)图片项目分开来管理再显示在QGraphicsView视图(View)控件上。
做了尝试依然无果。查阅资料,发现很多博主也出现类似问题(QCamera使用QGraphicsVideoItem输出图像),用QGraphicsVideoItem取景机制本身就存在各种不足,连最基础的显示都很难实现,且限制非常多,不利于二次开发。后来又找到一篇博文,直接了当地阐述QGraphicsVideoItem 和其它QGraphicsItem无法共同渲染的问题,这次尝试也基本失败了。

Rendering a QGraphicsScene with QGraphicsVideoItem to QImage:
The video is usually decoded and rendered using HW acceleration, so asking for the widget to be painted like that might not work at all, or at least it depends on the actual video player backend.
You could use QScreen::grabWindow (assuming Qt 5, it was QPixmap::grabWindow in Qt 4), after the screen is actually rendered. To actually have any video, you need to grab the screenshow when video is actually showing, so you have to do it once the event loop is running and window is actually shown, for example by overrding showEvent or just using QTimer.
If you want a screenshot of both the video and the GUI without actually showing the window, I’m not sure how to go about that.

小结

不要指望在Qt自带的取景器QVideoWidget/QGraphicsVideoItem上做太多的二次图形开发,限制太多了!如果像我一样想做一些工业相机的二次开发的话,Qt Multimedia模块还是不太合适,最好在相机公司提供的官方SDK上进一步开发吧。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值