Конвертировать QVideoFrame в QImage

Я хочу получить все кадры из QMediaPlayer и преобразовать его в QImage (или же cv::Mat)

поэтому я использовал videoFrameProbed сигнал от QVideoProbe:

connect(&video_probe_, &QVideoProbe::videoFrameProbed, 
         [this](const QVideoFrame& currentFrame){
   //QImage img = ??
}

Но я не нашел способа получить QImage от QVideoFrame!

Как я могу конвертировать QVideoFrame в QImage?!

2 ответа

Вы можете использовать конструктор QImage:

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

Где вы можете получить imageFormat от pixelFormat из QVideoFrame:

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

Для вывода QCamera этот метод не всегда работает. В частности, QVideoFrame::imageFormatFromPixelFormat() возвращает QImage::Format_Invalid при получении QVideoFrame::Format_Jpeg, что и есть в моей 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;
}

Qt имеет частную функцию qt_imageFromVideoFrame для преобразования QVideoFrame к QImage. Если вы хотите его использовать, вам необходимо:

  1. добавлять QT += multimedia multimedia-private на ваш .pro файл
  2. добавлять #include "private/qvideoframe_p.h" на ваш .cpp файл
  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

Для всех, кто оказался здесь после того, как обнаружил, что приватная функция qt "qt_imageFromVideoFrame" больше не существует в Qt5.15.0, теперь вы можете использовать недавно реализованный метод QImage QVideoFrame::image().

QImage img = frame.image();

Я тестировал это на Ubuntu, и он работает.

Другие вопросы по тегам