Qt5.0行车记录仪项目(二)摄像头每一帧数据的捕捉

这一功能需要使用摄像头 则需要链接多媒体模块多媒体工具模块 在pro文件中加上:

QT       += multimedia multimediawidgets

使用Qt的3个类实现画面显示
QCamera:摄像头对象
QCameraViewfinder:用于实时显示摄像头传来的图像的控件
QCameraImageCapture:用于捕获摄像头图像实现拍照功能

测试代码如下,在窗口类中定义即可

pCamera =new QCamera(this); //创建摄像头对象
pViewfinder =new QCameraViewfinder(this);    //创建实时显示摄像头图像的对象
pImageCapture =new QCameraImageCapture(pCamera); //创建截取摄像头图像的对象
//创建并设置按钮对象
QPushButton* button1 =new QPushButton("拍照");
QPushButton* button2 =new QPushButton("退出");
//创建并设置布局对象
QVBoxLayout* mainLayout =new QVBoxLayout(this);
mainLayout->addWidget(pViewfinder); //将取景器加入主界面中
mainLayout->addWidget(button1);
mainLayout->addWidget(button2);
pCamera->setViewfinder(pViewfinder); //给camera设置viewfinder
pCamera->start(); //开始摄像

分析:
1、在QWidget类上显示视频信息,一般使用paintEvent()显示图像,这样做CPU的占用率会很高,工程健壮性很低。
2、摄像头的捕捉数据需要存储成视频文件,使用这种方法不利于对图像帧的存储操作。

QOpenGLWidget

为解决CPU占用率过高的问题,显示视频的类,让它继承QOpenGLWidget,这样,在嵌入式设备上面,使用的是gpu渲染,而不是cpu,然后重新使用paintEvent()函数。

QOpenGLWidget类是用于渲染OpenGL图形。

除了可以选择使用QPainter和标准的OpenGL渲染图形,QOpenGLWidget类提供了在Qt应用程序中显示OpenGL图形的功能。它使用起来非常简单:新建类继承于QOpenGLWidget,使用方法就像继承于QWidget类子类一样。

QOpenGLWidget类提供了三个方便的虚函数,可以在新建的子类中重新实现以完成OpenGL的任务:
paintGL()—渲染OpenGL场景,需要更新Widget时就会调用。
resizeGL()—设置OpenGL视口,投影等。每当调整Widget的大小时(第一次显示窗口Widget时会调用它)。
initializeGL()—建立OpenGL的资源和状态。在第一次调用resizeGL()或paintGL()之前调用一次。

如果需要从paintGL()以外的地方触发重绘(一个典型的例子是使用定时器为场景设置动画),应该调用widget的update()函数来进行更新。
实现代码如下:

#ifndef OPENGLWIDGET_H
#define OPENGLWIDGET_H

#include <QWidget>
#include <QOpenGLWidget>
#include <QImage>

class OpenGLWidget : public QOpenGLWidget
{
    Q_OBJECT

public:
    OpenGLWidget(QWidget *parent = nullptr);
    QImage m_CameraFrame;

protected:
    void paintGL() override;

public slots:
    void showCameraFrameSlot(QImage image);
};

#endif // OPENGLWIDGET_H

#include "openglwidget.h"
#include <QPainter>

OpenGLWidget::OpenGLWidget(QWidget *parent)
{

}

void OpenGLWidget::paintGL()
{
    QPainter painter(this);
    if (m_CameraFrame.size().width() <= 0)
    {
        return;
    }
    QImage _image = m_CameraFrame.scaled(this->size());
    painter.drawImage(0, 0, _image);
}

void OpenGLWidget::showCameraFrameSlot(QImage image)
{
    m_CameraFrame = image;
    this->update();
}

Qt使用QAbstractVideoSurface捕获视频帧(信号槽方式)

Qt中捕获视频流方式:用QCamera::setViewfinder(QAbstractVideoSurface *surface)

QAbstractVideoSurface类是视频演示表面的基类。
QAbstractVideoSurface类定义视频制作者用于与视频演示表面交互操作的标准接口。您可以将此接口子类化,以接收来自解码媒体或相机等源的视频帧,以执行您自己的处理。

