记一次Qt6开发摄像头(UVC标准协议)

ReadyForQMultimedia

成品图:
在这里插入图片描述
在这里插入图片描述

  • Qt6.2及以上新加入的QMediaCaptureSession 用来捕获和录制音视频。
  • The capturing and recording functionality has undergone the largest API changes in Qt 6.
  • 需要注意的是,Repeater控件只能用于展示静态的数据,它并不适合用于实时动态更新的情况。如果你需要动态更新控件的数据,可以考虑使用其他控件,例如ListView或GridView。

链接:

Point

I-派生类防止浅拷贝

Q_DISABLE_COPYQ_DISABLE_MOVEQ_DISABLE_COPY_MOVE。(这几个宏是Qt5.13后才有的,Qt6.7里不能用Q_DISABLE_MOVE这个宏了)

  • Q_DISABLE_COPY拷贝构造函数被删除,拷贝赋值函数被删除。

  • Q_DISABLE_MOVE移动构造函数被删除,移动赋值函数被删除。

  • Q_DISABLE_COPY_MOVE:拷贝构造函数、拷贝赋值函数、移动构造函数、移动赋值函数都被删除。

  • MyClass(const MyClass &) = delete; //拷贝构造函数被删除
    MyClass(MyClass &&) = delete; //移动构造函数被删除,注意没有const
    MyClass &operator=(const MyClass &) = delete; //拷贝赋值函数被删除
    MyClass& operator=(MyClass&&); //移动赋值函数被删除,注意没有const
    

II-属性绑定

关于NOTIFY

  • 在用C++写的类属性绑定中,在Q_PROPERTY()中添加了一个NOTIFY功能,每当属性的值发出改变时,就会发送该信号,从而使得绑定的目标属性自动更新其值。
  • 注意,在调用WRITE功能的函数(例如setXxxx())时,必须判断设置的值是否与当前属性的值相等,这样确保信号不会不必要的发出,从而导致可能死循环的事情发生。

III- QML/C++打开摄像头

