一、前言

本地摄像头的采集可以有多种方式,一般本地摄像头会通过USB的方式连接,在嵌入式上可能大部分是CMOS之类的软带的接口,这些都统称本地摄像头,和网络摄像头最大区别就是一个是通过网络来通信,一个是直接本地通信。本地摄像机的采集可以用qcamera来做,但是qcamera类在很多平台没有实现,比如嵌入式linux系统上几乎是没有用的,所以更加推荐用兼容性跨平台最好的ffmpeg来做,在linux上当然也可以通过v4l2来实现,这个其实是整个linux系统中通用的USB摄像头采集的框架,无论是ffmpeg还是qcamera,在linux系统上底层其他都是用v4l2来实现的,所以如果在嵌入式板子受限于内存或者存储空间大小,用不了ffmpeg一堆库,这个时候就可以用最底层的v4l2来采集。

很多人的应用场景要求不止采集1路本地摄像头,可能有多路都需要同时采集,其实这个和程序没有太大关系,能够采集一路肯定就能多路,毕竟就是封装的一个类,直接new出来就行,要能采集多路最大的性能瓶颈在USB带宽,数据带宽,还有就是USB口子的供电足不足,供电不足,也只能采集1-2路,比如有些人用的是USB集线器,上面一排USB口子,由于供电不足,就算你插4个USB摄像头,也只能最多采集2路,带宽又限制了分辨率和帧率,比如一个普通PC机器可以同时采集4路640x480分辨率,但是由于带宽不足,无法同时采集4路1080P,尽管4个摄像头都支持1080p分辨率,所以这是个综合因素的叠加影响,遇到问题慢慢查。

这个摄像头综合应用,陆陆续续完善了很多年,最开始是v4l2版本,只能用在嵌入式linux上,而后从Qt5开始集成了qcamera类,所以有用qcamera来做了一个版本,现在Qt6在多媒体框架有了巨大的性能提升,又用Qt6的qcamera做了个版本,为什么不通用了?因为Qt6的多媒体框架做了巨大更新调整,完全不兼容之前的类。在做视频组件的实现用ffmpeg采集本地摄像头也实现了,而且做的比较完善,所以后面单独提炼出来做了ffmpeg的版本,最后测试下来发现ffmpeg是最通用的,在哪里都能正常采集。

做二维码采集这个功能,用的是zxing类,最初的做法是将采集的图片发给zxing解析,实际过程发现大图片解析很慢,比如1080P图片大概1s只能解析3张,这个速度看起来就慢了点,如果是2K的分辨率呢。后面经过一些真实场景应用,包括菜鸟驿站里面带的扫描识别,发现都有个放大区域,用户可以设置一个关心的矩形区域,要放在这个矩形区域内,能够最大最快的识别,这是个不错的策略,在ffmpeg中通过设置crop裁剪区域就可以直接将对应内容放大显示,然后采集的实时画面可以是原视频,发送给zxing解析的就是裁剪后的画面,这样速度大大提升。之前大图就算二维码在里面,由于在整个图片中的占比区域太小,也可能解析失败,自动调整后裁剪区域,性能大幅度提升,完美解决。

二、效果图

三、体验地址

  1. 国内站点:https://gitee.com/feiyangqingyun
  2. 国际站点:https://github.com/feiyangqingyun
  3. 个人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
  4. 体验地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取码:01jf 文件名:bin_video_camera。

四、相关代码

