将QVideoFrame转换为QImage

7
我想从 QMediaPlayer 中获取每一帧并将其转换为 QImage(或 cv::Mat)。因此,我使用了来自 QVideoProbevideoFrameProbed 信号:
connect(&video_probe_, &QVideoProbe::videoFrameProbed, 
         [this](const QVideoFrame& currentFrame){
   //QImage img = ??
}

但我没有找到任何方法可以从QVideoFrame获取QImage

如何将QVideoFrame转换为QImage?!


这取决于QVideoFrame的格式。QImage只能接受一些基于RGB的格式。有各种库可以在各种YUV和RGB格式之间进行转换。但是如果可以的话,我建议直接使用OpenCV读取视频帧。 - tux3
你解决了吗? - aviit
4个回答

12

您可以使用QImage的构造函数:

 QImage img( currentFrame.bits(),
             currentFrame.width(),
             currentFrame.height(),
             currentFrame.bytesPerLine(),
             imageFormat);

您可以从QVideoFramepixelFormat中获取imageFormat

 QImage::Format imageFormat = QVideoFrame::imageFormatFromPixelFormat(currentFrame.pixelFormat());

虽然视频可能是YUV格式(imageFormatFromPixelFormat将失败)。不确定Qt是否内置了yuv转换.. 或者可以使用libyuv。 - Pete
1
我认为Qt依赖于底层媒体框架进行转换。在Linux上,这是gstreamer,它支持许多转换。作为一个支持事实,在QAbstractVideoSurface的自定义实现中,您被迫实现纯虚拟supportedPixelFormats,并且当我指定BGR24作为唯一格式时,我收到了该格式。 - Mr. Developerdude
3
请阅读此处的要求:http://doc.qt.io/qt-5/qvideoframe.html#details,对于调用bits()的QVideoFrame,需要先调用map()函数。 - Mr. Developerdude
我尝试了img.save("filename.png"),但没有输出。 - aviit

8

对于QCamera的输出,该方法并不总是有效的。特别是当给定QVideoFrame::Format_Jpeg时,QVideoFrame::imageFormatFromPixelFormat()会返回QImage::Format_Invalid,而这正是我的QCamera输出的格式。但是以下方法可以解决:

QImage Camera::imageFromVideoFrame(const QVideoFrame& buffer) const
{
    QImage img;
    QVideoFrame frame(buffer);  // make a copy we can call map (non-const) on
    frame.map(QAbstractVideoBuffer::ReadOnly);
    QImage::Format imageFormat = QVideoFrame::imageFormatFromPixelFormat(
                frame.pixelFormat());
    // BUT the frame.pixelFormat() is QVideoFrame::Format_Jpeg, and this is
    // mapped to QImage::Format_Invalid by
    // QVideoFrame::imageFormatFromPixelFormat
    if (imageFormat != QImage::Format_Invalid) {
        img = QImage(frame.bits(),
                     frame.width(),
                     frame.height(),
                     // frame.bytesPerLine(),
                     imageFormat);
    } else {
        // e.g. JPEG
        int nbytes = frame.mappedBytes();
        img = QImage::fromData(frame.bits(), nbytes);
    }
    frame.unmap();
    return img;
}

3

Qt有一个名为qt_imageFromVideoFrame的私有函数,用于将QVideoFrame转换为QImage。如果您想使用它,需要:

  1. 在您的.pro文件中添加QT += multimedia multimedia-private
  2. 在您的.cpp文件中添加#include "private/qvideoframe_p.h"
  3. 然后您就可以使用以下签名的qt_imageFromVideoFrame函数:

    QImage qt_imageFromVideoFrame(const QVideoFrame& f);

请注意头文件中的警告:

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

实现使用 QVideoFrame::map() 并对位进行低级别操作。转换器处理多种条件,包括 YUV。 qt_imageFromVideoFrame 在几乎所有平台上都有效,如Windows、macOS、Linux和iOS。唯一不太可靠的平台是Android。在那里,你需要使用OpenGL调用来提取VideoFrame。此外,在Android上,您需要在最后调用 QImage:rgbSwapped(),因为 QImage 存储图像的格式为 ARGB,而 OpenGL 检索到的格式为 ABGR。
QImage QVideoFrameToQImage( const QVideoFrame& videoFrame )
{
    if ( videoFrame.handleType() == QAbstractVideoBuffer::NoHandle )
    {
        QImage image = qt_imageFromVideoFrame( videoFrame );
        if ( image.isNull() ) return QImage();
        if ( image.format() != QImage::Format_ARGB32 ) return image.convertToFormat( QImage::Format_ARGB32 );
        return image;
    }
    if ( videoFrame.handleType() == QAbstractVideoBuffer::GLTextureHandle )
    {
        QImage image( videoFrame.width(), videoFrame.height(), QImage::Format_ARGB32 );
        GLuint textureId = static_cast<GLuint>( videoFrame.handle().toInt() );
        QOpenGLContext* ctx = QOpenGLContext::currentContext();
        QOpenGLFunctions* f = ctx->functions();
        GLuint fbo;
        f->glGenFramebuffers( 1, &fbo );
        GLint prevFbo;
        f->glGetIntegerv( GL_FRAMEBUFFER_BINDING, &prevFbo );
        f->glBindFramebuffer( GL_FRAMEBUFFER, fbo );
        f->glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,  GL_TEXTURE_2D, textureId, 0 );
        f->glReadPixels( 0, 0,  videoFrame.width(),  videoFrame.height(), GL_RGBA, GL_UNSIGNED_BYTE, image.bits() );
        f->glBindFramebuffer( GL_FRAMEBUFFER, static_cast<GLuint>( prevFbo ) );
        return image.rgbSwapped();
    }
    return QImage();
}

我在GitHub上有一个完整的应用程序,其中包括上述函数的实现:

https://github.com/stephenquan/MyVideoFilterApp/blob/master/QVideoFrameToQImage.cpp


2

如果您在发现Qt5.15.0中不再存在qt_imageFromVideoFrame私有函数后到达此处,那么现在您可以使用新实现的方法QImage QVideoFrame::image()。

QImage img = frame.image();

我已在Ubuntu上测试过,它可以正常工作。
对于Ubuntu 6.4版本,请使用: QImage img = frame.toImage();

谢谢,你救了我一命,希望它也能在安卓上运行。 - StereoMatching

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接