QT QCamera与QZXing库 结合实现实时扫码功能

QT需要实现扫码功能,在网络上搜索,都是推荐使用QZXing库进行识别,但是大部分都是已经有固定一张图片去识别而已;

如果需要实现实时扫码,可以结合Opencv去处理,本文使用的是QT自带的相机处理类QCamera;

当然,QCamera并不支持实时扫码,所以需要QVideoProbe视频帧等类与QThread线程结合一起才可以实现实时扫码而不卡主线程的效果。

目录

一、QZXing

1.下载

2.介绍

3.编译成为库使用

3.将QZXing源码添加到项目中使用

4.使用

二、QCamera

1.初始化相机类

2.实时获取帧图片

3.捕获QImage,实现拍照功能

三、QCamera与QZXing库 结合实现实时扫码功能

ScanCodes.h

ScanCodes.cpp

四、结语


一、QZXing

1.下载

https://github.com/ftylitak/qzxing

2.介绍

  • QZXing是根据ZXing封装得来的,他是开源的,可以随便用。
  • QZXing可以实现生成二维码或条形码,也可以解析二维码或条形码。
  • QZXing的文档就在github链接中。
  • QZxing可以编译成为库去使用,也可以直接讲整个源码工程添加到QT工程中使用,本文主要是添加源码方式使用。

3.编译成为库使用

下载完成后,进入文件,只需关注src文件夹即可,src保存的就是源码

然后打开QtCreator,选择src文件夹里面的QZXing.pro,打开QT工程

然后直接编译即可,等待编译完成,就可以在编译路径文件夹内找到相应的库文件了;

我的是Linux环境,所以编译出来的是.so库,如果是Window环境,编译出来的是.dll和.lib库等;

然后库文件src文件夹下的所有.h文件拷贝到自己的项目中去使用即可!

3.将QZXing源码添加到项目中使用

拷贝src文件夹到自己的QT项目中,并改名字为qzxing;

然后在自己的QT项目中的.pro文件中,导入该源码即可;

只需要添加一行代码:

include($$PWD/qzxing/QZXing.pri)

保存后就会成功添加到项目中去了。

4.使用

需要添加头文件:#include "QZXing.h"

(1).生成二维码

// 生成二维码
QString data = "Hello Wrold!";
QImage barcode = QZXing::encodeData(data, QZXing::EncoderFormat::EncoderFormat_QR_CODE, QSize(800, 800));
barcode.save("abc.png");

(2).生成条形码

目前我没有找到有什么方式可以生成条形码;所以如果需要生成条形码,可以考虑其他库,或者在一些网页手动生成;

条形码在线生成工具-BeJSON.com

生成如下条形码:(右键另存即可保持本地)

(3).识别

通过上面的代码,已经生成一个二维码,内容为“Hello Wrold”,还有网站上面生成的条形码,我们就来解析他们:

QZXing decoder;                                                                        
// 会增加解码的成功率,但也会降低解码速度                                                                 
decoder.setTryHarder(true);                                                            
                                                                                       
// 只能识别 条形码                                                                                 
//decoder.setDecoder(QZXing::DecoderFormat_QR_CODE | QZXing::DecoderFormat_CODE_128);  
                                                                                       
// 只能识别 二维码                                                                                 
//decoder.setDecoder(QZXing::DecoderFormat_QR_CODE | QZXing::DecoderFormat_EAN_13 );   

// 条形码和二维码都可以识别                                                                                
decoder.setDecoder(QZXing::DecoderFormat_QR_CODE | QZXing::DecoderFormat_CODE_128 | QZXing::DecoderFormat_EAN_13);                                      
                                                                                       
decoder.setSourceFilterType(QZXing::SourceFilter_ImageNormal);                         
decoder.setTryHarderBehaviour(QZXing::TryHarderBehaviour_ThoroughScanning | QZXing::TryHarderBehaviour_Rotate);                      
                                                                                                                                                  
                                                                                       
                                                                                       
QImage image("abc.png");    // 加载需要识别的图片(条形码或二维码)                                                               
                                                                                       
// 开始识别                                                                                
QString result = decoder.decodeImage(image);                                           
                                                                                       
QMessageBox::information(nullptr, "解析结果", result);                                     

程序运行后:

  二维码的识别结果:     条形码的识别结果:

都可以正常识别出来,如果识别不出来,将返回空,""

二、QCamera

这个是QT内部的类,可以用于拍照和录像的;