结合C++和QML方式实现打开摄像头(Qt6.2及以上)

  • 代码如下:

  • main.qml

    import MyQuickUsbCamera 1.0
    //省略了外面的window
    Rectangle {
        id: usbCamera1_ID
        width: 320
        height: 240
        anchors.left: parent.left
        anchors.top: parent.top
        color: "burlywood"
    
        UsbCamera {
            //这里是自己实现的Type,在main.cpp中注册
            //qmlRegisterType<QuickUsbCamera>("MyQuickUsbCamera", 1, 0, "UsbCamera");
            id: camera1_ID
            videoOutput: videoOutput1_ID
        }
    
        VideoOutput {
            id: videoOutput1_ID
            anchors.fill: parent
        }
    
        Text {
            anchors.centerIn: parent
            text: "USB Camera is unavailable"
            color: "steelblue"
            font.pointSize: 32
            wrapMode: Text.WordWrap
            visible: {
                !(camera1_ID.isCameraAvailable)
            }
        }
    }
    
  • QuickUsbCamera.cpp

  • #include "QuickUsbCamera.h"
    
    #include <QVideoFrame>
    #include <QVideoSink>
    #include <QDebug>
    
    QuickUsbCamera::QuickUsbCamera(QObject *parent)
        : QObject{parent}, m_camera(new QCamera()), m_captureSession(new QMediaCaptureSession()), m_mediaDevices(new QMediaDevices())
    {
        //输入端口更改则重新初始化Camera
        QObject::connect(m_mediaDevices, &QMediaDevices::videoInputsChanged, this, &QuickUsbCamera::initCamera);
        QObject::connect(m_camera, SIGNAL(errorOccurred(QCamera::Error,QString)), this, SLOT(printErrorToConsole(QCamera::Error,QString)));
        this->initCamera();
    }
    
    QuickUsbCamera::~QuickUsbCamera()
    {
        delete m_mediaDevices;
        delete m_captureSession;
    }
    
    QObject *QuickUsbCamera::getVideoOutput()
    {
        return m_captureSession->videoOutput();
    }
    
    bool QuickUsbCamera::getIsCameraAvailable()
    {
        return m_isCameraAvailable;
    }
    
    bool QuickUsbCamera::setVideoOutput(QObject *videoOutput)
    {
        //必须判断设置的值是否与当前属性的值相等,这样确保信号不会不必要的发出,从而导致可能死循环的事情发生。
        qDebug() << "setVideoOutput:" << videoOutput->objectName();
        if (videoOutput != m_captureSession->videoOutput()) {
            qDebug() << "not equal";
            m_captureSession->setVideoOutput(videoOutput);
            emit videoOutputChanged(videoOutput);
            return true;
        } else {
            return false;
        }
    }
    
    void QuickUsbCamera::printErrorToConsole(QCamera::Error error, const QString &message)
    {
        qDebug() << "Camera open error:" << error;
        qDebug() << "error message:" << message;
    }
    
    void QuickUsbCamera::initCamera()
    {
        // if (nullptr != m_captureSession->videoOutput()) {
        //     qDebug() << "videoOutput isn't null";
        //     // auto videoSink = m_captureSession->videoOutput()->property("videoSink").value<QVideoSink *>();
        //     auto videoSink = m_captureSession->videoSink();
        //     if (nullptr != videoSink) {
        //         qDebug() << "videoSink isn't null";
        //         videoSink->setVideoFrame(QVideoFrame());
        //     }
        // }
    
        auto videoInputs = m_mediaDevices->videoInputs();
        qDebug() << "videoInputs size:" << videoInputs.size();
        m_isCameraAvailable = false; //重置一下
    
        for (const auto &it : videoInputs) {
            qDebug() << "id:" << it.id();
            qDebug() << "description:" << it.description();
            qDebug() << "isDefault:" << it.isDefault() << "isNull:" << it.isNull();
            qDebug() << "photoResolutions:" << it.photoResolutions();
            qDebug() << "position:" << it.position() << "\n";
        }
    
        for (const auto &it : videoInputs) {
            if (it.id().contains("usb")) {
                m_camera = new QCamera(it);
                m_captureSession->setCamera(m_camera);
                m_camera->setActive(true);
                // m_camera->start(); //Same as setActive(true).
                m_isCameraAvailable = true;
                break;
            }
        }
        emit this->isCameraAvailableChanged(m_isCameraAvailable);
    }
    
  • QuickUsbCamera.h

  • #ifndef QUICKUSBCAMERA_H
    #define QUICKUSBCAMERA_H
    
    #include <QObject>
    #include <QCamera>
    #include <QMediaCaptureSession>
    #include <QMediaDevices>
    #include <qqml.h>
    
    class QuickUsbCamera : public QObject
    {
        Q_OBJECT
    
        Q_DISABLE_COPY(QuickUsbCamera);
        Q_PROPERTY(QObject *videoOutput READ getVideoOutput WRITE setVideoOutput NOTIFY videoOutputChanged);
        //是否可用,注意避免信号无意义的emit,导致无限递归
        Q_PROPERTY(bool isCameraAvailable READ getIsCameraAvailable NOTIFY isCameraAvailableChanged);
        QML_ELEMENT
    
    public:
        explicit QuickUsbCamera(QObject *parent = nullptr);
        ~QuickUsbCamera();
    
    public slots:
        QObject *getVideoOutput();
        bool getIsCameraAvailable();
    
        bool setVideoOutput(QObject *videoOutput);
    
        void printErrorToConsole(QCamera::Error error, const QString &message);
    
    private slots:
        void initCamera();
    
    signals:
        void videoOutputChanged(QObject *);
        void isCameraAvailableChanged(bool);
    
    private:
        QCamera *m_camera{nullptr};
        QMediaCaptureSession *m_captureSession{nullptr}; //capture、record
        QMediaDevices *m_mediaDevices{nullptr};
        bool m_isCameraAvailable{false};
    };
    
    #endif // QUICKUSBCAMERA_H
    
  • pro文件中:

  • QT += quick multimedia multimediawidgets
    

