Qt/C++ camera collection/QR code analysis/simultaneous collection of multiple channels/picture transmission/resolution frame rate adjustable/automatic reconnection

I. Introduction

Local cameras can be collected in many ways. Generally, local cameras will be connected through USB. On embedded systems, most of them may be soft-band interfaces such as CMOS. These are collectively called local cameras. The biggest difference from network cameras is that One communicates through the network, and the other communicates directly locally. You can use qcamera to capture local cameras, but the qcamera class is not implemented on many platforms. For example, it is almost useless on embedded Linux systems. Therefore, it is more recommended to use ffmpeg, which has the best cross-platform compatibility. Of course, on Linux It can also be implemented through v4l2. This is actually a common USB camera acquisition framework in the entire Linux system. Whether it is ffmpeg or qcamera, the other underlying components on the Linux system are implemented using v4l2, so if the embedded board is limited Due to the size of memory or storage space, a bunch of ffmpeg libraries cannot be used. At this time, the lowest level v4l2 can be used to collect.

Many people's application scenarios require more than one local camera to be collected. Multiple channels may need to be collected at the same time. In fact, this has nothing to do with the program. If you can collect one channel, you can definitely collect multiple channels. After all, it is an encapsulated class and can be created directly. That's fine. The biggest performance bottlenecks in being able to collect multiple channels are USB bandwidth, data bandwidth, and insufficient power supply of the USB port. If the power supply is insufficient, you can only collect 1-2 channels. For example, some people use USB hubs, as shown above. A row of USB ports. Due to insufficient power supply, even if you plug in 4 USB cameras, you can only capture 2 channels at most. The bandwidth limits the resolution and frame rate. For example, an ordinary PC machine can capture 4 channels of 640x480 resolution at the same time, but Due to insufficient bandwidth, it is impossible to capture 4 channels of 1080P at the same time. Although all 4 cameras support 1080p resolution, this is a superposition of comprehensive factors. If you encounter problems, check them slowly.

This comprehensive camera application has been continuously improved for many years. It was originally the v4l2 version, which can only be used on embedded Linux. Then the qcamera class was integrated from Qt5, so a version was made using qcamera. Now Qt6 is used in multimedia The framework has had huge performance improvements, and a version has been made using Qt6's qcamera. Why is it not universal? Because Qt6's multimedia framework has undergone huge updates and adjustments, it is completely incompatible with previous classes. When implementing the video component, I also used ffmpeg to capture local cameras, and it was relatively complete, so I extracted it separately and made a version of ffmpeg. After the final test, I found that ffmpeg is the most versatile and can be collected normally anywhere.

For the QR code collection function, the zxing class is used. The initial method is to send the collected pictures to zxing for analysis. In the actual process, it is found that the analysis of large pictures is very slow. For example, 1080P pictures can only be parsed 3 times per second. At this speed, It will be a bit slower to start up, what if it is 2K resolution. Later, after some real-life scene applications, including the scanning recognition in Cainiao Station, I found that there is a magnification area. The user can set a rectangular area of ​​concern and place it in this rectangular area to achieve the largest and fastest recognition. This is a good idea. According to the strategy, by setting the crop cropping area in ffmpeg, the corresponding content can be directly enlarged and displayed. Then the real-time image captured can be the original video, and the cropped image is sent to zxing for analysis, which greatly improves the speed. In the past, even if the QR code was in a large image, the analysis might fail because the area occupied by the entire image was too small. After automatically adjusting the cropping area, the performance was greatly improved, which is a perfect solution.

2. Effect drawing

Insert image description here

3. Experience address

  1. Domestic site: https://gitee.com/feiyangqingyun
  2. International site: https://github.com/feiyangqingyun
  3. Personal work: https://blog.csdn.net/feiyangqingyun/article/details/97565652
  4. Experience address: https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g Extraction code: 01jf File name: bin_video_camera.

4. Related codes

#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());
}

5. Functional features

  1. At the same time, it supports three kernels: qcamera, ffmpeg, and v4l2 to parse local cameras.
  2. The function findCamera is provided to automatically search all local camera devices in the environment, and the search result signal is sent out.
  3. Supports two modes: automatic search and specified device. In automatic search mode, the first device searched will be opened as the current device.
  4. Supports opening multiple devices at the same time, personally tested 4 channels, limited by specific environment such as bandwidth.
  5. Supports automatic reconnection, which is enabled by default. After failure, it will automatically search and try to open again.
  6. Both the ffmpeg solution and the v4l2 solution support callback mode (converting to QImage drawing after collection) and handle mode (YUV data GPU drawing after collection, high performance).
  7. The video display position automatically adjusts the algorithm. When the video resolution exceeds the size of the display control, it will be scaled and displayed in the center. If it does not exceed the size, it will be displayed in the center of the original size. It can also be set to stretch and fill the display. (Automatic adjustment, proportional scaling, stretch filling).
  8. You can choose different resolutions to open the camera, supporting 160x120, 320x240, 640x480, 800x600, 1280x720, 1280x960, 1920x1080, etc.
  9. Different frame rates can be selected to open the camera, supporting 0 (default value), 5, 10, 15, 20, 25, 30, etc.
  10. Supports capturing screenshots. If the file name is passed in, the screenshot file will be automatically saved. If no file name is passed in, the image data QImage signal will be sent out.
  11. Provides function interfaces to start play, stop play, pause, and continue play next.
  12. Supports dynamic hot-plug loading, including automatically reading all device names into drop-down boxes.
  13. Supports recording file storage, and provides functions such as recordStart to start recording, recordPause to pause recording, and recordStop to stop recording.
  14. Provide QR code examples, automatically collect the screen to identify the QR code, and support automatically regenerating the recognized QR code into a large image.
  15. QR code recognition supports setting hotspot areas, cropping and identifying images in this area, which is very useful when collecting large-resolution images, improving speed and efficiency.
  16. Supports selecting image files to parse QR codes, and manually input text content to generate QR codes.
  17. Provides image transmission examples, automatically transmits the opened camera video in real time, and analyzes and displays it after the server receives it. This solution can be used to remotely transmit local camera real-time images, such as camera images on an embedded board, to a PC for display.
  18. Supports proportional stretching and filling display. If the width and height of the screen are smaller than the width and height of the display control, the original video size will prevail. If it is larger, the width and height will be scaled and centered according to the size of the display control.
  19. The video control floating bar comes with functions such as starting and stopping recording, muting the sound, taking screenshots, and closing the video.
  20. The audio component supports sound waveform value data analysis. Waveform curves and columnar sound bars can be drawn based on this value. Sound amplitude signals are provided by default.
  21. The code framework and structure are optimized to the extreme, the performance is strong, and it is continuously updated and upgraded.
  22. The source code supports Qt4, Qt5, and Qt6 and is compatible with all versions.

Guess you like

Origin blog.csdn.net/feiyangqingyun/article/details/135400552