简单来说通过继承QAbstractVideoSurface类,重新实现他的一些虚函数对我们获取的视频帧数据进行处理,存储成我们需要的视频文件
实现代码如下:

#ifndef CAMERAVIDEOSURFACE_H
#define CAMERAVIDEOSURFACE_H

#include <QAbstractVideoSurface>
#include <QDebug>
#include "sqldata.h"

extern "C"
{
//avcodec:编解码(最重要的库)
#include <libavcodec/avcodec.h>
//avformat:封装格式处理
#include <libavformat/avformat.h>
//swscale:视频像素数据格式转换
#include <libswscale/swscale.h>
//avdevice:各种设备的输入输出
#include <libavdevice/avdevice.h>
//avutil:工具库(大部分库都需要这个库的支持)
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
//音频采样数据格式库
#include <libswresample/swresample.h>
}

class CameraVideoSurface : public QAbstractVideoSurface
{
    Q_OBJECT
public:
    CameraVideoSurface(QObject *parent = NULL);
    ~CameraVideoSurface();
    void setCameraResolution(const QSize &size);
    void escEvent();
    void InitEncoder();
protected:
    QList<QVideoFrame::PixelFormat> supportedPixelFormats(
    QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const override;
    bool present(const QVideoFrame &frame) override;
private slots:
    void cameraStopSlot();
signals:
    void showFrame(QImage image);
private:    
    void Encode(AVFrame *frame);    
    SqlData *pSqlData;
    int FirstImg;
private:
    AVFormatContext *pOutputFormatCtx;
    AVCodecContext *pCodecCtx;
    AVCodec *pCodec;
    AVStream *pOutputStream;
    AVPacket *packet;
    AVFrame *yuvFrame;
    QString FileName;
    QString OutPutFileName;
    struct SwsContext *image_convert_ctx;   
};

#endif // CAMERAVIDEOSURFACE_H
#include "cameravideosurface.h"
#include "allwidget.h"
#include <QDateTime>


CameraVideoSurface::CameraVideoSurface(QObject *parent) : QAbstractVideoSurface(
      parent),
      pOutputFormatCtx(NULL),
      pCodec(NULL),
      pOutputStream(NULL),
      packet(NULL),
      yuvFrame(NULL),
      image_convert_ctx(NULL)
{
//    this->InitEncoder();
}

CameraVideoSurface::~CameraVideoSurface()
{
    av_write_trailer(pOutputFormatCtx);
    avio_close(pOutputFormatCtx->pb);
    avformat_close_input(&pOutputFormatCtx);
    av_frame_free(&yuvFrame);
    av_packet_free(&packet);
    avcodec_close(pCodecCtx);
}

void CameraVideoSurface::setCameraResolution(const QSize &size)
{
    this->setNativeResolution(size);
}

QList<QVideoFrame::PixelFormat> CameraVideoSurface::supportedPixelFormats
(QAbstractVideoBuffer::HandleType handleType) const
{
    QList<QVideoFrame::PixelFormat > pixelFormats;
    pixelFormats.append(QVideoFrame::Format_RGB32);
    pixelFormats.append(QVideoFrame::Format_YUV420P);
    return pixelFormats;
}

bool CameraVideoSurface::present(const QVideoFrame &frame)
{
    if(!AllWidget::pLoginWidget->CurUser.isEmpty() && !AllWidget::pSettingWidget->pSettingPath->ImagePath.isEmpty()){
    //判断 是否有用户登录 是否处于设置路径界面
        if (frame.isValid()){
            QVideoFrame cloneFrame(frame);
            cloneFrame.map(QAbstractVideoBuffer::ReadOnly);
            QImage image(cloneFrame.bits(), cloneFrame.width(), cloneFrame.height(),
                         QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat()));
            image = image.mirrored(true, true);

            // rgb 转 yuv
            uint8_t *data[AV_NUM_DATA_POINTERS] = {0};
            data[0] = (uint8_t *)image.constBits();
            int linesize[AV_NUM_DATA_POINTERS] = {0};
            linesize[0] = pCodecCtx->width * 4;
            sws_scale(image_convert_ctx, data, linesize, 0, pCodecCtx->height,
                      yuvFrame->data, yuvFrame->linesize);
            // 编码
            this->Encode(yuvFrame);
            emit showFrame(image);
            if(FirstImg == 0){
                image.save(QString("%1.jpg").arg(FileName));	//保存第一帧图片 用于视频展示列表的封面
                FirstImg++;
            }
            cloneFrame.unmap();
            return true;
        }
        return false;
    }
    return false;
}

