How to Build a Qt Barcode Scanner with MSVC and Dynamsoft C++ Barcode SDK on Windows

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5175

    #1

    How to Build a Qt Barcode Scanner with MSVC and Dynamsoft C++ Barcode SDK on Windows

    Qt is a cross-platform C++ framework for developing applications with graphical user interfaces (GUIs). In this tutorial, you'll learn how to create a robust, production-ready barcode scanner application using Qt 6, Microsoft Visual C++ (MSVC), and the Dynamsoft Barcode Reader C++ SDK on Windows. This tutorial covers everything from environment setup to implementing advanced features like real-time camera scanning and visual barcode overlays.


    Demo: Qt Barcode Scanner for Windows



    Prerequisites



    What You'll Build

    By the end of this tutorial, you'll create a barcode scanner application with these features:
    • Dual-mode scanning: Image file scanning and real-time camera scanning
    • Visual feedback: Live barcode detection overlays with bounding boxes
    • Multiple format support: Support for 1D and 2D barcodes (QR codes, Code 128, DataMatrix, etc.)
    • Drag-and-drop functionality: Easy image loading via drag-and-drop





    Step 1: Environment Setup

    1.1 Install Qt 6 with MSVC

    Download and install Qt 6 from the official Qt installer:

    1. Run the Qt Online Installer.
    2. Select Qt 6.7.2 or later.
    3. Choose MSVC 2019/2022 64-bit component.
    4. Install to C:\Qt\6.7.2\msvc2022_64




      # Set Qt environment variable
      set Qt6_DIR=C:\Qt\6.7.2\msvc2022_64\lib\cmake\Qt6


    1.2 Install OpenCV

    Download OpenCV 4.8.0 from opencv.org:

    1. Download the Windows release
    2. Extract to C:\opencv
    3. Set environment variable:




      set OpenCV_DIR=C:\opencv\build


    Step 2: Project Structure and CMake Configuration

    2.1 Create Project Directory





    mkdir qt-barcode-scanner
    cd qt-barcode-scanner







    2.2 CMakeLists.txt Configuration

    Create a CMakeLists.txt file that handles all dependencies:






    cmake_minimum_required(VERSION 3.16)
    project(QtBarcodeScanner VERSION 1.0 LANGUAGES CXX)

    set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)

    # Find Qt6 components
    find_package(Qt6 REQUIRED COMPONENTS Core Widgets Multimedia MultimediaWidgets)

    # Enable Qt MOC, UIC, and RCC
    set(CMAKE_AUTOMOC ON)
    set(CMAKE_AUTOUIC ON)
    set(CMAKE_AUTORCC ON)

    # OpenCV for camera support - always enabled
    find_package(OpenCV REQUIRED)
    if(OpenCV_FOUND)
    message(STATUS "OpenCV found: ${OpenCV_VERSION}")
    endif()

    # Source files
    set(SOURCES
    main.cpp
    mainwindow.cpp
    mainwindow.h
    mainwindow.ui
    barcodeworker.cpp
    barcodeworker.h
    opencvcamera.cpp
    opencvcamera.h
    )

    # Create executable
    add_executable(QtBarcodeScanner ${SOURCES})

    # Link Qt libraries
    target_link_libraries(QtBarcodeScanner
    Qt6::Core
    Qt6::Widgets
    Qt6::Multimedia
    Qt6::MultimediaWidgets
    )

    # Link OpenCV
    if(OpenCV_FOUND)
    target_link_libraries(QtBarcodeScanner ${OpenCV_LIBS})
    target_include_directories(QtBarcodeScanner PRIVATE ${OpenCV_INCLUDE_DIRS})
    endif()

    # Dynamsoft SDK configuration
    set(DCV_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../dcv")
    target_include_directories(QtBarcodeScanner PRIVATE "${DCV_ROOT}/include")

    if(WIN32)
    target_link_libraries(QtBarcodeScanner
    "${DCV_ROOT}/lib/win/DynamsoftCaptureVisionRouter.lib"
    "${DCV_ROOT}/lib/win/DynamsoftCore.lib"
    "${DCV_ROOT}/lib/win/DynamsoftUtility.lib"
    "${DCV_ROOT}/lib/win/DynamsoftLicense.lib"
    )
    endif()

    # Post-build DLL and resource copying
    if(WIN32)
    # Copy Qt platform plugins
    add_custom_command(TARGET QtBarcodeScanner POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E make_directory
    "$/platforms"
    COMMAND ${CMAKE_COMMAND} -E copy_directory
    "${Qt6_DIR}/../../../plugins/platforms"
    "$/platforms"
    )

    # Copy complete DLL directory
    add_custom_command(TARGET QtBarcodeScanner POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_directory
    "${DCV_ROOT}/lib/win"
    "$"
    )
    endif()







    Key Configuration Points:
    • Automatic MOC/UIC/RCC: Essential for Qt meta-object compilation
    • Complete DLL copying: Ensures all Dynamsoft libraries are available
    • Plugin directory copying: Required for Qt platform plugins
    • Conditional compilation: Enables camera features based on available components


    Step 3: Core Application Structure

    3.1 Main Window Header (mainwindow.h)





    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H

    #include
    #include
    #include
    #include
    #include
    #include
    #include

    #include
    #include
    #include
    #include

    #include "opencvcamera.h"
    #include

    #include "barcodeworker.h"

    QT_BEGIN_NAMESPACE
    namespace Ui { class MainWindow; }
    QT_END_NAMESPACE

    class MainWindow : public QMainWindow
    {
    Q_OBJECT

    public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

    protected:
    void dragEnterEvent(QDragEnterEvent *event) override;
    void dropEvent(QDropEvent *event) override;
    void resizeEvent(QResizeEvent *event) override;
    void changeEvent(QEvent *event) override;

    private slots:
    void openImageFile();
    void startCamera();
    void stopCamera();
    void clearResults();
    void about();
    void setLicense();
    void loadTemplate();

    void onBarcodeResults(const QListBarcodeResult> &results);
    void onImageTabSelected();
    void onCameraTabSelected();
    void onLicenseStatusChanged(const QString &status, bool isValid);

    private:
    void setupConnections();
    void loadImageFile(const QString &filePath);
    void updateImageDisplay(const QPixmap &pixmap, const QListBarcodeResult> &results = QListBarcodeResult>());
    void updateResultsDisplay(const QListBarcodeResult> &results);
    void updateCameraDisplay();
    void updateLicenseStatus(const QString &status, bool isValid);

    bool tryStartOpenCVCamera();

    Ui::MainWindow *ui;

    std::unique_ptrQCamera> camera;
    std::unique_ptrQMediaCaptureSession> captureSession;
    std::unique_ptrQVideoWidget> videoWidget;

    OpenCVCamera *openCVCamera;
    QLabel *cameraLabel;
    bool useOpenCVCamera;
    QTimer *resizeTimer;
    bool cameraUpdatesPaused;
    #endif

    QThread *workerThread;
    BarcodeWorker *barcodeWorker;

    QPixmap currentPixmap;
    QListBarcodeResult> currentImageResults;
    QPixmap currentCameraFrame;
    QListBarcodeResult> currentCameraResults;
    QString licenseKey;
    QString templateContent;
    QString lastImageDirectory;

    QLabel *licenseStatusLabel;
    };

    #endif // MAINWINDOW_H







    3.2 Barcode Worker Implementation

    The BarcodeWorker class handles barcode detection in a separate thread to prevent UI blocking:






    // barcodeworker.h
    #ifndef BARCODEWORKER_H
    #define BARCODEWORKER_H

    #include
    #include
    #include
    #include

    #include "DynamsoftCaptureVisionRouter.h"
    #include "DynamsoftUtility.h"

    using namespace dynamsoft::license;
    using namespace dynamsoft::cvr;
    using namespace dynamsoft::dbr;
    using namespace dynamsoft::utility;
    using namespace dynamsoft::basic_structures;

    struct BarcodeResult
    {
    QString format;
    QString text;
    QListQPoint> points;
    };

    class BarcodeWorker : public QObject, public CCapturedResultReceiver
    {
    Q_OBJECT

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

    virtual void OnDecodedBarcodesReceived(CDecodedBarcodesResult *pResult) override;

    public slots:
    void initialize();
    void processImage(const QImage &image);
    void processFrame(const QImage &frame);
    void setLicense(const QString &license);

    signals:
    void resultsReady(const QListBarcodeResult> &results);
    void licenseStatusChanged(const QString &status, bool isValid);

    private:
    QListBarcodeResult> convertResults(CDecodedBarcodesResult *pResult);

    CCaptureVisionRouter *m_router;
    QString m_licenseKey;
    bool m_initialized;
    };

    #endif // BARCODEWORKER_H







    Key Implementation Details:
    • Thread Safety: All barcode processing happens in a worker thread
    • Dynamsoft Integration: Implements CCapturedResultReceiver for result callbacks
    • Signal-Slot Communication: Uses Qt signals for thread-safe communication
    • License Management: Handles Dynamsoft license initialization and validation


    Step 4: Implementing the Barcode Worker

    4.1 Worker Initialization and License Setup





    // barcodeworker.cpp
    void BarcodeWorker::initialize()
    {
    try
    {
    char errorMsgBuffer[512];
    int ret = CLicenseManager::InitLicense(m_licenseKey.toUtf8() .constData(), errorMsgBuffer, 512);
    if (ret != EC_OK)
    {
    QString errorMsg = QString::fromUtf8(errorMsgBuffer);
    emit licenseStatusChanged(QString("License: Failed (%1)").arg(errorMsg), false);
    }
    else
    {
    emit licenseStatusChanged("License: Valid", true);
    }

    m_router = new CCaptureVisionRouter();
    if (!m_router)
    {
    return;
    }

    m_router->AddResultReceiver(this);

    m_initialized = true;
    }
    catch (const std::exception &e)
    {
    m_initialized = false;
    }
    }







    4.2 Image Processing Implementation





    void BarcodeWorker:rocessImage(const QImage &image)
    {
    if (!m_initialized || !m_router || image.isNull())
    {
    if (!m_initialized)
    {
    initialize();
    if (!m_initialized)
    {
    emit resultsReady(QListBarcodeResult>());
    return;
    }
    }

    if (image.isNull())
    {
    emit resultsReady(QListBarcodeResult>());
    return;
    }
    }

    try
    {
    // Convert QImage to Dynamsoft-compatible format
    QImage rgbImage = image.convertToFormat(QImage::Format_RGB888);

    CImageData imageData(
    rgbImage.sizeInBytes(),
    rgbImage.bits(),
    rgbImage.width(),
    rgbImage.height(),
    rgbImage.bytesPerLine(),
    IPF_RGB_888
    );

    CCapturedResult *result = m_router->Capture(&imageData, CPresetTemplate::PT_READ_BARCODES);

    if (result)
    {
    if (result->GetErrorCode() != EC_OK)
    {
    emit resultsReady(QListBarcodeResult>());
    }
    else
    {
    CDecodedBarcodesResult *barcodeResult = result->GetDecodedBarcodesResult();
    if (barcodeResult)
    {
    QListBarcodeResult> results = convertResults(barcodeResult);
    emit resultsReady(results);
    }
    else
    {
    emit resultsReady(QListBarcodeResult>());
    }
    }
    result->Release();
    }
    else
    {
    emit resultsReady(QListBarcodeResult>());
    }
    }
    catch (const std::exception &e)
    {
    emit resultsReady(QListBarcodeResult>());
    }
    }







    Critical Points in Image Processing:
    • Format Conversion: Dynamsoft requires RGB888 format for optimal performance
    • Preset Templates: Using PT_READ_BARCODES for optimal detection settings


    4.3 Result Conversion and Coordinate Extraction





    QListBarcodeResult> BarcodeWorker::convertResults(CDecodedBarcodesResu lt *pResult)
    {
    QListBarcodeResult> results;

    if (!pResult || pResult->GetErrorCode() != EC_OK)
    {
    return results;
    }

    int count = pResult->GetItemsCount();

    for (int i = 0; i count; i++)
    {
    const CBarcodeResultItem *barcodeItem = pResult->GetItem(i);
    if (!barcodeItem)
    continue;

    BarcodeResult result;
    result.format = QString::fromUtf8(barcodeItem->GetFormatString());
    result.text = QString::fromUtf8(barcodeItem->GetText());

    CQuadrilateral location = barcodeItem->GetLocation();
    CPoint points[4];
    location.points[0] = points[0];
    location.points[1] = points[1];
    location.points[2] = points[2];
    location.points[3] = points[3];

    for (int j = 0; j 4; j++)
    {
    result.points.append(QPoint(points[j].coordinate[0], points[j].coordinate[1]));
    }

    results.append(result);
    }

    return results;
    }







    Step 5: Implementing Visual Overlay System

    5.1 Image Display with Barcode Overlay





    void MainWindow::updateImageDisplay(const QPixmap &pixmap, const QListBarcodeResult> &results)
    {
    QPixmap displayPixmap = pixmap;

    if (!results.isEmpty())
    {
    QPainter painter(&displayPixmap);
    painter.setPen(QPen(Qt::green, 3));
    painter.setFont(QFont("Arial", 12, QFont::Bold));

    for (const auto &result : results)
    {
    if (result.points.size() >= 4)
    {
    // Draw bounding polygon
    QPolygon polygon;
    for (const auto &point : result.points)
    {
    polygon point;
    }
    painter.drawPolygon(polygon);

    if (!result.points.isEmpty())
    {
    int textWidth = painter.fontMetrics().horizontalAdvance(result.tex t);
    QRect textRect(result.points[0].x(), result.points[0].y() - 20,
    textWidth + 10, 20);

    painter.fillRect(textRect, QColor(0, 255, 0, 180));
    painter.setPen(Qt::black);
    painter.drawText(result.points[0].x() + 5, result.points[0].y() - 5, result.text);
    painter.setPen(QPen(Qt::green, 3));
    }
    }
    }
    }

    ui->imageLabel->setPixmap(displayPixmap.scaled(ui->imageLabel->size(),
    Qt::KeepAspectRatio,
    Qt::SmoothTransformation));
    }







    5.2 Real-time Camera Overlay





    connect(openCVCamera, QOverloadconst QPixmap &>:f(&OpenCVCamera::frameReady),
    this, [this](const QPixmap &pixmap)
    {
    if (useOpenCVCamera && cameraLabel && cameraLabel->isVisible() && !cameraUpdatesPaused) {
    currentCameraFrame = pixmap;
    QSize labelSize = cameraLabel->size();

    if (labelSize.width() > 10 && labelSize.height() > 10) {
    if (!currentCameraResults.isEmpty()) {
    // Create overlay on current frame
    QPixmap overlayPixmap = pixmap;
    QPainter painter(&overlayPixmap);
    painter.setRenderHint(QPainter::Antialiasing);

    // Draw real-time barcode detection boxes
    for (const auto &result : currentCameraResults) {
    if (!result.points.isEmpty() && result.points.size() >= 4) {
    QPen pen(Qt::green, 3);
    painter.setPen(pen);

    // Draw detection polygon
    QPolygonF polygon;
    for (const auto &point : result.points) {
    polygon point;
    }
    painter.drawPolygon(polygon);

    // Draw format label
    if (!result.format.isEmpty()) {
    painter.setPen(QPen(Qt::yellow, 2));
    QFont font = painter.font();
    font.setPointSize(12);
    font.setBold(true);
    painter.setFont(font);

    QPointF textPos = result.points.first();
    textPos.setY(textPos.y() - 10);
    painter.drawText(textPos, result.format);
    }
    }
    }
    painter.end();

    QPixmap scaledPixmap = overlayPixmap.scaled(labelSize,
    Qt::KeepAspectRatio,
    Qt::SmoothTransformation);
    cameraLabel->setPixmap(scaledPixmap);
    } else {
    // No overlay needed
    QPixmap scaledPixmap = pixmap.scaled(labelSize,
    Qt::KeepAspectRatio,
    Qt::SmoothTransformation);
    cameraLabel->setPixmap(scaledPixmap);
    }
    }

    // Process frame for barcode detection
    if (barcodeWorker) {
    QImage image = pixmap.toImage();
    barcodeWorker->processImage(image);
    }
    }
    });







    Step 6: OpenCV Camera Implementation

    The application uses OpenCV exclusively for camera access, providing better reliability and cross-platform compatibility than Qt6 Multimedia.


    6.1 Camera Initialization and Setup





    void MainWindow::startCamera()
    {
    if (openCVCamera && openCVCamera->start(0))
    {
    // Show OpenCV camera display
    if (cameraLabel)
    {
    cameraLabel->show();
    }

    ui->startCameraButton->setEnabled(false);
    ui->stopCameraButton->setEnabled(true);

    ui->resultsTextEdit->append("OpenCV camera started successfully!");
    }
    else
    {
    QStringList availableCameras = openCVCamera ? openCVCamera->availableCameras() : QStringList();
    if (availableCameras.isEmpty())
    {
    ui->resultsTextEdit->append("No cameras detected by OpenCV");
    }
    else
    {
    ui->resultsTextEdit->append(QString("Available cameras: %1, but failed to start")
    .arg(availableCameras.join(", ")));
    }
    }
    }







    6.2 OpenCV Camera Class Implementation





    // opencvcamera.h
    class OpenCVCamera : public QObject
    {
    Q_OBJECT

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

    bool start(int cameraIndex = 0);
    void stop();
    bool isActive() const { return active; }
    bool isAvailable() const;
    QStringList availableCameras() const;

    signals:
    void frameReady(const QPixmap &frame);

    private slots:
    void captureFrame();

    private:
    cv::VideoCapture capture;
    QTimer *captureTimer;
    bool active;
    int currentCameraIndex;
    };







    6.3 Real-time Frame Processing





    void OpenCVCamera::captureFrame()
    {
    if (!active || !capture.isOpened())
    return;

    cv::Mat frame;
    if (capture.read(frame) && !frame.empty())
    {
    // Convert BGR to RGB for Qt
    cv::Mat rgbFrame;
    cv::cvtColor(frame, rgbFrame, cv::COLOR_BGR2RGB);

    // Convert to QImage then QPixmap
    QImage qImage(rgbFrame.data, rgbFrame.cols, rgbFrame.rows,
    rgbFrame.step, QImage::Format_RGB888);
    QPixmap pixmap = QPixmap::fromImage(qImage);

    emit frameReady(pixmap);
    }
    }







    Step 7: Building and Deployment

    Create build.bat for easy building:






    @echo off
    echo Building Qt Barcode Scanner in Release mode...

    REM Check for Qt6_DIR environment variable
    if not defined Qt6_DIR (
    echo Qt6_DIR environment variable not set. Searching for Qt installations...

    REM Search common Qt installation paths
    for %%d in (
    "C:\Qt\6.7.2\msvc2022_64\lib\cmake\Qt6"
    "C:\Qt\6.7.2\msvc2019_64\lib\cmake\Qt6"
    "C:\Qt\6.6.0\msvc2022_64\lib\cmake\Qt6"
    ) do (
    if exist %%d (
    set "Qt6_DIR=%%~d"
    echo Found Qt at: %%~d
    goto :found_qt
    )
    )

    echo Qt6 installation not found. Please install Qt6 or set Qt6_DIR manually.
    pause
    exit /b 1
    )

    :found_qt
    echo Using Qt6_DIR: %Qt6_DIR%
    set "CMAKE_PREFIX_PATH=%Qt6_DIR%\..\..\.."

    echo Configuring CMake...
    if not exist build mkdir build
    cd build

    cmake .. -DQt6_DIR="%Qt6_DIR%" -DOpenCV_DIR="%OpenCV_DIR%" -G "Visual Studio 17 2022"
    if errorlevel 1 (
    echo CMake configuration failed
    pause
    exit /b 1
    )

    echo Building project...
    cmake --build . --config Release
    if errorlevel 1 (
    echo Build failed
    pause
    exit /b 1
    )

    echo Build completed successfully
    echo Executable location: %CD%\bin\QtBarcodeScanner.exe

    echo To run the application:
    echo cd %CD%\bin
    echo QtBarcodeScanner.exe

    pause







    Source Code






    More...
Working...