Qt 之相机Camera预览、拍照

概述

项目中使用到相机的预览和拍照功能,在嵌入式下由于是第三方提供的平台,不是标准的,在qml下,Camera拿不到相机信息,只能通过底层适配,但是目前适配的流程是每次控制相机都要去遍历所有相机,然后根据某些规则去找到是自己要控制的相机,所以导致相机的预览、开关、拍照等操作均反应迟缓,作为Qt开发者来说,去做了三方面的尝试:
只能说被这个第三方嵌入式平台坑惨了- -

1 纯Qml控制,通过qt的mutimedia 模块中的Camera配合VideoOutput来实现;
2 通过QCamera 和 QAbstractVideoSurface ,然后配合qml的 VideoOutput来实现;
3 通过QCamera和 QAbstractVideoSurface以及 QQuickImageProvider ,配合qml的 Image来实现;
4 通过V4l2库,QQuickImageProvider ,配合qml的 Image来实现;

项目中的想法的相机预览时候一个低分辨率,拍照的时候采用一个高分辨率,然后拍完照再设置一个低分辨率的预览,qt这方面没有实现,可能是笔者不知道方法
void QCamera::setViewfinderSettings(const QCameraViewfinderSettings &settings)
Sets the viewfinder settings.
If some parameters are not specified, or null settings are passed, the camera will choose default values.
If the camera is used to capture videos or images, the viewfinder settings might be ignored if they conflict with the capture settings. You can check the actual viewfinder settings once the camera is in the QCamera::ActiveStatus status.
Changing the viewfinder settings while the camera is in the QCamera::ActiveState state may cause the camera to be restarted

摄像头模式是捕捉视频或图片, 如何取景器的设置与拍照设置相冲突,则可能会被忽略。

第一种 纯qml实现

参考qt自己的demo 即可
Camera模式模式 Camera.CaptureStillImage
可以通过devideId来指定camera
linux下默认是后摄
captureToLocation()是capture()的重载,可以指定抓图的路径
笔者的板子上还要设置VideoOutput的orientation,并且还要设置成360°,见鬼了。
camera.viewfinder.resolution = Qt.size(2592, 1296); 设置viewfinder的分辨率,拍照的分辨率也一样被设置了。

这种实现方法是最简单的,代码最少,基本上qml的demo已经满足你所有的需求,但是笔者的板子上QtMultimedia.availableCameras竟然是空 - -,坑爹啊,导致预览和相机的开关都很慢

Camera {
        id: camera
        //deviceId:QtMultimedia.availableCameras[0].deviceId
        imageCapture {
            onImageCaptured: {
            //your process
            }
        }
  }

    VideoOutput {
        id: viewfinder
        anchors.fill: parent
        source: camera
        //autoOrientation: true
        orientation: 360
        MouseArea
        {
            anchors.fill: parent
            onClicked:
            {
                var filepath = UILogic.getCurImageName()
                camera.imageCapture.captureToLocation(filepath)
                photoPreview.imagePath = filepath+".jpg";
            }
        }
    }

第二种 通过QCamera 和 QAbstractVideoSurface ,然后配合qml的 VideoOutput来实现

VideoOutput 的介绍中说明, source 属性可以是一个自定义派生与 QObject 的子类,并提供一个类型为 QMediaObject 的属性命名为 mediaObject ,或者是一个派生与 QObject 的子类并提供一个类型为 QAbstractVideoSurface 的属性命名为 videoSurface 给 QML

class FrameProvider: public QObject {
    Q_OBJECT
    Q_PROPERTY(QAbstractVideoSurface *videoSurface READ getVideoSurface WRITE setVideoSurface NOTIFY videoSurfaceChanged)
    
public:
    FrameProvider(QObject *parent = nullptr)
        : QObject(parent) {}
    
    QAbstractVideoSurface* getVideoSurface() const { return m_surface; }
    void setVideoSurface(QAbstractVideoSurface *surface) {
        if (m_surface != surface && m_surface && m_surface->isActive()) {
            m_surface->stop();
        }
        
        m_surface = surface;
        QList<QVideoFrame::PixelFormat> list = m_surface->supportedPixelFormats();
        if (m_surface && m_format.isValid()) {
            m_format = m_surface->nearestFormat(m_format);
            m_surface->start(m_format);
        }
        emit videoSurfaceChanged();
    }
    
