How to Build a Desktop Barcode Scanner with Webcam Support Using Qt QCamera

A few days ago, I published a blog post how to implement a desktop barcode reader application using Qt and C++. That application was limited to scanning barcodes from still images. In this article, I’ll take it a step further by adding webcam support, enabling real-time barcode scanning for a more dynamic user experience.

Prerequisites

CMake Configuration for QCamera on Windows

To enable webcam support in a Qt application, you need to import the QCamera class from the QtMultimedia library, available in Qt 5.

Qt maintenance tool

The QCamera header file can be found at Qt/5.12.11/mingw73_64/include/QtMultimedia/qcamera.h.

Update your CMakeLists.txt to link the QtMultimedia library as follows:

cmake_minimum_required(VERSION 3.5)

project(BarcodeReader VERSION 0.1 LANGUAGES CXX)

set(CMAKE_INCLUDE_CURRENT_DIR ON)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

if (CMAKE_HOST_WIN32)
    link_directories("${PROJECT_SOURCE_DIR}/platform/windows/lib/") 
elseif(CMAKE_HOST_UNIX)
    link_directories("${PROJECT_SOURCE_DIR}/platform/linux/")
endif()
include_directories("${PROJECT_SOURCE_DIR}/include/")

find_package(Qt5 COMPONENTS Widgets REQUIRED)
find_package(Qt5MultimediaWidgets REQUIRED)

set(PROJECT_SOURCES
        main.cpp
        mainwindow.cpp
        mainwindow.h
        mainwindow.ui
        myvideosurface.h
        myvideosurface.cpp
)

add_executable(${PROJECT_NAME} ${PROJECT_SOURCES})

if (CMAKE_HOST_WIN32)
    target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Widgets Qt5::MultimediaWidgets "DBRx64")    
elseif(CMAKE_HOST_UNIX)
    target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Widgets Qt5::MultimediaWidgets "DynamsoftBarcodeReader")
endif()

if(CMAKE_HOST_WIN32)
    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 
        COMMAND ${CMAKE_COMMAND} -E copy_directory
        "${PROJECT_SOURCE_DIR}/platform/windows/bin/"      
        $<TARGET_FILE_DIR:${PROJECT_NAME}>)
endif()

The myvideosurface.h file and myvideosurface.cpp files handle webcam preview and real-time barcode recognition. We’ll explore their implementation in the next section.

Real-time Barcode Scanning

QCamera provides a setViewfinder() function to display the webcam preview. This function accepts parameters like QVideoWidget, QGraphicsVideoItem and QAbstractVideoSurface.

Since our goal is to capture frames for barcode detection, we will subclass QAbstractVideoSurface and override the present() method to access the frame data.

First, create the myvideosurface.h file:

#ifndef MYVIDEOSURFACE_H
#define MYVIDEOSURFACE_H

#include <QImage>
#include <QPixmap>
#include <QDebug>
#include <QCamera>
#include <QCameraInfo>
#include <QAbstractVideoSurface>
#include <QLabel>
#include <QDateTime>

#include "DynamsoftCommon.h"
#include "DynamsoftBarcodeReader.h"

QT_BEGIN_NAMESPACE
namespace Ui
{
    class MainWindow;
}
QT_END_NAMESPACE

class MyVideoSurface : public QAbstractVideoSurface
{
    Q_OBJECT
private:
    Ui::MainWindow *ui;
    void *reader;
    bool is_detecting;

public:
    MyVideoSurface(QObject *parent, Ui::MainWindow *ui, void *reader);
    ~MyVideoSurface();

    void reset();

    QList<QVideoFrame::PixelFormat>
    supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const;

    bool present(const QVideoFrame &frame);
};
#endif // MYVIDEOSURFACE_H

The constructor accepts a Ui::MainWindow pointer to access the UI widgets, and a void *reader pointer for barcode detection.

Next, implement MyVideoSurface in myvideosurface.cpp:

Supported Pixel Formats

To retrieve supported pixel formats, override the supportedPixelFormats() method. Initially, you may try QVideoFrame::Format_RGB24, but if it’s unsupported, switch to QVideoFrame::Format_ARGB32:

QList<QVideoFrame::PixelFormat> MyVideoSurface::supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const
{
    return QList<QVideoFrame::PixelFormat>()
               << QVideoFrame::Format_RGB32;
}

Processing Frames in present()

In present() method, map the frame data to a QImage:

bool MyVideoSurface::present(const QVideoFrame &frame)
{
    if (frame.isValid() && is_detecting)
    {
        QVideoFrame cloneFrame(frame);
        cloneFrame.map(QAbstractVideoBuffer::ReadOnly);
        const QImage img(cloneFrame.bits(),
                         cloneFrame.width(),
                         cloneFrame.height(),
                         QVideoFrame::imageFormatFromPixelFormat(cloneFrame.pixelFormat()));

        cloneFrame.unmap();

        return true;
    }
    return false;
}

Flipping and Decoding the Image

To display the preview, copy the frame and flip it using QImage::mirrored():

QImage cp = img.copy().mirrored(false, true);

Then, call DBR_DecodeBuffer() to decode barcodes and draw bounding boxes:

int ret = DBR_DecodeBuffer(reader, (unsigned char *)cp.bits(), cp.width(), cp.height(), cp.bytesPerLine(), IPF_ARGB_8888, "");

DBR_GetAllTextResults(reader, &handler);

QPixmap pm = QPixmap::fromImage(cp);
QPainter painter(&pm);
painter.setPen(Qt::red);

QString out = "";
TextResult **results = handler->results;
for (int index = 0; index < handler->resultsCount; index++)
{
    LocalizationResult* localizationResult = results[index]->localizationResult;
    out += "Index: " + QString::number(index) + ", Elapsed time: " + QString::number(start.msecsTo(end)) + "ms\n";
    out += "Barcode format: " + QString(results[index]->barcodeFormatString) + "\n";
    out += "Barcode value: " + QString(results[index]->barcodeText) + "\n";
    out += "Bounding box: (" + QString::number(localizationResult->x1) + ", " + QString::number(localizationResult->y1) + ") "
    + "(" + QString::number(localizationResult->x2) + ", " + QString::number(localizationResult->y2) + ") "
    + "(" + QString::number(localizationResult->x3) + ", " + QString::number(localizationResult->y3) + ") "
    + "(" + QString::number(localizationResult->x4) + ", " + QString::number(localizationResult->y4) + ")\n";
    out += "----------------------------------------------------------------------------------------\n";

    painter.drawLine(localizationResult->x1, localizationResult->y1, localizationResult->x2, localizationResult->y2);
    painter.drawLine(localizationResult->x2, localizationResult->y2, localizationResult->x3, localizationResult->y3);
    painter.drawLine(localizationResult->x3, localizationResult->y3, localizationResult->x4, localizationResult->y4);
    painter.drawLine(localizationResult->x4, localizationResult->y4, localizationResult->x1, localizationResult->y1);
}

DBR_FreeTextResults(&handler);

painter.end();

Displaying the Results

Finally, display the processed image and barcode recognition results:

ui->label->setPixmap(pm.scaled(ui->label->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
ui->textEdit_results->setText(out);

Camera Control Logic

In mainwindow.cpp, initialize the camera and connect the control buttons. Add camera control buttons in mainwindow.ui:

Qt UI button

Initialize Camera

In the MainWindow constructor, detect and initialize the available camera:

QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
if (cameras.size() > 0) 
{
    for( int i = 0; i < cameras.count(); ++i )
    { 
        QCameraInfo cameraInfo = cameras.at(i);
        qDebug() << cameraInfo.deviceName();
        qDebug() << cameraInfo.description();
        camera = new QCamera(cameraInfo);
        surface = new MyVideoSurface(this, ui, reader);
        camera->setViewfinder(surface);
        break;
    }
}
else {
    ui->pushButton_open->setEnabled(false);
    ui->pushButton_stop->setEnabled(false);
}

Start and Stop Camera

Bind the buttons to the camera control functions:

connect(ui->pushButton_open, SIGNAL(clicked()), this, SLOT(startCamera()));
connect(ui->pushButton_stop, SIGNAL(clicked()), this, SLOT(stopCamera()));

void MainWindow::startCamera()
{
    surface->reset();
    camera->start();
}

void MainWindow::stopCamera()
{
    camera->stop();
}

Build and Run

To build and run the barcode scanner application:

mkdir build
cd build
cmake -G "MinGW Makefiles" ..
cmake --build .
./BarcodeReader.exe

Qt C++ GUI barcode scanner

Source Code

https://github.com/yushulx/cmake-cpp-barcode-qrcode-mrz/tree/main/examples/9.x/qt