You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Qt捕获QVideoFrame并在QVideoWidget显示的异常问题排查

问题1:Format_YUYV格式QVideoFrame转QImage后base64为空

问题出在QImage不直接支持YUYV格式,直接转换会得到无效的QImage,自然导出的base64是空的。手动将YUYV像素数据转成RGB格式即可解决:

  1. 先把QVideoFrame映射到内存,获取像素指针:
bool QMyAbstractVideoSurface::present(const QVideoFrame &frame)
{
    if (frame.format() != QVideoFrame::Format_YUYV) {
        return QAbstractVideoSurface::present(frame);
    }

    if (!frame.map(QAbstractVideoBuffer::ReadOnly)) {
        return false;
    }

    int width = frame.width();
    int height = frame.height();
    int bytesPerLine = frame.bytesPerLine();
    uchar *yuyvData = frame.bits();

    // 创建RGB888格式的QImage,逐个像素转换YUYV到RGB
    QImage rgbImage(width, height, QImage::Format_RGB888);
    uchar *rgbData = rgbImage.bits();

    // 用BT.601标准公式转换,颜色偏色的话换下面的BT.709公式
    for (int y = 0; y < height; ++y) {
        uchar *yuyvRow = yuyvData + y * bytesPerLine;
        uchar *rgbRow = rgbData + y * rgbImage.bytesPerLine();

        for (int x = 0; x < width; x += 2) {
            // YUYV结构为Y0 U Y1 V,两个像素共享一组UV分量
            uint8_t Y0 = yuyvRow[0];
            uint8_t U = yuyvRow[1];
            uint8_t Y1 = yuyvRow[2];
            uint8_t V = yuyvRow[3];

            // 转换第一个像素
            int R = Y0 + 1.370705 * (V - 128);
            int G = Y0 - 0.698001 * (V - 128) - 0.337633 * (U - 128);
            int B = Y0 + 1.732446 * (U - 128);

            // 将数值限制在0-255范围内
            R = qBound(0, R, 255);
            G = qBound(0, G, 255);
            B = qBound(0, B, 255);

            rgbRow[0] = R;
            rgbRow[1] = G;
            rgbRow[2] = B;

            // 转换第二个像素
            R = Y1 + 1.370705 * (V - 128);
            G = Y1 - 0.698001 * (V - 128) - 0.337633 * (U - 128);
            B = Y1 + 1.732446 * (U - 128);

            R = qBound(0, R, 255);
            G = qBound(0, G, 255);
            B = qBound(0, B, 255);

            rgbRow[3] = R;
            rgbRow[4] = G;
            rgbRow[5] = B;

            yuyvRow += 4;
            rgbRow += 6;
        }
    }

    frame.unmap();

    // 现在保存PNG并转base64就正常了
    QByteArray pngData;
    QBuffer buffer(&pngData);
    buffer.open(QIODevice::WriteOnly);
    if (rgbImage.save(&buffer, "PNG")) {
        QString base64 = pngData.toBase64();
        // 在这里使用base64进行后续操作
    }

    // 触发显示逻辑(见问题2)
    emit frameCaptured(rgbImage);

    return true;
}

若转换后颜色偏色,替换为BT.709公式(适配高清视频):

int R = Y0 + 1.5748 * (V - 128);
int G = Y0 - 0.1873 * (U - 128) - 0.4681 * (V - 128);
int B = Y0 + 1.8556 * (U - 128);
问题2:将捕获的帧显示到QVideoWidget并添加至网格布局

你当前把取景器设为自定义surface,QVideoWidget自身的渲染管道会被覆盖,提供两个可行方案:

方案1:原生预览+帧捕获(推荐,性能最优)

不要将QCamera的取景器设置为自定义surface,改用QMediaCaptureSession同时连接QVideoWidget和自定义QVideoSink,既保留QVideoWidget的硬件加速预览,又能捕获帧:

// 初始化代码
QMediaCaptureSession session;
QCamera camera;
QVideoWidget videoWidget;
QMyVideoSink *videoSink = new QMyVideoSink(this); // 继承QVideoSink

session.setCamera(&camera);
session.setVideoOutput(&videoWidget); // 让QVideoWidget显示原生预览
session.setVideoSink(videoSink); // 额外绑定sink用于帧捕获

// 将videoWidget添加到网格布局
ui->gridLayout->addWidget(&videoWidget, 0, 0); // 替换为你的实际布局位置

camera.start();

自定义QVideoSink重写setVideoFrame方法:

class QMyVideoSink : public QVideoSink
{
    Q_OBJECT
protected:
    void setVideoFrame(const QVideoFrame &frame) override {
        QVideoSink::setVideoFrame(frame); // 保证QVideoWidget正常渲染
        // 在这里处理帧转换和base64逻辑,和上面present方法中的逻辑一致
        if (frame.format() == QVideoFrame::Format_YUYV) {
            // 转换为QImage并处理base64
        }
    }
};

方案2:自定义surface手动绘制到QVideoWidget

若必须使用自定义surface,可在surface中发送信号将转换后的QImage传到UI线程,再手动绘制到QVideoWidget:

  1. 在自定义surface中添加信号:
class QMyAbstractVideoSurface : public QAbstractVideoSurface
{
    Q_OBJECT
signals:
    void frameReady(const QImage &image);
    // 其他代码...
};
  1. 在present方法中发送信号:
// 转换得到rgbImage后
emit frameReady(rgbImage);
  1. 在UI类中连接信号并绘制:
// 初始化时绑定信号槽
connect(mySurface, &QMyAbstractVideoSurface::frameReady, this, [this](const QImage &image) {
    QPainter painter(&videoWidget);
    painter.drawImage(videoWidget.rect(), image.scaled(videoWidget.size(), Qt::KeepAspectRatio));
    painter.end();
});

// 将videoWidget添加到网格布局
ui->gridLayout->addWidget(&videoWidget, 0, 0);

此方案为软件绘制,性能不如方案1的硬件加速预览,仅在特殊场景使用。

额外提醒
  • present方法运行在视频线程,更新UI必须切换到UI线程,信号槽是最安全的方式,禁止直接在present中操作UI。
  • 先打印日志确认帧格式确实为Format_YUYV,部分摄像头可能输出NV12等其他YUV格式。
  • 转换后用rgbImage.isNull()检查QImage有效性,无效则排查转换逻辑。

内容的提问来源于stack exchange,提问作者zabitstack

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 17:15:41