【Qt从摄像头视频中获取数据】

有时候需要在视频上画图,所以需要能获取到每一帧视频数据。

以前从视频文件或视频流中得到帧,一般都是使用qt + ffmpeg或qt + vlc。

qt对显示处理视频大体有以下方法:

  1. QMediaPlayer + QVideoWidget

这种方法只适合简单的显示视频功能,不适合对视频进行处理(比如画图)

  1. QMediaPlayer + QGraphicsVideoItem + QGraphicsScene + QGraphicsView

这种方法功能强大,除了显示视频功能,还可以做复杂的图形处理(具体可以查看QGraphicsScene的使用)

  1. QMediaPlayer + QAbstractVideoSurface

这种方法比较简单,是我下面要介给的。可以获取到每一帧视频数据,基本可以实现与qt + ffmpeg或qt + vlc相同的效果。

自定义VideoSurface:

class VideoSurface : public QAbstractVideoSurface
{
	Q_OBJECT
 
public:
	VideoSurface(QObject *parent = Q_NULLPTR);
	~VideoSurface();
 
	QList<QVideoFrame::PixelFormat> supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const;
	bool present(const QVideoFrame &frame);
 
signals:
	void frameAvailable(QVideoFrame &frame);
 
};

实现如下:

VideoSurface::VideoSurface(QObject *parent)
	: QAbstractVideoSurface(parent)
{
}
 
VideoSurface::~VideoSurface()
{
}
 
QList<QVideoFrame::PixelFormat> VideoSurface::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const
{
	QList<QVideoFrame::PixelFormat> listPixelFormats;
 
	listPixelFormats << QVideoFrame::Format_ARGB32
		<< QVideoFrame::Format_ARGB32_Premultiplied
		<< QVideoFrame::Format_RGB32
		<< QVideoFrame::Format_RGB24
		<< QVideoFrame::Format_RGB565
		<< QVideoFrame::Format_RGB555
		<< QVideoFrame::Format_ARGB8565_Premultiplied
		<< QVideoFrame::Format_BGRA32
		<< QVideoFrame::Format_BGRA32_Premultiplied
		<< QVideoFrame::Format_BGR32
		<< QVideoFrame::Format_BGR24
		<< QVideoFrame::Format_BGR565
		<< QVideoFrame::Format_BGR555
		<< QVideoFrame::Format_BGRA5658_Premultiplied
		<< QVideoFrame::Format_AYUV444
		<< QVideoFrame::Format_AYUV444_Premultiplied
		<< QVideoFrame::Format_YUV444
		<< QVideoFrame::Format_YUV420P
		<< QVideoFrame::Format_YV12
		<< QVideoFrame::Format_UYVY
		<< QVideoFrame::Format_YUYV
		<< QVideoFrame::Format_NV12
		<< QVideoFrame::Format_NV21
		<< QVideoFrame::Format_IMC1
		<< QVideoFrame::Format_IMC2
		<< QVideoFrame::Format_IMC3
		<< QVideoFrame::Format_IMC4
		<< QVideoFrame::Format_Y8
		<< QVideoFrame::Format_Y16
		<< QVideoFrame::Format_Jpeg
		<< QVideoFrame::Format_CameraRaw
		<< QVideoFrame::Format_AdobeDng;
 
	//qDebug() << listPixelFormats;
 
	// Return the formats you will support
	return listPixelFormats;
}
 
bool  VideoSurface::present(const QVideoFrame &frame)
{
	// Handle the frame and do your processing
	if (frame.isValid())
	{
		QVideoFrame cloneFrame(frame);
		emit frameAvailable(cloneFrame);
 
		return true;
	}
 
	return false;
}

看了上面的代码就知道,只需要外部连接frameAvailable信号就可以获取到每一帧数据。

具体使用:

QMediaPlayer *mediaPlayer = new QMediaPlayer;
VideoSurface *videoSurface = new VideoSurface;
mediaPlayer->setVideoOutput(videoSurface);
mediaPlayer->setMedia(QUrl("rtsp://admin:admin@192.168.1.112"));
mediaPlayer->play();

把frameAvailable信号与显示窗口的槽连接,比如:

connect(videoSurface, SIGNAL(frameAvailable(QVideoFrame &)), this, SLOT(ProcessFrame(QVideoFrame &)));
void QtVideoTest::ProcessFrame(QVideoFrame &frame)
{
	qDebug() << "=============ProcessFrame===============";
	qDebug() << "width : " << frame.width() << " height : " << frame.height();
	qDebug() << "start time : " << frame.startTime()/1000 << "ms";
	qDebug() << "end time : " << frame.endTime()/1000 << "ms";
	qDebug() << "pixelFormat :" << frame.pixelFormat();
 
	frame.map(QAbstractVideoBuffer::ReadOnly);
	QImage recvImage(frame.bits(), frame.width(), frame.height(), QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat()));
 
	qDebug() << "frame data size :" << frame.mappedBytes();
	frame.unmap();
}

如上,QVideoFrame转QImage,拿QImage进行画图操作就简单了。

Qt6.2.3获取摄像头录制YUV文件,逐帧获取录制,使用QVideoSink

Qt6.2.3获取摄像头录制YUV文件,逐帧获取录制,使用QVideoSink
必须要使用到QVideoSink,相应的依赖模块有:QMediaCaptureSession、QMediaDevices、QCamera、QVideoFrame

具体的使用方法是:
1、创建一个QMediaCaptureSession:
QMediaCaptureSession m_captureSession;
2、设置摄像头:
m_captureSession.setCamera(new QCamera(QMediaDevices::defaultVideoInput()));
3、设置YUV逐帧输出:
m_captureSession.setVideoSink(new QVideoSink m_videoSink);
4、设置YUV格式:
void QVideoSink::setVideoFrame(const QVideoFrame &frame)
5、主动获取一帧YUV数据:
QVideoFrame QVideoSink::videoFrame() const
6、被动接收每一帧:
connect(m_videoSink, &QVideoSink::videoFrameChanged, YourClass, yourParseFunction);
7、获取帧数据:
videoFrame.map(QVideoFrame::ReadOnly);
m_file.write((const char *)videoFrame.bits(0和1), videoFrame.mappedBytes(0和1));

mainwindow.cpp:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QCameraFormat>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    m_camera = new QCamera(QMediaDevices::defaultVideoInput());
    m_captureSession.setCamera(m_camera);

    m_captureSession.setVideoSink(&m_videoSink);
    connect(&m_videoSink, &QVideoSink::videoFrameChanged, this, &MainWindow::on_frame_changed);

    m_camera->start();
    m_file.setFileName("test.nv12");
    m_file.open(QIODevice::WriteOnly | QIODevice::Truncate);
}

MainWindow::~MainWindow()
{
    m_camera->stop();
    m_file.close();

    delete ui;
}

void MainWindow::on_frame_changed(const QVideoFrame &frame)
{
    QVideoFrame videoFrame = frame;
    /**
     * 我的电脑上返回的格式是Format_NV12:18,每个像素1.5字节,一帧1382400字节,
     * 如果你需要用别的YUV或者RGB格式,则需要自己设置QVideoSink或者QCamera格式
     */
    QVideoFrameFormat::PixelFormat pixelFormat = videoFrame.pixelFormat();
    int width = videoFrame.width();
    int height = videoFrame.height();
    int planeCount = videoFrame.planeCount();
    uchar *pdata = nullptr;
    int len = 0;

    videoFrame.map(QVideoFrame::ReadOnly);

    for (int i = 0; i < planeCount; i++) {
        pdata = videoFrame.bits(i);
        len = videoFrame.mappedBytes(i);
        m_file.write((const char *)pdata, len);
    }

    frame_num++;
    qDebug("%d, %d, %d, %d, %d, %p, %d", frame_num,
           pixelFormat, width, height, planeCount, pdata, len);

    videoFrame.unmap();
}

mainwindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QCamera>
#include <QMediaCaptureSession>
#include <QMediaDevices>
#include <QVideoSink>
#include <QVideoFrame>
#include <QFile>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    QMediaCaptureSession m_captureSession;
    QVideoSink m_videoSink;
    QCamera *m_camera;
    QFile m_file;
    int frame_num = 0;

private slots:
    void on_frame_changed(const QVideoFrame &frame); // 一帧视频到来的信号
};
#endif // MAINWINDOW_H

main.cpp:

/**
 * \brief Qt6.2.3要逐帧获取YUV需要使用QVideoSink和QMediaCaptureSession
 * \details 编译运行后,窗口弹出即开始录制YUV文件,关闭窗口后文件保存
 * \note 保存的文件在本工程文件夹上级目录中的debug*目录下的test.nv12
 *       我的电脑摄像头默认输出是YUV NV12格式1280 * 720
 *       播放方式是安装ffmpeg软件后,在test.nv12文件目录下使用命令:
 *       ffplay -f rawvideo -pix_fmt nv12 -video_size 1280x720 test.nv12
 */

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

005_yuv_input_save_file.pro:

QT       += core gui
QT       += multimedia

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++17

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    main.cpp \
    mainwindow.cpp

HEADERS += \
    mainwindow.h

FORMS += \
    mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

mainwindow.ui:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QPushButton" name="pushButton">
      <property name="text">
       <string>窗口不要开太久!开几秒就够了,否则存储的YUV文件会把你的硬盘塞满!</string>
      </property>
     </widget>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>800</width>
     <height>22</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>

报错:`QVideoFrame::unmap() was called more times then QVideoFrame::map()

原因:格式对不上QVideoFrame转QImage格式对照表`
QVideoFrame Class

参考:QT——QCamera摄像头的切换、分辨率切换、截图显示

cv::QVideoFrame转QImage失败原因及解决方案

Qt是一款流行的C++跨平台开发框架,它的多样化的类库和工具链支持了广泛的应用程序类型和领域。在使用Qt开发图像视频应用时,对数据源的支持是至关重要的,因为它牵涉到访问和处理信号、视频、音频和其他流数据。本文将介绍如何使用Qt技术完成从 v4l2 摄像头获取视频数据以及处理的方法。 在开始介绍方法之前,需要先了解一下v4l2摄像头。v4l2是一种Linux内核框架,用于控制视频设备的采集、编码和显示等操作。v4l2摄像头主要用于Linux系统下的视频采集,它最初是为了支持USB摄像头而设计的。在使用v4l2摄像头时,我们需要通过系统的Video-For-Linux接口和相应的API进行操作。 Qt提供了一个QCamera类,支持从摄像头和文件获取视频数据,但是它不支持v4l2协议。因此,我们可以使用Qt的QWidget类进行自定义图形界面,使用v4l2的API获取视频数据,并将视频数据通过Qt的信号槽机制传递给QWidget对象进行显示。具体步骤如下: 1.定义v4l2摄像头结构体,设置参数,包括设备的名称、宽度、高度、帧率、格式等。 2.打开v4l2设备,检查设备是否打开正常。 3.通过ioctl()系统调用获取v4l2摄像头的参数,并设置相应控制,例如启动视频流。 4.使用Qt的定时器,通过定时器超时来触发读取v4l2摄像头视频数据。 5.使用QT的QImage类将读取的RGB格式的视频数据转换为可用于QWidget的图像并显示。 6.释放相关的资源,包括关闭v4l2设备。 总的来说,Qt与v4l2结合使用是一种可行的方法,可以支持Linux平台上的视频采集、处理和显示等功能。这种方法可以使用Qt丰富的类库和工具链进行开发,也可以使用v4l2提供的高效的图像采集框架实现更加灵活和高效的图像处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值