IV-纯QML打开摄像头

  • 研究了下Qt6的官方文档,根据例子发现直接用纯QML方式可实现打开摄像头等功能。

  • //下面是官方给的例子
    MediaDevices {
        id: mediaDevices
    }
    CaptureSession {
        camera: Camera {
            cameraDevice: mediaDevices.defaultVideoInput
        }
        videoOutput: videoOutput_ID
    }
    VideoOutput {
        id: videoOutput_ID
        anchors.fill: parent
    }
    
  • 当然pro文件里面还是要把该用的模块加上去的:QT += quick multimedia multimediawidgets

  • main.qml源码(省略了外层):

  • import QtMultimedia
    
    MediaDevices {
        id: mediaDevices_ID
    }
    CaptureSession {
        camera: Camera {
            id: camera2_ID
            // cameraDevice: mediaDevices_ID.videoInputs[0]
    
            cameraDevice: {
                for (var i = 0; i < mediaDevices_ID.videoInputs.length; i++) {
                    console.log("id:" + mediaDevices_ID.videoInputs[0].id.toString());
                    if (mediaDevices_ID.videoInputs[i].id.toString().indexOf("usb") !== -1) {
                        console.log("find usb " + i + ", id: " + mediaDevices_ID.videoInputs[i].id.toString());
                        return mediaDevices_ID.videoInputs[i];
                    }
                }
            }
        }
        videoOutput: videoOutput2_ID
    }
    
    VideoOutput {
        id: videoOutput2_ID
        anchors.fill: parent
    }
    
    Component.onCompleted: {
        camera2_ID.start();
    }
    
    Text {
        anchors.centerIn: parent
        text: "USB Camera is unavailable"
        color: "steelblue"
        font.pointSize: 32
        wrapMode: Text.WordWrap
        visible: {
            camera2_ID.error === Camera.NoError ? false : true
        }
    }
    

V-one source, two camera

  • one input camera source, two video output;

  • 突发奇想能不能做个下拉框,同时选择相同的就可以显示两个相同的画面了。

  • 实践了下,确实可以。结合上个点的源码:

  • ShaderEffectSource {
            id: usbCamera2_Shader_ID
            sourceItem: usbCamera2_ID
            anchors.left: parent.left
            anchors.top: parent.top
            width: 320
            height: 240
        }
    
  • 参考链接:One source, two video output in QML - Stack Overflow

VI-关于Qt6使用带参数信号处理问题

原文报错是:

Parameter "xxxxxxx" is not declared. Injection of parameters into signal handlers is deprecated. Use JavaScript functions with formal parameters instead.

原因是因为:

  • Qt6开始,QML中使用信号处理函数参数时,需要显式捕获参数,写法如下:

  • onPositionChanged: function(xxxxxxx){
     ...
    }
    
  • There are no warning messages in QT5.

VII-QML中延时的功能

  • 因为两次拍照时间间隔问题,导致不能直接拍完照后立马capture()

  • 所以设计一个延时功能:

  • Timer {
        id: delayTimer_ID
    }
    
    //延时功能
    function delay_func(delayTime, cb) {
        delayTimer_ID.interval = delayTime;
        delayTimer_ID.repeat = false;
        delayTimer_ID.triggered.connect(cb);
        delayTimer_ID.start();
    }
    
    //具体使用
    Button {
        onClicked: delay(5000, function() { xxxx.visible = true;} );
    }
    
  • 注意:这里函数直接作为响应连接的地方会有问题,建议不这么用(2024年4月16日11点21分)

拓展:qml中有个延时按钮,其中不同信号发射的时机是不一样的,可以活用一下。

  • DelayButton {
        delay: 1000
        onClicked: console.log("clicked");
        onActivated: console.log("activated");
        onPressed: console.log("pressed");
        onReleased: console.log("released");
    }
    

VIII-关于Image设置source后更改图像内容不同步

Image {
    id: image_ID
    cache: false //首先这里要设置不缓存
    source: ""
}

function xxxx() {
    image_ID.source = ""; //这里作用是清空一下缓存
    image_ID.source = "xxxxxxx"; //然后再设置一次资源
}

IX-关于打包QML做的程序

  • 先把Release版的程序单独拷贝到一个目录里。

  • 然后使用qt自带的打包工具(类似命令行),注意区分32bit/64bit

  • cd xxxxxxx #到程序所在位置
    
  • windeployqt  xxx.exe  --qmldir=xxxxxxx
    #注意,这里的目录是你工程目录里面的那些qml文件所在位置,一般跟main.qml同级
    
  • 这样就可以了,如果打包完成后打不开程序那就直接把所有的dll都拷贝进去然后一个个试吧。

  • 20
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Taiga_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值