void CameraVideoSurface::InitEncoder()
{
    FirstImg = 0;

    av_register_all();
    avformat_network_init();
    avcodec_register_all();

    FileName = AllWidget::pSettingWidget->pSettingPath->VideoPath;
    QString CurDate = QDateTime::currentDateTime().toString("yyyyMMddhhmmss");
    FileName = QString("%1/%2").arg(FileName).arg(CurDate);
    OutPutFileName = QString("%1.h264").arg(FileName);
    qDebug() << OutPutFileName;

    QString encoderName = "libx264";
    //QString rtmpAddress = "rtmp://192.168.1.111/live/livestream";

    pCodec = avcodec_find_encoder_by_name(encoderName.toStdString().c_str());
    if(NULL == pCodec){
        qDebug() <<"查找视频编码器失败!";
        return;
    }

    pCodecCtx = avcodec_alloc_context3(pCodec);
    if(NULL == pCodecCtx){
        qDebug() <<"开辟编解码器上下文";
        return;
    }
    // 输入样本参数
    pCodecCtx->bit_rate = 400000;
    pCodecCtx->width = 1280/2;
    pCodecCtx->height = 720/2;
    pCodecCtx->time_base = {1, 25};
    pCodecCtx->framerate = {25, 1};
    pCodecCtx->gop_size = 10;
    pCodecCtx->max_b_frames = 1;
    pCodecCtx->qmin = 10;
    pCodecCtx->qmax = 51;
    pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;

    if(AV_CODEC_ID_H264 == pCodecCtx->codec_id){
        av_opt_set(pCodecCtx->priv_data, "preset", "slow", 0);
        av_opt_set(pCodecCtx->priv_data, "tune", "zerolatency", 0);
    }

    // 打开编码器
    if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0){
        qDebug() <<"打开编码器失败 !";
        return;
    }

    pOutputFormatCtx = avformat_alloc_context();
    if(NULL == pOutputFormatCtx){
        qDebug() <<"视频封装器开辟失败!";
        return;
    }

    AVOutputFormat *outputFormat = av_guess_format(NULL, OutPutFileName.toStdString().c_str(), NULL);
    if(NULL == outputFormat){
        qDebug() <<"猜测outputformat失败 !";
        return;
    }

    pOutputFormatCtx->oformat = outputFormat;
    // oprn url
    if(avio_open(&pOutputFormatCtx->pb, OutPutFileName.toStdString().c_str(), AVIO_FLAG_READ_WRITE) < 0){
        qDebug() <<"打开输出文件失败!";
        return;
    }

    pOutputStream = avformat_new_stream(pOutputFormatCtx, NULL);
    if(NULL == pOutputStream){
        qDebug() <<"新建输出流失败 !";
        return;
    }
    // 输出详细信息
    av_dump_format(pOutputFormatCtx, 0, OutPutFileName.toStdString().c_str(), 1);
    // 新建数据包
    packet = av_packet_alloc();
    if(NULL == packet){
        qDebug() <<"新建数据包失败 !";
        return;
    }
    // yuvFrame 初始化
    yuvFrame = av_frame_alloc();
    if(NULL == yuvFrame){
        qDebug() <<"开辟AVFrame失败 !";
        return;
    }
    yuvFrame->width = pCodecCtx->width;
    yuvFrame->height = pCodecCtx->height;
    yuvFrame->format = pCodecCtx->pix_fmt;
    // 初始化 image 空间
    av_image_alloc(yuvFrame->data, yuvFrame->linesize, yuvFrame->width, yuvFrame->height,
                       pCodecCtx->pix_fmt, 32);
    // 转换上下文
    image_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB32, pCodecCtx->width,
                                       pCodecCtx->height, AV_PIX_FMT_YUV420P,
                                       SWS_BICUBIC, NULL, NULL, NULL);
    if(NULL == image_convert_ctx){
        qDebug() <<"转换上下文失败 !";
        return;
    }
    // 写封装头
    if(avformat_write_header(pOutputFormatCtx, NULL) < 0){
        qDebug() <<"视频封装头写失败 !";
        return;
    }   
}