    void setFormat(int width, int height, QVideoFrame::PixelFormat frameFormat) {
        QSize size(width, height);
        QVideoSurfaceFormat format(size, frameFormat);
        if (m_surface) {
            if (m_surface->isActive())
                m_surface->stop();
            m_format = m_surface->nearestFormat(m_format);
            m_surface->start(m_format);
        }
    }
    
public slots:
    void setFrame(const QVideoFrame &frame) {
        if (m_surface) 
        //do Your process 格式转换
            m_surface->present(frame);
    }
    
signals:
    void videoSurfaceChanged();
    
private:
    QAbstractVideoSurface *m_surface = NULL;
    QVideoSurfaceFormat m_format;
};

将对象注入到全局 Context 提供 QML 使用

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    auto camera = new QCamera();

    FrameProvider fp;
    fp.setFormat(1280,720, QVideoFrame::PixelFormat::Format_YUV420P);

    auto probe = new QVideoProbe();
    if(probe->setSource(camera))
    {
        QObject::connect(probe, SIGNAL(videoFrameProbed(const QVideoFrame &)), &fp, SLOT(setFrame(const QVideoFrame &)));
    }

    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("frameProvider", &fp);

    camera->start();
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

main.qml文件

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    VideoOutput {
        id: display
        visible: true
        objectName: "display"
        anchors.fill :parent
        source: frameProvider
    }
}

第二种方法有时候会出现不出图,问题定位思路:
1 m_surface->start(m_format); 判断是否start成功
2 setFrame(const QVideoFrame &frame) 判断frame的图像格式,是否与设置的一致

主要是QAbstractVideoSurface 的图像格式和相机图像格式冲突,需要转换,在 setFrame 函数中提前转换,
思路如下(仅提供参考):
QVideoFrame 的变形与QImage之间的转换

QVideoFrame f(size, QSize(width, height), width, QVideoFrame::Format_YUV420P); 
if (f.map(QAbstractVideoBuffer::WriteOnly)) {
	 memcpy(f.bits(), data, size); 
	 f.setStartTime(0); 
	 f.unmap(); 
	 emit newFrameAvailable(f); 
}


bool present(const QVideoFrame &frame)
{
    if (frame.isValid()) {
        QVideoFrame cloneFrame(frame);
        cloneFrame.map(QAbstractVideoBuffer::ReadOnly);
        int format= QVideoFrame::imageFormatFromPixelFormat(cloneFrame.pixelFormat());
        if (format != QImage::Format_Invalid)
        {
            QImage image(cloneFrame.bits(),
                         cloneFrame.width(),
                         cloneFrame.height(),
                         cloneFrame.bytesPerLine(),
                         format);
            //do something
        }
        else {
            if (cloneFrame.pixelFormat() == QVideoFrame::Format_YUYV)
            {
                int nbytes = cloneFrame.mappedBytes();
                char *rgb24 = new char[nbytes*2]();
                convert_yuv_to_rgb_buffer((unsigned char*)cloneFrame.bits(), (unsigned char*)rgb24, cloneFrame.width(), cloneFrame.height());
                //不用QT的话,这里可以直接把图片rgb24buffer写入文件image.bmp中,
                //注意保存格式,bmp是位图,而类似jpg,png之类的是经压缩过的。
                QImage img((unsigned char*)rgb24, cloneFrame.width(), cloneFrame.height(), cloneFrame.width()*3, QImage::Format_RGB888);
                emit frameAvailable(img);
                delete[] rgb24;
            }
            else
            {
                int nbytes = cloneFrame.mappedBytes();
                QImage image = QImage::fromData(cloneFrame.bits(), nbytes);
                emit frameAvailable(image);
            }
        }
        cloneFrame.unmap();
        return true;
    }
    return false;
}

第三种 通过QCamera和 QAbstractVideoSurface以及 QQuickImageProvider ,配合qml的 Image来实现

核心代码如下:
踏坑经验:图片刷新不出来
qml 的Image处理
或者 Image 设置
注意,这里刷新图片的时候必须先设置为空img.source = “”,否则无法刷新。 Image 属性加上一句:cache:false 就搞定了,不显示的原因是 Image 自动做了缓存,把缓存去掉就行了。
这样一来,C++端设置新图片后会发送信号,在QML端连接信号然后去图片目录下去取出来刷新当前控件

Image{
        id:img
        anchors.fill: parent
    }
    Connections{
        target: CodeImage
        onCallQmlRefeshImg:{
            img.source = "image://CodeImg/"+ Math.random()
        }
    }
class QtCamera : public QObject
{
    Q_OBJECT
public:
    explicit QtCamera(QCameraInfo cameraInfo = QCameraInfo::defaultCamera(), QObject *parent = 0);
    ~QtCamera();
    