这篇文章不会详细介绍的,只是知道怎么调用摄像头出来,可以实时拿到QIamge图片即可。

定义类成员对象

QCamera *mCamera;
QCameraViewfinder *mViewfinder;
QVBoxLayout *mVLayout;

1.初始化相机类

mCamera=new QCamera(0);             // 摄像头                                                          
mViewfinder=new QCameraViewfinder;  // 取景器                                                                                                              
mCamera->setViewfinder(mViewfinder);    // 设置取景器,否则无法显示摄像头画面                            
                                                                                        
// 取景器添加到布局中,方便添加到QWidget中显示                                                            
mVLayout = new QVBoxLayout(this);                                                       
mVLayout->addWidget(mViewfinder);                                                       
this->setLayout(mVLayout);                                                              

// 相机状态发生改变会触发,一般用于相机启动后的一些处理,后就不会再使用
connect(mCamera, &QCamera::statusChanged, this, &ScanCodes::statuschanged);

只需要调用启动相机代码即可:

mCamera->start();

2.实时获取帧图片

QVideoProbe *mVideoProbe;           // 获取视频帧

// 设置视频探针以捕获视频帧
mVideoProbe = new QVideoProbe(this);
mVideoProbe->setSource(mCamera);
// 只要QCamera捕获到画面都会触发信号
connect(mVideoProbe, &QVideoProbe::videoFrameProbed, this, &ScanCodes::processFrame);

通过setSource函数将相机里设置后,就可以实时获取图片了;

每当有画面时,都会触发videoFrameProbed信号,当然,参数是QVideoFrame,只需要将QVideoFrame转为QImage即可。

3.捕获QImage,实现拍照功能

这里介绍意下如何实现拍照功能;

定义图片捕获类:

QCameraImageCapture *mImageCapture;

然后初始化时绑定信号槽

// 这个是照片捕获操作,可用来实现点击按钮拍照功能
mImageCapture = new QCameraImageCapture(mCamera);
connect(mImageCapture, &QCameraImageCapture::imageCaptured, [&](int id, const QImage &preview) {
    // mImageCapture->capture("capture.png");
    // 可以在任意位置调用此方法,即可获取一张照片,可以用点击拍照使用!
});

最后,可以自己定义一个按钮,绑定信号槽,在按钮的槽函数中触发拍照,调用capture函数就可

mImageCapture->capture("capture.png");

函数调用后会触发imageCaptured信号,就会有一个QImage图片了。

三、QCamera与QZXing库 结合实现实时扫码功能

相机画面显示还是主线程,识别二维码或条形码则使用现场去处理;

定义带有ui的类文件

ScanCodes.h

#ifndef SCANCODES_H
#define SCANCODES_H

#include <QWidget>
#include <QCamera>
#include <QCameraInfo>
#include <QCameraImageCapture>
#include <QCameraViewfinder>
#include <QCameraViewfinderSettings>
#include <QVideoProbe>
#include <QThread>
#include <QTimer>
#include <QVBoxLayout>

#include "QZXing.h"

class ScanCodesThread;

namespace Ui {
class ScanCodes;
}


class ScanCodes : public QWidget
{
    Q_OBJECT

public:
    explicit ScanCodes(QWidget *parent = nullptr);
    ~ScanCodes();

    void initStyle();

    void initCamrea();

    void startCamrea();

    void stopCamrea();

    bool isLoadedStatus() const;
    void setIsLoadedStatus(bool isLoadedStatus);

private slots:

    void statuschanged(QCamera::Status status);

    void processFrame(const QVideoFrame &frame);

    /**
     * @brief 接收条形码扫描结果
     * @param result 结果
     */
    void onDecodeImageResult(const QString &result);

signals:
    /**
     * @brief 将条形码结果发送给首页用于查询数据
     * @param result 结果
     */
    void signalDecodeImageResult(const QString &result);
    
    
private:
    /// QVideoFrame 转 QImage
    QImage videoFrameToImage(QVideoFrame &frame);

private:
    QCamera *mCamera;                   // 相机
    bool mIsLoadedStatus;               // 是否加载相机成功
    QCameraViewfinder *mViewfinder;     // 取景器
    QCameraImageCapture *mImageCapture; // image捕获
    QVBoxLayout *mVLayout;              // 布局

    QVideoProbe *mVideoProbe;           // 获取视频帧
    ScanCodesThread *mScanCodesThread;  // 线程处理识别条形码

