Barcode, OpenCV and QML (Qt 6)

In this post, I would like to share how to build a barcode scanner (for Android and iOS) in QML using OpenCV.

As a first step, we need to build the OpenCV library or download it from here: https://github.com/niqt/ocvdecoder (the readme discusses QR code scanning, but the library can also be used for barcode scanning).

To reach the goal we have to do:

  • Create the C++ class
  • Create the QML Item
  • Use the QML component

C++

The logic involves receiving frames from the camera and decoding them until the barcode is recognized. These frames are captured by videoSync, which is the object responsible for obtaining frames from the camera.

/*
 * This class implement a qml wrapper for the OpenCV bar code feature
 * */

class Bardecoder : public QObject
{
    Q_OBJECT
    QML_ELEMENT
    Q_PROPERTY(QObject* videoSink WRITE setVideoSink) // the video sync used to get the frames
    Q_PROPERTY(bool run READ run WRITE setRun) // start/top the decode
public:
    Bardecoder(QObject *parent = nullptr);
    void setVideoSink(QObject *videoSink);
    bool isDecoding() {return m_decoding; } // the status of the decoding - decondig in progress
    void setRun(bool run);
    bool run() {return m_run;}
public slots:
    void setFrame(const QVideoFrame &frame);


signals:
    void videoSyncChanged();
    void decoded(const QString &code);

private:
    QVideoSink *m_videoSink;
    cv::Ptr<cv::barcode::BarcodeDetector> m_bardet;
    QFuture<void> m_processThread;
    bool m_decoding;
    bool m_run;
};

The Bardecoder class is a QML_ELEMENT. It has two properties: videoSync (to set the videoSync) and run to initiate the decoding process.

The setFrame slot is invoked when videoSync captures a frame from the camera.

In the private section of the class, the key components are m_barded (the OpenCV barcode decoder) and m_processThread (the thread that executes the decoding action).

In the cpp part we have:

Bardecoder::Bardecoder(QObject *parent)
    : QObject{parent}
{
    m_bardet = makePtr<barcode::BarcodeDetector>("", ""); // create the bardetector
}

void Bardecoder::setVideoSink(QObject *videoSink)
{
    m_videoSink = qobject_cast<QVideoSink*>(videoSink);

    if (m_videoSink) {
        QObject::connect(m_videoSink, &QVideoSink::videoFrameChanged, // when a frame is received call setFrame
                         this, &Bardecoder::setFrame);
    }
}

void Bardecoder::setFrame(const QVideoFrame &frame)
{
    if(m_run && !isDecoding() && m_processThread.isFinished()) { // if start decode and is not decoding and the previous is finished => decode the frame
        m_decoding = true;
        QImage image = frame.toImage().convertToFormat(QImage::Format_RGB32).rgbSwapped(); // convert the frame to an imageso can be used from opecv

        m_processThread = QtConcurrent::run([=]() {
            if(image.isNull()) {
                m_decoding = false;
                return;
            }
            cv::Mat img(image.height(), image.width(), CV_8UC4, (void *) image.bits(), image.bytesPerLine());
            vector<Mat> points;

            vector<Point> corners;
            vector<cv::String> decode_info;
            vector<string> decode_type;
            m_bardet->detectMulti(img, corners);
            m_bardet->detectAndDecodeWithType(img, decode_info, decode_type, corners);
            if (decode_info.size() > 0) {
                auto code = QString(decode_info.at(0).c_str());
                if (code.length() > 0) // if the barcode is decoded emit the signal
                    emit decoded(QString(decode_info.at(0).c_str()));
            }
            m_decoding = false;
        });
    }
}

void Bardecoder::setRun(bool run)
{
    m_run = run;
}

The two main components are setVideoSync, which sets the slot, and setFrame. When a frame changes, setVideoSync triggers setFrame.

Within setFrame, the decoding action occurs. This process begins if run is true (where run functions as a start command). The decoding action starts only if there is no other frame currently being decoded and if the previous decoding thread has finished. Before using the OpenCV decoder functions, it’s necessary to transform the frame into an image in a specific format. Once this transformation is complete, the OpenCV functions are called as detailed in the OpenCV documentation. If the barcode is successfully decoded, a signal is emitted.

QML