    Q_INVOKABLE bool start();
    Q_INVOKABLE bool stop();
    Q_INVOKABLE bool isStarted();
    Q_INVOKABLE bool capture(QString filePath);
    QtImageProvider *m_pImageProvider;
signals:
    void imageOutput(QImage image);
private slots:
    void grabImage(QImage image);
private:
    QCamera *m_camera;
    QtCameraCapture *m_cameraCapture;
    QCameraInfo   m_cameraDeviceInfo;
    bool      m_started;
};
QtCamera::QtCamera(QCameraInfo cameraInfo, QObject *parent) : QObject(parent)
{
    m_started = false;
    m_camera = NULL;
    m_cameraCapture = NULL;

    if (cameraInfo.isNull()) {
        qDebug() << __LINE__ << __FUNCTION__;
        return;
    }

    m_cameraDeviceInfo = cameraInfo;
    m_cameraCapture = new QtCameraCapture;
    connect(m_cameraCapture, SIGNAL(frameAvailable(QImage)), this, SLOT(grabImage(QImage)));

    m_pImageProvider = new QtImageProvider;
}

QtCamera::~QtCamera()
{
    if (m_camera != NULL) {
        delete m_camera;
        m_camera = NULL;
    }

    if (m_cameraCapture != NULL) {
        delete m_cameraCapture;
        m_cameraCapture = NULL;
    }
}

bool QtCamera::start()
{
    if (m_started) {
        return false;
    }
    else {
        if (m_cameraDeviceInfo.isNull()) {
            return false;
        }
        else {
            m_camera = new QCamera(m_cameraDeviceInfo);
            m_camera->setViewfinder(m_cameraCapture);
            m_camera->start();
            m_started = true;
        }

    }

    return true;
}

bool QtCamera::stop()
{
    m_camera->stop();
    m_started = false;
    return true;
}

bool QtCamera::isStarted()
{
    return m_started;
}

bool QtCamera::capture(QString filePath)
{
    if (! m_started) {
        return false;
    }
    //return m_image.save(filePath, "jpg");
}

void QtCamera::grabImage(QImage image)
{
    //m_image = image.mirrored(false, true);

    m_pImageProvider->img = image;
    emit imageOutput(image);
}

自定义的QAbstractVideoSurface,处理抓到frame数据

class QtCameraCapture : public QAbstractVideoSurface
{
    Q_OBJECT
public:
    enum PixelFormat {
        Format_Invalid,
        Format_ARGB32,
        Format_ARGB32_Premultiplied,
        ...
        Format_User = 1000
    };

    Q_ENUM(PixelFormat)
    explicit QtCameraCapture(QObject *parent = 0);
    QList<QVideoFrame::PixelFormat> supportedPixelFormats(
            QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const;
    bool present(const QVideoFrame &frame) override;
signals:
    void frameAvailable(QImage frame);
};
QtCameraCapture::QtCameraCapture(QObject *parent) : QAbstractVideoSurface(parent)
{

}

QList<QVideoFrame::PixelFormat> QtCameraCapture::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const
{
    Q_UNUSED(handleType);
    return QList<QVideoFrame::PixelFormat>()
            << QVideoFrame::Format_ARGB32
            << QVideoFrame::Format_ARGB32_Premultiplied
            ...
            << QVideoFrame::Format_AdobeDng;
}


bool QtCameraCapture::present(const QVideoFrame &frame)
{
    if (frame.isValid()) {
        QVideoFrame cloneFrame(frame);
        cloneFrame.map(QAbstractVideoBuffer::ReadOnly);
        int format= QVideoFrame::imageFormatFromPixelFormat(cloneFrame.pixelFormat());
        qDebug() << cloneFrame.size() <<  cloneFrame.pixelFormat();
        if (format != QImage::Format_Invalid)
        {
            const QImage image(cloneFrame.bits(),
                                       cloneFrame.width(),
                                       cloneFrame.height(),
                                       QVideoFrame::imageFormatFromPixelFormat(cloneFrame.pixelFormat()));
                    emit frameAvailable(image);
                    cloneFrame.unmap();
                    return true;
        }
        else {
            if (cloneFrame.pixelFormat() == QVideoFrame::Format_YUYV)
            {
                qDebug() << "QVideoFrame::Format_YUYV";
                ...
            }
            else
            {
                int nbytes = frame.mappedBytes();
                emit frameAvailable(QImage::fromData(frame.bits(), nbytes));
            }
        }
        cloneFrame.unmap();
        return true;
    }
    else {
        qDebug() << "frame is not valid";
    }
    return false;
}

实际上,关键的地方在于能够提供正确的 QVideoFrame,并设置正确的 Format,

而 QVideoFrame 支持的格式相当多,例如 NV12、NV21、YUYV 等等,然后使用 QAbstractVideoSurface::present(const QVideoFrame &frame) 传递给 VideoOutput 即可呈现。

#include <QQuickImageProvider>

class QtImageProvider : public QQuickImageProvider
{
public:
    explicit QtImageProvider(QObject *parent = nullptr);

    QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);
    QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize);

    QImage img;
signals:

};
#include "qtimageprovider.h"
QtImageProvider::QtImageProvider(QObject *parent) : QQuickImageProvider(QQuickImageProvider::Image)
{
}

QImage QtImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
    return this->img;
}

QPixmap QtImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
{
    return QPixmap::fromImage(this->img);
}

注册ImageProvider

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    
    QtCamera * pcamera = new QtCamera;
    engine.rootContext()->setContextProperty("zcamera",pcamera);
    engine.addImageProvider(QLatin1String("CodeImg"), pcamera->m_pImageProvider);
    
    
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);
    
    return app.exec();
}

qml文件

Image{
    id:img
    anchors.fill: parent
}
Connections{
    target: zcamera
    onImageOutput:{
        img.source = ""
        img.source = "image://CodeImg"
    }
}

第四种 通过V4l2库,QQuickImageProvider ,配合qml的 Image来实现

思路是定时去设备拿图像,然后刷新界面


	this->timer = new QTimer(this);
//	connect(this->timer, SIGNAL(timeout()), this, SLOT(update()));
    connect(timer, SIGNAL(timeout()), this, SLOT(update()));
    this->timer->start(100);
    
void ShowVideo::paintEvent(QPaintEvent *)
{
    qDebug()<<"Start updat"<<endl;
	/*	获取视频采集的初始数据	*/
	this->myVideoDevice->get_frame((void **)(&(this->pVideoData)), &uDataLength);
	
	/*	数据转换	*/
    convert_yuv_to_rgb_buffer(this->pVideoData, this->pChangedVideoData, 640, 480);
	
	/*	加载数据	*/
    this->pImage->loadFromData(this->pChangedVideoData, 640 * 480 * 3 * sizeof(char));
	
	/*	显示数据	*/
    this->pLabel->setPixmap(QPixmap::fromImage(*pImage));
	
	/*	处理后的数据帧重新放到队列末尾		*/
    this->myVideoDevice->unget_frame();
}

总结

相机在不同平台上,表现大有不同,在Android上相机有多个通道,预览和拍照走不同的通道来实现,可分别控制。但是qt在嵌入式下的平台上只有一个通道,所以基本实现不了预览和拍照分辨率的相互切换。笔者的代码也基本上传到了csdn供下载
第二种方法 QCamera+VideoOutput

第三种方法QCamera和+QAbstractVideoSurface+ QQuickImageProvider + Image

第四种方法 V4l2

QT V4L2是指QT开发平台上的V4L2(Video for Linux 2)视频驱动程序,用于支持Linux平台上的低层次音频和视频设备的接口规范。QT是一款跨平台的C++图形应用程序开发框架,在移动平台、桌面平台、嵌入式系统等各种场景下都得到了广泛应用。V4L2是Linux下用于处理视频设备(例如摄像头)的API,同时也支持音频设备。V4L2可以直接调用Linux内核中的设备驱动程序,实现数据的采集、处理、传输等功能。 QT V4L2 Camera是指两者结合起来,实现在Linux平台上进行摄像头数据采集和实时视频处理的应用。基于QT V4L2 Camera,开发者可以实现各种各样的应用,例如视频监控、视频会议、人脸识别、图像识别等领域。通过QT V4L2 Camera,开发者可以方便地实现数据采集、处理、呈现和存储等功能,并且具有高度的灵活性和可扩展性。 在实际应用中,QT V4L2 Camera的优势不仅在于其功能强大,还在于它跨平台、开放源代码、易学易用、具有丰富的社区支持等方面,大大降低了开发者的开发成本和学习门槛,同时可以保证应用的可移植性和可维护性。 综上所述,QT V4L2 Camera是一款非常重要的视频采集和处理框架,它为开发者提供了丰富的功能和高度的灵活性,同时又具备跨平台、易学易用、开放源代码等优势,是开发基于Linux平台的视频应用的最佳选择之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值