    /// 用于停止相机
    QTimer *mTimer;

    Ui::ScanCodes *ui;
};


class ScanCodesThread : public QThread {
    Q_OBJECT

public:
    explicit ScanCodesThread(QObject *parent = nullptr);
    ~ScanCodesThread() override;

    void setImage(const QImage &image);


protected:
    void run() override;

signals:
    /**
     * @brief 将识别条形码的结果发送出去
     * @param result 条形码结果
     */
    void signalDecodeImageResult(const QString &result);

private:
    /// 需要被识别的图片
    QImage m_image;
    QZXing m_decoder;
};


#endif // SCANCODES_H

ScanCodes.cpp

#include "scancodes.h"
#include "ui_scancodes.h"

#include <QVBoxLayout>
#include <QDebug>


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

    initStyle();
    initCamrea();

}

ScanCodes::~ScanCodes()
{
    delete ui;
}

void ScanCodes::initStyle()
{
    mScanCodesThread = new ScanCodesThread(this);
    connect(mScanCodesThread, &ScanCodesThread::signalDecodeImageResult, this, &ScanCodes::onDecodeImageResult, Qt::ConnectionType::QueuedConnection);

    mTimer = new QTimer(this);
    mTimer->setInterval(120 * 1000);
    connect(mTimer, &QTimer::timeout, this, [&] {
        if (mScanCodesThread->isRunning()) {
            mScanCodesThread->quit();       // 结束线程
        }

        // 关闭相机,停止定时器
        stopCamrea();
    });
}

void ScanCodes::initCamrea()
{
    mCamera=new QCamera(0); // 摄像头
    mViewfinder=new QCameraViewfinder; // 取景器
    //mViewfinder->resize(640, 480);
    mIsLoadedStatus = false;
    mCamera->setViewfinder(mViewfinder);    // 设置取景器,否则无法显示摄像头画面

    // 取景器添加到布局中,方便添加到QWidget中显示
    mVLayout = new QVBoxLayout(this);
    mVLayout->addWidget(mViewfinder);
    this->setLayout(mVLayout);

/*
    // 这个是照片捕获操作,可用来实现点击按钮拍照功能
    mImageCapture = new QCameraImageCapture(mCamera);
    connect(mImageCapture, &QCameraImageCapture::imageCaptured, [&](int id, const QImage &preview) {
        // mImageCapture->capture("capture.png");
        // 可以在任意位置调用此方法,即可获取一张照片,可以用点击拍照使用!
    });
*/

    // 相机状态发生改变会触发,一般用于相机启动后的一些处理,后就不会再使用
    connect(mCamera, &QCamera::statusChanged, this, &ScanCodes::statuschanged);


    // 设置视频探针以捕获视频帧
    mVideoProbe = new QVideoProbe(this);
    mVideoProbe->setSource(mCamera);
    // 只要QCamera捕获到画面都会触发信号
    connect(mVideoProbe, &QVideoProbe::videoFrameProbed, this, &ScanCodes::processFrame);
}

void ScanCodes::startCamrea()
{
    mCamera->start();
    mTimer->start();
}

void ScanCodes::stopCamrea()
{
    mTimer->stop();
    mCamera->stop();
}

void ScanCodes::statuschanged(QCamera::Status status)
{
    if (QCamera::LoadedStatus != status) {
        qDebug() << ("摄像头加载失败!");
        return;
    }
    mIsLoadedStatus = true;
    //std::sort(resolutions.begin(), resolutions.end(), qt_sizeLessThan);
    QCameraViewfinderSettings setting = mCamera->viewfinderSettings();
    QList<QSize> size_list = mCamera->supportedViewfinderResolutions();
    if (size_list.size() <= 0) {
        qDebug() << ("摄像头未开启");
        return;
    }
    disconnect(mCamera, &QCamera::statusChanged, this, &ScanCodes::statuschanged);

    //获取摄像头支持的分辨率、帧率等参数
    QList<QCameraViewfinderSettings > ViewSets = mCamera->supportedViewfinderSettings();
    int i = 0;
    foreach (QCameraViewfinderSettings ViewSet, ViewSets) {
        qDebug() << i++ <<" max rate = " << ViewSet.maximumFrameRate() << "min rate = "<< ViewSet.minimumFrameRate() << "resolution "<<ViewSet.resolution()<<\
    "Format="<<ViewSet.pixelFormat()<<""<<ViewSet.pixelAspectRatio();
    }

    //设置摄像头参数
    if(ViewSets.count() > 0){
        mCamera->setViewfinderSettings(ViewSets[1]);    // 根据实际情况选择
    }
    mCamera->start();
}