Create the BarReader QML component:

import QtQuick
import Felgo
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.1
import QtMultimedia
import bardecoder

/*
  This element contains the logic and the components for the Barcode reader.
  It use the camera, the CaputureSession to capture the video from the camera and it sent
  to VideoOutput to be shown. The decoder get the video frames using the videoSync from the VideoOutput.
  The signal barCaptured is emitted if the decoder found a barcode.
  */

Item {

    id: item

    // This property enable/disable the decoding action
    property alias run: decoder.run

    // This signal is emitted when the barcode is found
    signal barCaptured(string barcode)

    Camera // The camera
    {
        id:camera
        active: run
        focusMode: Camera.FocusModeAuto
    }

    CaptureSession {
        camera: camera
        videoOutput: videoOutput
    }

    VideoOutput
    {
        id: videoOutput
        anchors.fill: parent

        // The follow properties are used to scale the red capture area on the camera video
        property double captureRectStartFactorX: 0.25
        property double captureRectStartFactorY: 0.25
        property double captureRectFactorWidth: 0.5
        property double captureRectFactorHeight: 0.5

        MouseArea { // Clicking remove the autofocus
            anchors.fill: parent
            onClicked: {
                camera.customFocusPoint = Qt.point(mouseX / width,  mouseY / height);
                camera.focusMode = Camera.FocusModeManual;
            }
        }

        Rectangle { // The capture zone
            id: captureZone
            color: "red"
            opacity: 0.2
            width: parent.width * parent.captureRectFactorWidth
            height: parent.height * parent.captureRectFactorHeight
            x: parent.width * parent.captureRectStartFactorX
            y: parent.height * parent.captureRectStartFactorY

        }
    }

    Bardecoder { // The barcode decoder
        id: decoder
        videoSink: videoOutput.videoSink // in this way capture the video frames

        onDecoded: code => {
                       console.log("QR " + code)
                       run = false // stop to decode
                       barCaptured(code) // send the value
                   }
    }
}

The logic behind this process is detailed in the code comments. The only aspect not specified in these comments is that we use a pink (red with opacity)rectangle to define the area of the camera used for decoding.

Now, we can integrate it into our page in the following way:

BarReader {
        id: isbnReader
        visible: false
        run: false
        anchors.fill: parent
        onBarCaptured: barcode => {
            visible = false // hide itself
            run = false // stop to scanning
        }

    }

A working example of this project can be found here: https://github.com/niqt/felgobar. Although it uses Felgo, the same principles apply to pure Qt as well.

TableView in QML

Take a look at how the TableView works in Qt6 with QML. If we refer to the example from the official documentation, we find this code:

 import QtQuick 2.14
 import Qt.labs.qmlmodels 1.0

 TableView {
     anchors.fill: parent
     columnSpacing: 1
     rowSpacing: 1
     clip: true

     model: TableModel {
         TableModelColumn { display: "name" }
         TableModelColumn { display: "color" }

         rows: [
             {
                 "name": "cat",
                 "color": "black"
             },
             {
                 "name": "dog",
                 "color": "brown"
             },
             {
                 "name": "bird",
                 "color": "white"
             }
         ]
     }

     delegate: Rectangle {
         implicitWidth: 100
         implicitHeight: 50
         border.width: 1

         Text {
             text: display
             anchors.centerIn: parent
         }
     }
 }

It produces this:

If we add more columns to the model in the following manner:

model: TableModel {
                    TableModelColumn { display: "name" }
                    TableModelColumn { display: "color" }
                    TableModelColumn { display: "feet" }
                    TableModelColumn { display: "fly" }

                    rows: [
                        {
                            "name": "cat",
                            "color": "black",
                            "feet": "four",
                            "fly": "no"
                        },
                        {
                            "name": "dog",
                            "color": "brown",
                            "feet": "four",
                            "fly": "no"
                        },
                        {
                            "name": "bird",
                            "color": "white",
                            "feet": "two",
                            "fly": "yes"
                        }
                    ]
                }

we have:

In this approach, not all elements are visible because each cell is of the same size. While this uniformity can be visually appealing, it may not always be practical. Next, let’s see how we can add a custom header and adjust the cells to have widths based on the size of the elements within them.