// 编码为 h.264
void CameraVideoSurface::Encode(AVFrame *frame)
{
    static int index = 0;
    frame->pts = index++;

    int ret = 0;
    if((ret = avcodec_send_frame(pCodecCtx, frame)) < 0){
        qDebug() <<"avcodec_send_frame 失败 !";
        return;
    }

    while(ret >= 0)
    {
        ret = avcodec_receive_packet(pCodecCtx, packet);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){
            return;
        }
        else if (ret < 0){
            qDebug() <<  "编码时出错";
            return;
        }
        packet->stream_index = 0;
        av_interleaved_write_frame(pOutputFormatCtx, packet); // write frame
        av_packet_unref(packet);
    }
}

//跳转至设置界面时调用 提前结束录像 并清空先前设置的路径 present()进程中的视频存储操作不会进行
void CameraVideoSurface::escEvent()
{
    qDebug()<<"关闭摄像头";
    av_write_trailer(pOutputFormatCtx);
    avio_close(pOutputFormatCtx->pb);
    av_frame_free(&yuvFrame);
    avcodec_close(pCodecCtx);
    QString CurDate = QDateTime::currentDateTime().toString("yyyyMMddhhmmss");
    pSqlData->insert_sourcetable(
                AllWidget::pLoginWidget->CurUser,
                CurDate,
                "video",
                AllWidget::pSettingWidget->pSettingPath->VideoPath);
    AllWidget::pSettingWidget->pSettingPath->ImagePath.clear();
    AllWidget::pSettingWidget->pSettingPath->VideoPath.clear();
    AllWidget::pSettingWidget->pSettingPath->TimeInterval.clear();
}

void CameraVideoSurface::cameraStopSlot()
{
    qDebug()<<"关闭摄像头";
    av_write_trailer(pOutputFormatCtx);
    avio_close(pOutputFormatCtx->pb);   
    av_frame_free(&yuvFrame);
    avcodec_close(pCodecCtx);
    QString CurDate = QDateTime::currentDateTime().toString("yyyyMMddhhmmss");
    if(!AllWidget::pLoginWidget->CurUser.isEmpty()){
        pSqlData->insert_sourcetable(
                    AllWidget::pLoginWidget->CurUser,
                    CurDate,
                    "video",
                    AllWidget::pSettingWidget->pSettingPath->VideoPath);
    }
    InitEncoder();
}

使用方法:

QVBoxLayout *VBoxLayoutPlay;
QCamera *pCamera;
OpenGLWidget *pOpenGLWidget;
//    QCameraViewfinder *pCameraViewfinder;
CameraVideoSurface *pCameraVideoSurface;
QCameraImageCapture *pCameraImageCapture; //获取摄像头当前帧

VBoxLayoutPlay = new QVBoxLayout(this);
pOpenGLWidget = new OpenGLWidget();
pCameraVideoSurface = new CameraVideoSurface();
pCamera = new QCamera(this);
pCameraImageCapture = new QCameraImageCapture(pCamera);

VBoxLayoutPlay->setGeometry(QRect(38, 20, 635, 341));
VBoxLayoutPlay->setContentsMargins(0, 0, 0, 0);
pCamera->setViewfinder(pCameraVideoSurface);
VBoxLayoutPlay->addWidget(pOpenGLWidget);
pCamera->start;

connect(pCameraVideoSurface, SIGNAL(showFrame(QImage)), pOpenGLWidget, SLOT(showCameraFrameSlot(QImage)));
connect(pCameraImageCapture, SIGNAL(imageCaptured(int,QImage)), this, SLOT(capture_slot(int,QImage)));

原文链接: https://blog.csdn.net/qq_34623621/article/details/106453823.
GitHub源码:https://github.com/Gernan-2135/Carcorder

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值