bool ScanCodes::isLoadedStatus() const
{
    return mIsLoadedStatus;
}

void ScanCodes::setIsLoadedStatus(bool isLoadedStatus)
{
    mIsLoadedStatus = isLoadedStatus;
}


void ScanCodes::processFrame(const QVideoFrame &frame)
{
    // 也可以将转换为QImage这部分逻辑放在线程中执行
    QVideoFrame vf = static_cast<QVideoFrame>(frame);
    QImage image = videoFrameToImage(vf);
    if (image.isNull()) {
        qDebug() << "image is null.";
        return;
    }

    if (!mScanCodesThread->isRunning()) {
        mScanCodesThread->setImage(image);
        mScanCodesThread->start();
    }
}

void ScanCodes::onDecodeImageResult(const QString &result)
{
    emit signalDecodeImageResult(result);
    //stopCamrea();
}


QImage ScanCodes::videoFrameToImage(QVideoFrame &frame) {
    // 确保视频帧格式可以转换为QImage
    if (!frame.isValid()) {
        return QImage();
    }

    // 映射视频帧到内存
    if (!frame.map(QAbstractVideoBuffer::ReadOnly)) {
        return QImage();
    }

    QImage image;
    // 根据视频帧创建QImage对象
    switch (frame.pixelFormat()) {
        case QVideoFrame::Format_ARGB32:
        case QVideoFrame::Format_ARGB32_Premultiplied:
        case QVideoFrame::Format_RGB32:
            image = QImage(frame.bits(), frame.width(), frame.height(), QImage::Format_ARGB32).copy();
            break;
        case QVideoFrame::Format_RGB565:
            image = QImage(frame.bits(), frame.width(), frame.height(), QImage::Format_RGB16).copy();
            break;
        case QVideoFrame::Format_RGB24:
            image = QImage(frame.bits(), frame.width(), frame.height(), QImage::Format_RGB888).copy();
            break;
        default:
            // 不支持的格式,不能转换
            frame.unmap();
            return QImage();
    }

    // 解除视频帧映射
    frame.unmap();

    return image;
}

ScanCodesThread::ScanCodesThread(QObject *parent) : QThread(parent)
{
    // 会增加解码的成功率,但也会降低解码速度
    m_decoder.setTryHarder(true);

    // 条形码
    m_decoder.setDecoder(QZXing::DecoderFormat_QR_CODE | QZXing::DecoderFormat_CODE_128);

    // 二维码
    //decoder.setDecoder(QZXing::DecoderFormat_QR_CODE | QZXing::DecoderFormat_EAN_13 );

    m_decoder.setSourceFilterType(QZXing::SourceFilter_ImageNormal);
    m_decoder.setTryHarderBehaviour(QZXing::TryHarderBehaviour_ThoroughScanning
                                    | QZXing::TryHarderBehaviour_Rotate);
}

ScanCodesThread::~ScanCodesThread()
{

}

void ScanCodesThread::setImage(const QImage &image)
{
    this->m_image = image;
}

void ScanCodesThread::run()
{
    // 识别 码
    QString result = m_decoder.decodeImage(m_image);
    if (!(result.isEmpty() || "" == result)) {
        emit signalDecodeImageResult(result);
    }

    quit();
}

然后在另一个QWidget中使用这个类就好,例如:

ScanCodes *m_scanCodes = new ScanCodes;
m_scanCodes->show();
m_scanCodes->hide();

然后通过按钮绑定信号槽即可

// 打开相机
void Widget::on_btnOpen_clicked()
{
    m_scanCodes->setHidden(false);
    m_scanCodes->startCamrea();
}

// 关闭相机
void Widget::on_btnClose_clicked()
{
    m_scanCodes->stopCamrea();
    m_scanCodes->setHidden(true);
}

如图,相机正常显示,就可以愉快的去扫码啦.

四、结语

ScanCodes 这个类文件代码已经给出来了,根据需要添加到自己的项目中,在处理一下ui文件,根据大小调节一下取景器的大小,就将整个widget添加到对应的部件中显示即可;

或者根据自己需要再改写一下代码!

完!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

cpp_learners

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

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

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

打赏作者

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

抵扣说明:

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

余额充值