QT需要实现扫码功能,在网络上搜索,都是推荐使用QZXing库进行识别,但是大部分都是已经有固定一张图片去识别而已;
如果需要实现实时扫码,可以结合Opencv去处理,本文使用的是QT自带的相机处理类QCamera;
当然,QCamera并不支持实时扫码,所以需要QVideoProbe视频帧等类与QThread线程结合一起才可以实现实时扫码而不卡主线程的效果。
目录
一、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).生成条形码
目前我没有找到有什么方式可以生成条形码;所以如果需要生成条形码,可以考虑其他库,或者在一些网页手动生成;
生成如下条形码:(右键另存即可保持本地)
(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添加到对应的部件中显示即可;
或者根据自己需要再改写一下代码!
完!