#include "frmzxing.h"
#include "frmmain.h"
#include "ui_frmzxing.h"
#include "qthelper.h"
#include "camerahelper.h"
#include "widgethelper.h"
#include "zxingthread.h" frmZxing::frmZxing(QWidget *parent) : QWidget(parent), ui(new Ui::frmZxing)
{
ui->setupUi(this);
this->initForm();
this->initConfig();
} frmZxing::~frmZxing()
{
delete ui;
} void frmZxing::initForm()
{
ui->frame->setFixedWidth(AppData::RightWidth); //关联信号槽
connect(ui->cameraWidget, SIGNAL(sig_receivePlayStart(int)), this, SLOT(receivePlayStart(int)));
connect(ui->cameraWidget, SIGNAL(sig_receivePoint(int, QPoint)), this, SLOT(receivePoint(int, QPoint))); //实例化解析类并启动线程
zxing = new ZXingThread(this);
connect(zxing, SIGNAL(receiveResult(QString, QString, int)), this, SLOT(receiveResult(QString, QString, int)));
zxing->start(); //启动定时器截图
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(on_btnSnap_clicked()));
if (AppConfig::Zxing_Interval > 0) {
timer->start(AppConfig::Zxing_Interval);
}
} void frmZxing::initConfig()
{
CameraHelper::loadCameraCore(ui->cboxCameraCore, AppConfig::Zxing_CameraCore);
connect(ui->cboxCameraCore, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig())); this->cameraDeviceChanged();
connect(ui->cboxCameraName->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(saveConfig())); CameraHelper::loadVideoSize(ui->cboxVideoSize);
ui->cboxVideoSize->lineEdit()->setText(AppConfig::Zxing_VideoSize);
connect(ui->cboxVideoSize->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(saveConfig())); CameraHelper::loadFrameRate(ui->cboxFrameRate);
ui->cboxFrameRate->lineEdit()->setText(QString::number(AppConfig::Zxing_FrameRate));
connect(ui->cboxFrameRate->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(saveConfig())); ui->cboxVideoMode->setCurrentIndex(AppConfig::Zxing_VideoMode);
connect(ui->cboxVideoMode, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig())); zxing->setDecoder(AppConfig::Zxing_DecoderFormat);
ui->cboxDecoderFormat->addItem("仅二维码");
ui->cboxDecoderFormat->addItem("一维二维");
ui->cboxDecoderFormat->addItem("所有格式");
ui->cboxDecoderFormat->setCurrentIndex(AppConfig::Zxing_DecoderFormat);
connect(ui->cboxDecoderFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig())); ui->cboxInterval->addItem("暂停");
ui->cboxInterval->addItem("300");
ui->cboxInterval->addItem("500");
ui->cboxInterval->addItem("800");
ui->cboxInterval->addItem("1000");
int index = ui->cboxInterval->findText(QString::number(AppConfig::Zxing_Interval));
ui->cboxInterval->setCurrentIndex(index < 0 ? 0 : index);
connect(ui->cboxInterval, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig())); ui->txtTopLeft->setText(AppConfig::Zxing_TopLeft);
connect(ui->txtTopLeft, SIGNAL(textChanged(QString)), this, SLOT(saveConfig())); ui->txtBottomRight->setText(AppConfig::Zxing_BottomRight);
connect(ui->txtBottomRight, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));
} void frmZxing::saveConfig()
{
//内核变了需要重新搜索设备
int cameraCore = ui->cboxCameraCore->itemData(ui->cboxCameraCore->currentIndex()).toInt();
if (AppConfig::Zxing_CameraCore != cameraCore) {
AppConfig::Zxing_CameraCore = cameraCore;
this->cameraDeviceChanged();
} //不为空才需要改变
QString cameraName = ui->cboxCameraName->currentText();
if (!cameraName.isEmpty()) {
AppConfig::Zxing_CameraName = cameraName;
} AppConfig::Zxing_VideoSize = ui->cboxVideoSize->currentText();
AppConfig::Zxing_FrameRate = ui->cboxFrameRate->currentText().toInt();
AppConfig::Zxing_VideoMode = ui->cboxVideoMode->currentIndex(); //变了立即重新设置
int decoderFormat = ui->cboxDecoderFormat->currentIndex();
if (AppConfig::Zxing_DecoderFormat != decoderFormat) {
AppConfig::Zxing_DecoderFormat = decoderFormat;
zxing->setDecoder(AppConfig::Zxing_DecoderFormat);
} int interval = ui->cboxInterval->currentText().toInt();
if (AppConfig::Zxing_Interval != interval) {
AppConfig::Zxing_Interval = interval;
if (interval > 0) {
timer->start(interval);
} else {
timer->stop();
}
} AppConfig::Zxing_TopLeft = ui->txtTopLeft->text().trimmed();
AppConfig::Zxing_BottomRight = ui->txtBottomRight->text().trimmed();
AppConfig::writeConfig();
} void frmZxing::initPara()
{
//设置显示窗体参数
WidgetPara widgetPara = ui->cameraWidget->getWidgetPara();
widgetPara.borderWidth = 1;
widgetPara.videoMode = (VideoMode)AppConfig::Zxing_VideoMode;
ui->cameraWidget->setWidgetPara(widgetPara); //设置采集线程参数
CameraPara cameraPara = ui->cameraWidget->getCameraPara();
cameraPara.cameraCore = (CameraCore)AppConfig::Zxing_CameraCore;
cameraPara.cameraName = AppConfig::Zxing_CameraName;
cameraPara.videoSize = AppConfig::Zxing_VideoSize;
cameraPara.frameRate = AppConfig::Zxing_FrameRate;
ui->cameraWidget->setCameraPara(cameraPara);
} void frmZxing::append(int type, const QString &data, bool clear)
{
static int maxCount = 50;
static int currentCount = 0;
QtHelper::appendMsg(ui->txtMain, type, data, maxCount, currentCount, clear);
ui->txtMain->moveCursor(QTextCursor::End);
} void frmZxing::createImage(const QString &text)
{
if (!text.isEmpty()) {
QImage image = zxing->encodeData(text, QSize(250, 250));
ui->labResult->setImage(image, true);
}
} void frmZxing::cameraDeviceChanged()
{
frmMain::getCameraInfo((CameraCore)AppConfig::Zxing_CameraCore);
CameraHelper::loadCameraName(ui->cboxCameraName, AppConfig::Zxing_CameraName);
} void frmZxing::snapImage(const QImage &image, const QString &snapName)
{
QImage img = image;
if (snapName != "file") {
//有左上右下坐标说明需要裁减
int w = image.width();
int h = image.height();
if (!AppConfig::Zxing_TopLeft.isEmpty() && !AppConfig::Zxing_BottomRight.isEmpty()) {
QRect rect = WidgetHelper::getRect(AppConfig::Zxing_TopLeft, AppConfig::Zxing_BottomRight);
img = img.copy(rect);
QString msg = QString("执行裁剪(原分辨率: %1 x %2 新分辨率: %3 x %4)").arg(w).arg(h).arg(img.width()).arg(img.height());
append(4, msg);
img.save("f:/1.jpg", "jpg");
} //防止图片过大导致解析很慢
static int maxWidth = 1280;
static int maxHeight = 720;
w = img.width();
h = img.height();
if (w > maxWidth || h > maxHeight) {
img = img.scaled(QSize(maxWidth, maxHeight), Qt::KeepAspectRatio);
QString msg = QString("执行缩放(原分辨率: %1 x %2 新分辨率: %3 x %4)").arg(w).arg(h).arg(maxWidth).arg(maxHeight);
append(4, msg);
}
} //添加到线程中解析
zxing->append("", img);
//显示图片
ui->labImage->setImage(img, true);
} void frmZxing::receiveResult(const QString &flag, const QString &text, int time)
{
append(2, QString("(用时: %1 毫秒) 结果: %2").arg(time).arg(text));
ui->txtResult->setText(text);
//重新生成新的二维码图片
createImage(text);
} void frmZxing::receivePlayStart(int time)
{
//如果存在裁剪区域则设置图形
if (!AppConfig::Zxing_TopLeft.isEmpty() && !AppConfig::Zxing_BottomRight.isEmpty()) {
GraphInfo graph;
graph.rect = WidgetHelper::getRect(AppConfig::Zxing_TopLeft, AppConfig::Zxing_BottomRight);
graph.borderWidth = WidgetHelper::getBorderWidth(ui->cameraWidget);
ui->cameraWidget->setGraph(graph);
}
} void frmZxing::receivePoint(int type, const QPoint &point)
{
QString text = QString("%1, %2").arg(point.x()).arg(point.y());
if (ui->rbtnTopLeft->isChecked()) {
ui->txtTopLeft->setText(text);
} else {
ui->txtBottomRight->setText(text);
}
} void frmZxing::on_btnPlay_clicked()
{
this->initPara();
if (!ui->cameraWidget->init()) {
return;
} //关联采集线程信号槽
CameraThread *cameraThread = ui->cameraWidget->getCameraThread();
connect(cameraThread, SIGNAL(cameraDeviceChanged()), this, SLOT(cameraDeviceChanged()));
connect(cameraThread, SIGNAL(snapImage(QImage, QString)), this, SLOT(snapImage(QImage, QString))); ui->cameraWidget->play();
ui->widget->setEnabled(false);
} void frmZxing::on_btnStop_clicked()
{
ui->cameraWidget->stop();
ui->widget->setEnabled(true);
} void frmZxing::on_btnPause_clicked()
{
ui->cameraWidget->pause();
} void frmZxing::on_btnNext_clicked()
{
ui->cameraWidget->next();
} void frmZxing::on_btnSnap_clicked()
{
CameraThread *thread = ui->cameraWidget->getCameraThread();
if (thread && thread->getIsOk()) {
append(4, "执行截图并解析二维码");
ui->cameraWidget->snap();
}
} void frmZxing::on_btnFile_clicked()
{
QString fileName = QFileDialog::getOpenFileName(this, "打开", "", "*.jpg *.png");
if (!fileName.isEmpty()) {
QImage image(fileName);
snapImage(image, "file");
}
} void frmZxing::on_btnCreate_clicked()
{
createImage(ui->txtResult->text());
}s

