最近需要使用opencv,就简单写了一个测试示例并记录下来。
其中 QCamera 等相关的类同样可以实现当前功能。

界面展示.png

image.png

仓库

功能

  • 支持有线或IP摄像头连接
  • 支持视频流播放
  • 支持多分辨率设置
  • 支持视频录制
  • 支持拍照
  • 视频抽帧、拍照、录制等由独单线程处理

测试可用的视频流

CCTV1  高清 http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8
CCTV3  高清 http://ivi.bupt.edu.cn/hls/cctv3hd.m3u8
CCTV5+ 高清 http://ivi.bupt.edu.cn/hls/cctv5phd.m3u8
CCTV6  高清 http://ivi.bupt.edu.cn/hls/cctv6hd.m3u8
camara.h
#ifndef CAMARA_H
#define CAMARA_H

#include <QImage>
#include <QObject>
#include <QThread>
#include <QTimer>

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

Q_DECLARE_METATYPE(QImage);

/*
 *@brief Camara 在单独线程中完成视频抽帧、拍照及录像功能
 *@brief open、close 等是耗时操作,请保证在 Camara 线程时间中完成调用 (可考虑异步槽函数、QMetaObject::invokeMethod)
 */

class Camara : public QObject
{
    Q_OBJECT

public:
    Camara();
    ~Camara();

    bool isOpened()        const;
    bool isTakevideoed()   const;
    QString getSavePath()  const;
    QSize getResolutions() const;
    bool isTakeVideo()     const;

signals:
    void updateImage(QImage);
    void statusChanged(bool isOpen);

public slots:
    void openCamara(const QString &url);
    void openCamara(int index);
    void closeCamara();
    void takePicture();
    void takeVideo();
    void setSavePath(const QString &path);
    void setResolutions(const QSize &size);

private slots:
    void tbegin();
    void tend();
    void captureCamara();

private:
    QString m_savepath;
    QAtomicInteger<bool> m_isTakepicture = false;
    QAtomicInteger<bool> m_isTakevideo   = false;
    QAtomicInteger<bool> m_isflip        = false;

    QScopedPointer<cv::VideoCapture> m_capture;
    QScopedPointer<cv::VideoWriter>  m_writer;

    QTimer  *m_timer = nullptr;
    QThread m_thread;
};

#endif // CAMARA_H
#include "camara.h"

#include <QDateTime>

Camara::Camara()
{
    moveToThread(&m_thread);

    connect(&m_thread, &QThread::started,  this, &Camara::tbegin);
    connect(&m_thread, &QThread::finished, this, &Camara::tend);

    m_thread.start(QThread::HighPriority);
}

Camara::~Camara()
{
    m_thread.quit();
    m_thread.wait();
}

void Camara::tbegin()
{
    m_capture.reset(new cv::VideoCapture);
    m_writer.reset(new cv::VideoWriter);
    m_timer = new QTimer(this);

    m_timer->setTimerType(Qt::PreciseTimer);

    connect(m_timer, &QTimer::timeout, this, &Camara::captureCamara);
}

void Camara::tend()
{
    closeCamara();
}

void Camara::openCamara(const QString &url)
{
    if (!m_capture->isOpened() && m_capture->open(url.toLatin1().data()))
    {
        m_isflip = false;

        m_timer->start(33);

        emit statusChanged(true);
    }
    else
    {
       emit statusChanged(false);
    }
}

void Camara::openCamara(int index)
{
    if (!m_capture->isOpened() && m_capture->open(index))
    {
        m_isflip = true;

        m_timer->start(33);

        emit statusChanged(true);
    }
    else
    {
        emit statusChanged(false);
    }
}

void Camara::closeCamara()
{
    m_timer->stop();

    if (m_writer->isOpened())
        m_writer->release();

    if (m_capture->isOpened())
        m_capture->release();
}

void Camara::captureCamara()
{
    cv::Mat originalframe;
    cv::Mat flipframe;

    *m_capture >> originalframe;

    if (m_isflip)
        cv::flip(originalframe, flipframe, 1);
    else
        flipframe = originalframe;

    QImage img = QImage(flipframe.data, flipframe.cols, flipframe.rows, QImage::Format_RGB888).rgbSwapped();

    if (!img.isNull())
    {
        if (m_isTakepicture)
        {
            m_isTakepicture = !m_isTakepicture;

            QString name = m_savepath + QDateTime::currentDateTime().toString("yyyy-MM-hh hh_mm_ss") + ".jpeg";

            img.save(name, "jpeg");
        }

        if (m_isTakevideo)
        {
            *m_writer << flipframe;
        }

        updateImage(img);
    }

    originalframe.release();
    flipframe.release();
}

void Camara::takePicture()
{
    m_isTakepicture = true;
}

void Camara::takeVideo()
{
    if (!m_isTakevideo)
    {
        QString name =  m_savepath + QDateTime::currentDateTime().toString("yyyy-MM-hh hh_mm_ss") + ".avi";

        if (m_writer->open(name.toLatin1().data(), cv::VideoWriter::fourcc('M', 'J', 'P', 'G'), 30.0, cv::Size(m_capture->get(cv::CAP_PROP_FRAME_WIDTH), m_capture->get(cv::CAP_PROP_FRAME_HEIGHT)), true))
        {
            m_isTakevideo = true;
        }
    }
    else
    {
        m_isTakevideo = false;

        m_writer->release();
    }
}

void Camara::setSavePath(const QString &path)
{
    m_savepath = path + '/';
}

void Camara::setResolutions(const QSize &size)
{
    if (m_capture->isOpened())
    {
        m_capture->set(cv::CAP_PROP_FRAME_WIDTH, size.width());
        m_capture->set(cv::CAP_PROP_FRAME_HEIGHT, size.height());
    }
}

QString Camara::getSavePath() const
{
    return m_savepath;
}

bool Camara::isOpened() const
{
    return m_capture->isOpened();
}

bool Camara::isTakevideoed() const
{
    return m_isTakevideo;
}

QSize Camara::getResolutions()  const
{
    QSize ret;

    ret.setWidth(m_capture->get(cv::CAP_PROP_FRAME_WIDTH));
    ret.setHeight(m_capture->get(cv::CAP_PROP_FRAME_HEIGHT));

    return ret;
}

bool Camara::isTakeVideo() const
{
    return m_writer->isOpened();
}

TianSong
737 声望139 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