Let’s start with the header:

           HorizontalHeaderView {
                id: horizontalHeader
                anchors.left: tableView.left
                anchors.top: parent.top
                syncView: tableView
                clip: true
                model: ["Name", "Color", "Feet", "Fly"]
                delegate:
                    Rectangle {
                        implicitWidth: txt.width + dp(5)
                        implicitHeight: txt.height + dp(5)
                        color: "darkgray"
                        AppText {
                            id: txt
                            text: modelData
                            anchors.centerIn: parent
                            color: "white"
                        }

                }

            }

First, we attach the header to the left side of the table and set the syncView to the tableView. This ensures synchronization between the table and the header, meaning each header column aligns with its corresponding column in the tableView. Next, we define the labels for the header. Finally, we see in the delegate how to display the header’s cells.

The tableView:

            TableView {
                id: tableView
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.top: horizontalHeader.bottom
                anchors.bottom: parent.bottom
                columnSpacing: 1
                rowSpacing: 1
                clip: true
                contentWidth: parent.width
                columnWidthProvider: column => { return -1; }
                model: // is the same

                delegate: Rectangle {
                    implicitWidth: Math.max(horizontalHeader.columnWidth(column), txtRow.width) + dp(5)
                    implicitHeight: txtRow.height + dp(10)
                    border.color: "darkgray"
                    Text {
                        id: txtRow
                        text: display
                        anchors.centerIn: parent
                    }
                }
            }

The model remains the same as previously mentioned and is not included here. Regarding the positioning, the top of the table is anchored to the bottom of the header, as one would expect. The code enabling cell width to be based on the element’s width within the cell is as follows: columnWidthProvider: column => { return -1; }. The columnWidthProvider property expects a function that returns the width for a column. However, if it returns -1, the size is automatically calculated based on the size of the element in the cell.

Another important aspect to note is the use of the following code:

implicitWidth: Math.max(horizontalHeader.columnWidth(column), txtRow.width) + dp(5)

Why is this necessary? It ensures that if the text in the header of a column is wider than the text in the data cells, the size of the header remains readable. This code calculates the maximum width between the header and the row text, and then adds a small margin (dp(5)) for better visual spacing.

The final result is:

“Note: English is not my native language, so please excuse any errors. I would appreciate your assistance in correcting them.”

MongoDb, Go and Docker Compose

Go Docker Mongodb

In this brief post, I’ll share how to use MongoDB in a Golang application executed within a Docker container, using docker-compose. The challenge is ensuring that the Golang application waits for the MongoDB instance to be fully operational before starting, to avoid connection retries.

To achieve this, create a docker-compose.yml file.

version: '3.9'

services:
api:
build: .
ports:
- 8080:8080
links:
- mongodb
depends_on:
- mongodb
environment:
WAIT_HOSTS: mongodb:27017

mongodb:
image: mongo:7.0
ports:
- 27017:27017
volumes:
- ~/apps/mongo:/data/db

From the Docker Compose file, we see that we have a service in the build directory that listens on port 8080. The ‘links’ property set to MongoDB means that the application can refer to the MongoDB service using the hostname ‘mongodb’. It also ‘depends on’ MongoDB, indicating that it must wait for MongoDB to start listening on port 27017 before initiating. In short, our service can only start once the MongoDB port is open.

Docker

FROM golang:1.21.3-alpine as builder
WORKDIR /root/
RUN apk add --update --no-cache make

RUN mkdir -p /root/mongo-docker-compose
WORKDIR /root/mongo-docker-compose
COPY . .
RUN make build

FROM alpine:3.18
WORKDIR /root/

COPY --from=builder /root/mongo-docker-compose/bin/ .

ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.8.0/wait wait
RUN chmod +x wait

ENV TESTDB_MONGO=mongodb

EXPOSE 8080
CMD ./wait && ./mongo-docker-compose

In the first part of the Dockerfile, the Golang application is built. In the second part, we prepare for its execution. More specifically, we download the ‘wait’ command and assign execution permissions to it. It’s small command-line utility to wait for other docker images to be started while using docker-compose. It permits waiting for a fixed amount of seconds, until a TCP port is open on a target image (our case) or until a file or directory is present on the local filesystem.