五、功能特点

  1. 同时支持 qcamera、ffmpeg、v4l2 三种内核解析本地摄像头。
  2. 提供函数 findCamera 自动搜索环境中的所有本地摄像头设备,搜索结果信号发出。
  3. 支持自动搜索和指定设备两种模式,自动搜索模式下会将搜索到的第一个设备作为当前设备打开。
  4. 支持同时打开多路设备,亲测4路,受限于具体的环境比如带宽。
  5. 支持自动重连,默认开启,失败后会自动重新搜索和尝试打开。
  6. ffmpeg方案、v4l2方案都支持回调模式(采集后转成QImage绘制)和句柄模式(采集后YUV数据GPU绘制,性能高)。
  7. 视频显示位置自动调整算法,当视频分辨率超过显示控件大小则等比例缩放居中显示,不超过则原尺寸居中显示,还可设置拉伸填充显示。(自动调整、等比例缩放、拉伸填充)。
  8. 可选不同的分辨率来打开摄像头,支持 160x120、320x240、640x480、800x600、1280x720、1280x960、1920x1080 等。
  9. 可选不同的帧率来打开摄像头,支持 0(采用默认值)、5、、10、15、20、25、30 等。
  10. 支持抓拍截图,传入文件名则自动保存截图文件,不传入则将图片数据QImage信号发出。
  11. 提供函数接口 开始播放play、停止播放stop、暂停播放pause、继续播放next。
  12. 支持动态热插拔加载,包括自动读取所有设备名称到下拉框。
  13. 支持录像文件存储,提供开始录像recordStart、暂停录像recordPause、停止录像recordStop 等函数。
  14. 提供二维码示例,自动采集画面识别二维码,支持自动将识别到的二维码重新生成大图。
  15. 二维码识别支持设置热点区域,对该区域内的图片进行裁切并识别,在大分辨率图像采集的时候非常有用,提升速度和效率。
  16. 支持选择图片文件解析二维码,手动输入文本内容生成二维码。
  17. 提供图片传输示例,自动将打开的摄像头视频实时传输出去,服务器端接收后解析显示。此方案可以作为将本地的摄像头实时画面远程传输,比如嵌入式板子上的摄像头画面传输到PC端显示。
  18. 支持等比例拉伸填充显示,画面宽高小于显示控件的宽高则以原视频大小为准,大于则按照显示控件的尺寸等比例缩放居中。
  19. 视频控件悬浮条自带开始和停止录像切换、声音静音切换、抓拍截图、关闭视频等功能。
  20. 音频组件支持声音波形值数据解析,可以根据该值绘制波形曲线和柱状声音条,默认提供了声音振幅信号。
  21. 代码框架和结构优化到极致,性能彪悍,持续迭代更新升级。
  22. 源码支持Qt4、Qt5、Qt6,兼容所有版本。

