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.
This article is Part 8 in a 16-Part Series.
- Part 1 - Building a C/C++ Barcode & QR Code Reader for Raspberry Pi with Dynamsoft SDK
- Part 2 - CMake: Build C++ Project for Windows, Linux and macOS
- Part 3 - How to Port Visual Studio C++ Project to Linux with CMake
- Part 4 - Insight Into Dynamsoft Barcode SDK Decoding Performance
- Part 5 - Building ARM64 Barcode and QR Code Scanner on Nvidia Jetson Nano
- Part 6 - How to Decode QR Code on Mac with Apple Silicon
- Part 7 - How to Develop a Desktop GUI Barcode Reader with Qt and C/C++
- Part 8 - How to Build a Desktop Barcode Scanner with Webcam Support Using Qt QCamera
- Part 9 - Building Command-line Barcode and QR Code Reader in C++
- Part 10 - How to Build Linux ARM32 and Aarch64 Barcode QR Scanner in Docker Container
- Part 11 - How to Link MSVC DLLs with MinGW GCC in Windows
- Part 12 - Transforming Raspberry Pi 4 into a Barcode Scanner with a C++ App, USB Camera, and OLED Display
- Part 13 - Building Windows Desktop Barcode Reader with Win32 API and Dynamsoft C++ Barcode SDK
- Part 14 - How to Build a Command-Line Barcode Reader with Rust and C++ Barcode SDK
- Part 15 - How to Decode Barcode and QR Code from WebP Images in C++ and Python
- Part 16 - Building a Desktop C++ Barcode Scanner with Slimmed-Down OpenCV and Webcam
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.
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
:
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
Source Code
https://github.com/yushulx/cmake-cpp-barcode-qrcode-mrz/tree/main/examples/9.x/qt