Continuing our examination of the Dockerfile, we observe that the ‘TESTDB_MONGO’ variable is also defined. This variable, utilized in the Go code, holds the ‘name’ of the MongoDB service as specified in the docker-compose file. If the definition of this variable is omitted, or if it contains an incorrect value, the application will be unable to connect to MongoDB, thereby rendering the application non-functional.

Code

Let’s now turn our attention to the Go code above. Here, we will explore how it interacts with the setup we’ve discussed, particularly in relation to the Docker and MongoDB configuration.

const (
	hostKey        = key("hostKey")
	UserCollection = "users"
	DBName         = "test"
	mongoKey       = "TESTDB_MONGO"
)

// Initialize the database

func init() {
	mongoServer := os.Getenv(mongoKey)
	if mongoServer == "" {
		mongoServer = "localhost"
	}
	ctx = context.Background()
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	ctx = context.WithValue(ctx, hostKey, mongoServer)

	var err error
	testDb, err = configDB(ctx)

	if err != nil {
		fmt.Printf("db configuration %s\n", err)
	}
	fmt.Printf("db connected ")
}

It’s important to focus primarily on the ‘mongoKey’, which contains the value ‘TESTDB_MONGO’. This is the name of the environment variable that holds the MongoDB server’s name. That’s all. Enjoy with Go and Mongodb

It’s possible found the simple project here:

https://github.com/niqt/mongo-docker-compose

“Note: English is not my native language, so please excuse any errors. I would appreciate your assistance in correcting them.”

Close Keyboard on IOS

In the last week-end, while I was working on my side project, I discovered a problem: on iOS the keyboard stay opened after it’s opened due to a click in an input text. The obvious solution that I thought is:

Page {
      MouseArea {
                anchors.fill: parent
                enabled: Qt.inputMethod.visible
                onClicked: Qt.inputMethod.hide()
      }
      //.... input fields
}

Thus in this way, clicking/tapping on the screen, the keyboard should disappear. In my case it doesn’t work because the input fields are in a ScrollView (and if you move the MouseArea in the ScrollView the view doesn’t scroll).

My solution:

Page {
      MouseArea {
                anchors.fill: parent
                enabled: Qt.inputMethod.visible
                onClicked: Qt.inputMethod.hide()
                propagateComposedEvents: true
                z: 10
      }
      ScrollView {
        id: scrollView
        anchors.fill: parent
        anchors.bottomMargin: dp(60)
        clip: true
        contentWidth: -1

        ColumnLayout {
            id: colView
            //.... input fields
}

The tips&trick is set a Z value to the MouseArea and propagates the mouse event to the object in the bottom layers.

Carousel

Today, we often see complex interfaces built using carousel components, as seen on Netflix, Amazon, and others. In this post, I will show you how to build one using SwiftUI. Let’s get started.

First, I’ll show you an example of a carousel built using only text and a rectangle. This is a minimal example meant to help you understand the fundamentals. Then, in the second example, I’ll add some images.

As usual, let’s start from the end.

It’s possible to scroll any row horizontally and vertically.

The code:

struct ContentView: View {
    var colorsRow: [Color] = [.purple,.yellow]
    var body: some View {
        ScrollView {
            VStack(spacing: 5) {
                ForEach(0..<10) { j in
                    ScrollView(.horizontal) {
                        HStack(spacing: 5) {
                            ForEach(0..<10) { i in 
                                VStack {
                                    Text("Item \(i)")
                                        .foregroundColor(colorsRow[j % 2])
                                        .font(.largeTitle)
                                        .frame(width: 200, height: 200)
                                        .background(.black)
                                        
                                }.cornerRadius(20)
                            }
                        }
                    }
                }
            }.background(.black)
                .opacity(0.9)
        }.edgesIgnoringSafeArea(.all)
    }
}

We use two ScrollViews for this implementation: the first for vertical scroll (default behavior) and the second for horizontal scrolling. Note the “.horizontal” parameter. In the VStack, we specify the spacing, which is the space between rows. In the HStack, the spacing is the space between columns.

The first ForEach loop is for rows, and the second is for columns. The color of the text is chosen using the modulo of the row.

Finally, we added a bit of opacity for a better Look and Feel.

Note: English is not my native language, so I’m sorry for some errors. I appreciate it if you correct me.