Qt/C++摄像头采集/二维码解析/同时采集多路/图片传输/分辨率帧率可调/自动重连的更多相关文章

  1. h5端呼起摄像头扫描二维码并解析

    2016年6月29日补充: 最近做了一些与表单相关的项目,使用了h5的input控件,在使用过程中遇到了很多的坑.也包括与这篇文章相关的. 首先我们应该知道使用h5新提供的属性getUserMedia ...

  2. iOS--iOS7摄像头识别二维码功能

    iOS–iOS7摄像头识别二维码功能 属性介绍: AVFoundation 框架基于以下几个类实现图像捕捉 ,通过这些类可以访问来自相机设备的原始数据并控制它的组件. AVCaptureDevice ...

  3. C# winfrom调用摄像头扫描二维码(完整版)

    前段时间看到一篇博客,是这个功能的,参考了那篇博客写了这个功能玩一玩,没有做商业用途.发现他的代码给的有些描述不清晰的,我就自己整理一下发出来记录一下. 参考博客链接:https://www.cnbl ...

  4. 二维码解析:使用 JavaScript 库reqrcode.js解析二维码

    上次使用QRCode.js可以来生成二维码,但是我没有找到有文档说明可以对存在的二维码进行扫描解析其中的内容. 幸亏查找到了可行的解决方案,而且很好使哦!就是reqrcode.js 地址:https: ...

  5. 二维码解析(编译zxing-cpp)

    二维码解析使用的类库是zxing(官网 https://github.com/zxing/zxing). 这个类库是谷歌的,原来有c++版本,后来的更新去掉了,zxing介绍了目前基于zxing的其他 ...

  6. python实现发票二维码解析

    通过发票左上角的二维码信息,获取发票的关键信息,只需将图片格式的电子发票或扫描后的发票图片传入即可. 测试结果如下: 增值税电子普通发票:{'发票代码': '031xxxxxx311', '发票号码' ...

  7. .NET 采用 SkiaSharp 生成二维码和图形验证码及图片进行指定区域截取方法实现

    在最新版的 .NET 平台中,微软在逐步放弃 System.Drawing.Imaging ,给出的理由如下: System.Drawing命名空间对某些操作系统和应用程序类型有一些限制. 在Wind ...

  8. c# winform调用摄像头识别二维码

    首先我们需要引用两个第三方组件:AForge和zxing. Aforge是摄像头操作组件,zxing是二维码识别组件.都是开源项目.避免重复造轮子. 其实一些操作代码我也是参照别人的,若侵犯您的版权, ...

  9. Win10 UWP开发:摄像头扫描二维码/一维码功能

    这个示例演示整合了Aran和微软的示例,无需修改即可运行. 支持识别,二维码/一维码,需要在包清单管理器勾选摄像头权限. 首先右键项目引用,打开Nuget包管理器搜索安装:ZXing.Net.Mobi ...

  10. 在WPF中开启摄像头扫描二维码(Media+Zxing)

    近两天项目中需要添加一个功能,是根据摄像头来读取二维码信息,然后根据读出来的信息来和数据库中进行对比显示数据. 选择技术Zxing.WPFMediaKit.基本的原理就是让WPFmediaKit来对摄 ...

随机推荐

  1. ES5 和 ES6 的区别,说几个 ES6 的新增方法

    ECMAscript5.,即ES5 ,表示 ECMAscript的第五次修订-2009 : ECMAscript6.,即ES6 ,表示 ECMAscript的第六次修订-2015 : ES6 是对于 ...

  2. Android复习(五)设备兼容—>支持刘海屏

    支持刘海屏 刘海屏是指某些设备显示屏上的一个区域延伸到显示面,这样既能为用户提供全面屏体验,又能为设备正面的重要传感器留出空间.Android 在搭载 Android 9(API 级别 28)及更高版 ...

  3. vue打包后,添加入spring boot下,访问不到字体的BUG

    主要报错:OTS parsing error: incorrect file size in WOFF header OTS parsing error: incorrect entrySelecto ...

  4. Office365常用的命令(持续更新中)

    Office365常用的命令(持续更新中) 持续更新中............. Enable-ExchangeCertificate -Thumbprint B9C93420E7BEE46C1CD2 ...

  5. OpenFunction 0.6.0 发布: FaaS 可观测性、HTTP 同步函数能力增强及更多特性

    OpenFunction 是一个开源的云原生 FaaS(Function as a Service,函数即服务)平台,旨在帮助开发者专注于业务逻辑的研发.在过去的几个月里,OpenFunction 社 ...

  6. golang slice相关常见的性能优化手段

    介绍一些开发中常用的slice关联的性能优化手段.鉴于golang编译器本身捉鸡的优化能力,优化的成本就得分摊在开发者自己的头上了. 这篇文章会介绍的优化手段是下面这几样: 创建slice时预分配内存 ...

  7. 利用 canvas 实现签名效果

    利用 canvas 实现签名效果 使用插件  jSignature  github:https://github.com/brinley/jSignature 如果再H5 中使用需要加载  flash ...

  8. 基于Java+SpringBoot心理测评心理测试系统功能实现三

    一.前言介绍: 1.1 项目摘要 心理测评和心理测试系统在当代社会中扮演着越来越重要的角色.随着心理健康问题日益受到重视,心理测评和心理测试系统作为评估个体心理状态.诊断心理问题.制定心理治疗方案的工 ...

  9. 操作方法分享:4G模组中移OneNET轻松上云平台

    ​ 一.简介 1.1 IoT_CLOUD的功能 IoT_CLOUD库本质就是上层设计一套通用的API,用库来实现每个平台功能的对接. 目前已经实现了各个平台的所有注册方式,其中自动注册会将相关验证信息 ...

  10. 如何把composer版本降下来

    如果想把composer从2版本降到1版本 composer self-update 1.4.1 如果想降到1版本 composer self-update --1