Qt使用QAbstractVideoSurface捕获视频帧(信号槽方式),并用QPainter画出来

代码在最后!
Attention1:关于摄像头的内容,务必要在.pro加上下面两行,不加报错,注释用#哈。
#multimediawidgets 对应<QCameraViewfinder>等
#multimedia        对应<QCamera>, <QCameraImageCapture>,<QAbstractVideoSurface>等
QT       += core gui multimedia multimediawidgets
Attention2:为什么要用QAbstractVideoSurface呢?比如一些VideoWidget也可以办到啊。其实一般的widget只适合一般的显示,但你要对每一帧图像进行处理,画人脸框呢?用捕获视频帧,QPainter画出来是一个好办法。
一 :QAbstractVideoSurface抽象基类
1.1 养成好习惯,对于一个新类,加上它的头文件;添加qmake组建;留意它的父类;

在这里插入图片描述

1.2 抽象基类,务必显示所有的虚函数

在这里插入图片描述

1.2.1 支持的像素格式
QList<QVideoFrame::PixelFormat> supportedPixelFormats(QAbstractVideoBuffer::HandleType type = QAbstractVideoBuffer::NoHandle) const Q_DECL_OVERRIDE;

假如返回值是QAbstractVideoBuffer::NoHandle(其他的是OpenGL,shared memory XVideo image,macOS–帮助文档有),那么只能通过只读的方式来map(其实就是拷贝)这些帧数据了。如果你的帧像素数据格式不在这个这个函数中所提及,会报错。我只定义了几个,你可以全加上,但是Qt的QImage图像格式是RGB32。
在这里插入图片描述

//支持的像素格式
QList<QVideoFrame::PixelFormat> myvideosurface::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const
{
    if(handleType == QAbstractVideoBuffer::NoHandle){
        return QList<QVideoFrame::PixelFormat>() << QVideoFrame::Format_RGB32
                                                 << QVideoFrame::Format_ARGB32
                                                 << QVideoFrame::Format_ARGB32_Premultiplied
                                                 << QVideoFrame::Format_RGB565
                                                 << QVideoFrame::Format_RGB555;
    }
    else {
        return QList<QVideoFrame::PixelFormat>();
    }
}
1.2.2 捕获帧中视频格式是否被预设格式所支持
bool isFormatSupported(const QVideoSurfaceFormat &) const Q_DECL_OVERRIDE;    //将视频流中像素格式转换成格式对等的图片格式,若无对等的格式,返回QImage::Format_Invalid

这个函数其实是检测视频流的格式是否合法,返回时bool的,
(1):传入QVideoSurfaceFormat & 通过pixelFormat()返回视频流中帧的像素格式
(2):再将视频流中帧的像素格式通过imageFormatFromPixelFormat()弄成格式等效的图像格式,如不匹配,返回QImage::Format_Invalid
在这里插入图片描述

//将视频流中像素格式转换成格式对等的图片格式,若无对等的格式,返回QImage::Format_Invalid
bool myvideosurface::isFormatSupported(const QVideoSurfaceFormat &videoformat) const
{
    //imageFormatFromPixelFormat()-----返回与视频帧像素格式等效的图像格式
    //pixelFormat()-----返回视频流中帧的像素格式
    return QVideoFrame::imageFormatFromPixelFormat(videoformat.pixelFormat()) != QImage::Format_Invalid;
}
1.2.3 开始工作–只要摄像头开,就会调用
bool start(const QVideoSurfaceFormat &) Q_DECL_OVERRIDE;                       //只要摄像头开,就会调用
//这些虚函数,会自动被调用,start检测图像是否可以对等转换,每一帧有没有
bool myvideosurface::start(const QVideoSurfaceFormat &videoformat)
{
    //qDebug() << QVideoFrame::imageFormatFromPixelFormat(videoformat.pixelFormat());              //格式是RGB32
    if(QVideoFrame::imageFormatFromPixelFormat(videoformat.pixelFormat()) != QImage::Format_Invalid && !videoformat.frameSize().isEmpty()){
        QAbstractVideoSurface::start(videoformat);
        return true;
    }
    return false;
}
1.2.4 捕获每一帧视频,每一帧都会到present中
bool present(const QVideoFrame &) Q_DECL_OVERRIDE;                             //每一帧画面将回到这里处理

通过一个信号直接把获得的视频帧发送出去,出错直接停止,所以绝大部分时间都在present中,你把摄像头关了也就结束了。

bool myvideosurface::present(const QVideoFrame &frame)
{
    if (frame.isValid()){
        QVideoFrame cloneFrame(frame);                                      //每一帧视频都会进入present中,内部机制
        emit frameAvailable(cloneFrame);                                    //直接把视频帧发送出去
        return true;
    }
    stop();
    return false;
}
1.2.5 结束
void stop() Q_DECL_OVERRIDE; 
void myvideosurface::stop()
{
    QAbstractVideoSurface::stop();
}
二 :通过信号槽来将图片画到界面上。

在主窗口中实现相应的槽函数,如:

void rcvFrame(QVideoFrame);                             //接收图像帧数据

这里任有几个需要注意的:
map数据的时候要用ReadOnly方式,上面已经讲了。
其实视频数据可能存储在显存或者不可访问的内存中,所以在真正的访问视频数据时,需要用map接口把它拷贝出来。
使用map进行拷贝,务必用unmapping释放
在只读模式可以多次map,当然unmapping次数应该对应。
QAbstractVideoBuffer::ReadOnly可以适应大多数视频帧

void opencamera::rcvFrame(QVideoFrame m_currentFrame)
{
    m_currentFrame.map(QAbstractVideoBuffer::ReadOnly);
        //将视频帧转化成QImage,devicePixelRatio设备像素比,bytesPerLine一行的像素字节(1280*4=5120)
    videoImg =  QImage(m_currentFrame.bits(),
                   m_currentFrame.width(),
                   m_currentFrame.height(),
                   QVideoFrame::imageFormatFromPixelFormat(m_currentFrame.pixelFormat())).copy();       //这里要做一个copy,因为char* pdata在emit后释放了
    videoImg = videoImg.mirrored(false, true);                          //水平翻转,原始图片是反的
//    qDebug() <<  "image" << videoImg;  //可以看看输出啥东西
//    QString currentTime = QDateTime::currentDateTime().toString("yyyyMMddhhmmss");
//    QString savefile = QString("E:/study/QT/opencamera/opencamera/%1.jpg").arg(currentTime);
//    qDebug() << videoImg.save(savefile);
    m_currentFrame.unmap();                                         //释放map拷贝的内存
    QWidget::update();                                              //更新了,就会触发paintEvent画图
}
三 :在构造函数中创建myvideosurface对象并连接信号
//自己用QPainter将每一帧视频画出来
    myvideosurface *surface_ = new myvideosurface(this);
    m_camera->setViewfinder(surface_);                                                  //预览窗口重定向在surface_中
    //处理myvideosurface中每一帧视频
    connect(surface_, SIGNAL(frameAvailable(QVideoFrame)), this, SLOT(rcvFrame(QVideoFrame)), Qt::DirectConnection);
四 :在paintEvent事件中,发射信号
//QPainter一般情况下只能在paintEvent中使用。
//触发事件(3条,一般使用第二条进行触发):
//窗口部件第一次显示时,系统会自动产生一个绘图事件,从而强制绘制这个窗口部件。---主窗口起来会触发一次
//当重新调整窗口部件的大小时,系统也会产生一个绘制事件---QWidget::update()或者QWidget::repaint()来强制产生一个绘制事件
//当窗口部件被其他窗口部件遮挡,然后又再次显示出来的时候,就会对那些隐藏的区域产生一个绘制事件
void opencamera::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    QRect rect(130, 10, 550, 330);

    //设置画笔
    QPen pen;
    pen.setWidth(5);                    //线宽
    pen.setColor(Qt::red);
    pen.setStyle(Qt::SolidLine);        //实线
    pen.setCapStyle(Qt::FlatCap);       //线端点样式
    pen.setJoinStyle(Qt::BevelJoin);    //线连接点样式
    painter.setPen(pen);
    painter.drawRect(rect);

    //qDebug() <<  "image" << videoImg;     //第一次输出的QImage(null)
    //只要窗口启动,就会触发paintEvent,所以第一次是null,不可以画图。
    if(videoImg != QImage(nullptr)){
        painter.drawImage(rect, videoImg);
    }
}
五 :结果显示(工程是做人脸识别的,有密钥,整个工程传上来也没用),前提你要自己把摄像头开起来哦!我会再写一篇博客。

1 界面显示是画出来的
2 预览是最大分辨率1280*720,捕获的照片是自适应显示
3 对比是在线程中完成的
在这里插入图片描述
示例代码:
链接:https://pan.baidu.com/s/109B_QZ32-gU5-TnBzWP30w
提取码:bk2a

当然,我可以为您提供绘制螺旋线的示例代码。以下是使用QtQPainter类绘制螺旋线的示例: ```cpp #include <QApplication> #include <QMainWindow> #include <QPainter> #include <QTimer> class SpiralWidget : public QWidget { public: SpiralWidget(QWidget *parent = nullptr) : QWidget(parent) { resize(400, 400); // 设置定时器,每隔50毫秒重绘一次 QTimer *timer = new QTimer(this); connect(timer, &QTimer::timeout, this, QOverload<>::of(&SpiralWidget::update)); timer->start(50); } protected: void paintEvent(QPaintEvent *event) override { Q_UNUSED(event) QPainter painter(this); // 设置笔颜色和宽度 painter.setPen(Qt::red); painter.setRenderHint(QPainter::Antialiasing, true); // 设置螺旋线的起始点坐标 int centerX = width() / 2; int centerY = height() / 2; // 设置螺旋线的参数 int radiusStep = 5; int angleStep = 10; int maxRadius = qMin(width(), height()) / 2; // 绘制螺旋线 for (int radius = 0; radius <= maxRadius; radius += radiusStep) { int angle = radius / radiusStep * angleStep; int x = centerX + qCos(qDegreesToRadians(angle)) * radius; int y = centerY + qSin(qDegreesToRadians(angle)) * radius; painter.drawPoint(x, y); } } }; int main(int argc, char *argv[]) { QApplication app(argc, argv); QMainWindow window; SpiralWidget *spiralWidget = new SpiralWidget(&window); window.setCentralWidget(spiralWidget); window.show(); return app.exec(); } ``` 这是一个基于QWidget的简单例子,在窗口中绘制了一个红色的螺旋线。每隔50毫秒,窗口会重绘一次,实现动效果。您可以根据需要调整绘制的参数和样式。希望这可以帮助到您!
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值