Building a Desktop C++ Barcode Scanner with Slimmed-Down OpenCV and Webcam
Tags: cpp, opencv, cmake, camera
Published: 2024-06-19 07:40:58

**OpenCV** is a powerful library capable of handling a wide range of computer vision tasks. However, its comprehensive functionality can result in a large library size, which may be excessive for applications that only require **cross-platform camera streaming**. In this article, we will demonstrate how to slim down OpenCV to retain only the essential camera functionalities. We will then guide you through the process of building a desktop barcode scanner in C++ using the tailored OpenCV and **Dynamsoft C++ Barcode SDK v10.x** on Windows and Linux. A **CMake** project will be created to manage the build process efficiently.

## Prerequisites
- [Dynamsoft Barcode Reader Trial License](https://www.dynamsoft.com/customer/license/trialLicense/?product=dbr): You will receive a 30-day free trial license by email.
- [Dynamsoft C++ Barcode SDK v10.x](https://www.dynamsoft.com/barcode-reader/downloads/1000003-confirmation/): A ZIP package that contains the shared library and header files for Windows and Linux.
    
## Step 1: Tailoring OpenCV for Camera-Only Functionality
To reduce the size of OpenCV and include only the necessary components for camera streaming, you need to configure the OpenCV build process to exclude unnecessary modules. This involves specifying build options to disable certain features and modules that are not required for your application. Here's how you can do it:

1. Obtain the OpenCV source code from the official OpenCV repository on GitHub:
    
    ```bash
    git clone https://github.com/opencv/opencv.git
    cd opencv
    ```

2. Use CMake to configure the build options. We will disable all the non-essential modules and keep only the core, highgui, videoio, and imgproc modules needed for camera functionalities.
    
    ```bash
    mkdir build
    cd build
    cmake -DBUILD_SHARED_LIBS=ON -DBUILD_opencv_world=OFF -DBUILD_opencv_apps=OFF -DBUILD_opencv_calib3d=OFF -DBUILD_opencv_dnn=OFF -DBUILD_opencv_features2d=OFF -DBUILD_opencv_flann=OFF -DBUILD_opencv_gapi=OFF -DBUILD_opencv_ml=OFF -DBUILD_opencv_objdetect=OFF -DBUILD_opencv_photo=OFF -DBUILD_opencv_stitching=OFF -DBUILD_opencv_video=OFF -DBUILD_TESTS=OFF -DBUILD_PERF_TESTS=OFF -DBUILD_EXAMPLES=OFF -DBUILD_DOCS=OFF ..
    ```

3. Once the configuration is complete, build OpenCV with the specified options.
    
    ```bash
    cmake --build . --config Release
    ```
    

By following these steps, you will have a streamlined version of OpenCV that includes only the necessary components for handling camera input, significantly reducing the library size. This tailored version of OpenCV will be used in the subsequent steps to build a desktop barcode scanner application in C++.

## Step 2: Setting Up the CMake Project with OpenCV Libraries
With the tailored version of OpenCV built, the next step is to set up a CMake project that utilizes these libraries to build a simplified camera library. 

1. Set up the project directory structure as follows:

    ```bash
    camera_lib/
    ├── CMakeLists.txt
    ├── example
    |   ├── CMakeLists.txt
    |   └── main.cpp
    ├── lib/
    |   ├── linux/
    |   |   ├── libopencv_core.so
    |   |   ├── libopencv_highgui.so
    |   |   ├── libopencv_imgcodecs.so
    |   |   ├── libopencv_imgproc.so
    |   |   └── libopencv_videoio.so
    │   └── windows/
    |       ├── opencv_core480.dll
    |       ├── opencv_highgui480.dll
    |       ├── opencv_imgcodecs480.dll
    |       ├── opencv_imgproc480.dll
    |       ├── opencv_videoio480.dll
    |       ├── opencv_core480.lib
    |       ├── opencv_highgui480.lib
    |       ├── opencv_imgcodecs480.lib
    |       ├── opencv_imgproc480.lib
    |       └── opencv_videoio480.lib
    ├── src/
    │   ├── camera_lib.cpp
    │   └── CMakeLists.txt
    └── include/
        ├── camera_lib.h
        └── opencv2
    ```
    The CMake project is organized into several key directories: `src`, `example`, `lib`, and `include`. The `src` directory houses the source files for the camera library, which leverages the tailored OpenCV libraries. The `example` directory contains the main application file, showcasing how to use the camera library to implement barcode scanning functionality. The `lib` directory holds the OpenCV libraries for both Windows and Linux platforms. Finally, the `include` directory contains the header files necessary for the camera library and OpenCV.


2. Create the `CMakeLists.txt` file in the project root directory:

    ```cmake
    cmake_minimum_required(VERSION 3.10)
    project(camera_lib)
    
    add_subdirectory(src)
    add_subdirectory(example)

    ```

3. Create the `CMakeLists.txt` file in the `src` directory:

    ```cmake
    cmake_minimum_required(VERSION 3.10)
    project(camera_lib)
    
    # Set the library path based on the operating system
    if(WIN32)
        link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../lib/windows)
        set(OPENCV_LIBS opencv_core480 opencv_highgui480 opencv_videoio480 opencv_imgproc480)
    elseif(UNIX)
        link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../lib/linux)
        set(OPENCV_LIBS opencv_core opencv_highgui opencv_videoio opencv_imgproc)
    endif()
    
    # Create the shared library
    add_library(camera_lib SHARED camera_lib.cpp)
    target_include_directories(camera_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../include)
    target_link_libraries(camera_lib ${OPENCV_LIBS})
    
    if(MSVC)
        target_compile_definitions(camera_lib PRIVATE CAMERA_LIB_EXPORTS)
    endif()
    ```

4. Create the `CMakeLists.txt` file in the `example` directory:

    ```cmake
    cmake_minimum_required(VERSION 3.10)
    project(camera_example)
    
    if(WIN32)
        link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../lib/windows ${CMAKE_CURRENT_SOURCE_DIR}/../../examples/10.x/sdk/platforms/win/lib)
        file(GLOB DLL_FILES "${CMAKE_CURRENT_SOURCE_DIR}/../lib/windows/*.dll")
        set(DBR_LIBS "DynamsoftCorex64" "DynamsoftLicensex64" "DynamsoftCaptureVisionRouterx64" "DynamsoftUtilityx64")
    elseif(UNIX)
        SET(CMAKE_CXX_FLAGS "-std=c++11 -O3 -Wl,-rpath=$ORIGIN")
        SET(CMAKE_INSTALL_RPATH "$ORIGIN")
        link_directories(${CMAKE_CURRENT_SOURCE_DIR}/../lib/linux ${CMAKE_CURRENT_SOURCE_DIR}/../../examples/10.x/sdk/platforms/linux)
        set(DBR_LIBS "DynamsoftCore" "DynamsoftLicense" "DynamsoftCaptureVisionRouter" "DynamsoftUtility" pthread)
    endif()
    
    # Create the executable
    add_executable(camera_example main.cpp)
    target_include_directories(camera_example PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../include ${CMAKE_CURRENT_SOURCE_DIR}/../../examples/10.x/sdk/include)
    target_link_libraries(camera_example camera_lib ${DBR_LIBS})
    
    if(WIN32)
        add_custom_command(TARGET camera_example POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:camera_lib> $<TARGET_FILE_DIR:camera_example>)
    
        # Copy the DLL files to the executable directory
        foreach(DLL_FILE ${DLL_FILES})
            add_custom_command(TARGET camera_example POST_BUILD
                COMMAND ${CMAKE_COMMAND} -E copy_if_different
                ${DLL_FILE}
                $<TARGET_FILE_DIR:camera_example>)
        endforeach()
    
        add_custom_command(TARGET camera_example POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_directory
        ${CMAKE_CURRENT_SOURCE_DIR}/../../examples/10.x/sdk/platforms/win/bin/      
        $<TARGET_FILE_DIR:camera_example>)
    endif()
    
    if(UNIX)
        add_custom_command(TARGET camera_example POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:camera_lib> $<TARGET_FILE_DIR:camera_example>)
    endif()
    
    add_custom_command(TARGET camera_example POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy
    ${CMAKE_CURRENT_SOURCE_DIR}/../../examples/10.x/sdk/DBR-PresetTemplates.json
    $<TARGET_FILE_DIR:camera_example>/DBR-PresetTemplates.json)
    
    add_custom_command(TARGET camera_example POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy
    ${CMAKE_CURRENT_SOURCE_DIR}/../../examples/10.x/sdk/DLR-PresetTemplates.json
    $<TARGET_FILE_DIR:camera_example>/DLR-PresetTemplates.json)
    
    add_custom_command(TARGET camera_example POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_FILE_DIR:camera_lib>/CharacterModel
    COMMAND ${CMAKE_COMMAND} -E copy_directory
    ${CMAKE_CURRENT_SOURCE_DIR}/../../examples/10.x/sdk/CharacterModel
    $<TARGET_FILE_DIR:camera_example>/CharacterModel)
    ```

    Replace the `DBR_LIBS` variable with the actual library names of the Dynamsoft Barcode SDK v10.x for your platform.

## Step 3: Implementing the Camera Library and Barcode Scanner Application

In this step, we will develop a straightforward camera library to capture and display video frames from the webcam. Subsequently, we will create a barcode scanner application that leverages the camera library to stream video frames and decode barcodes in real-time using the Dynamsoft C++ Barcode SDK v10.x.

### C++ Camera Library
1. In the `src` directory, create the `camera_lib.h` header file:

    ```cpp
    #ifndef CAMERA_LIB_H
    #define CAMERA_LIB_H
    
    #ifdef _WIN32
    #ifdef CAMERA_LIB_EXPORTS
    #define CAMERA_LIB_API __declspec(dllexport)
    #else
    #define CAMERA_LIB_API __declspec(dllimport)
    #endif
    #else
    #define CAMERA_LIB_API
    #endif
    
    #include <cstdint>
    
    enum PixelFormat
    {
        PIXEL_FORMAT_BGR,
        PIXEL_FORMAT_GRAY
    };
    
    extern "C"
    {
        typedef struct
        {
            int width;
            int height;
            int stride;
            PixelFormat pixel_format;
            uint8_t *data;
        } ImageData;
    
        CAMERA_LIB_API int open_camera(int camera_index);
        CAMERA_LIB_API void close_camera();
        CAMERA_LIB_API ImageData get_frame();
        CAMERA_LIB_API void release_frame(ImageData *image);
        CAMERA_LIB_API void display_frame(const char *name, const ImageData *image);
        CAMERA_LIB_API void draw_line(ImageData *image, int x1, int y1, int x2, int y2, int thickness, int r, int g, int b);
        CAMERA_LIB_API void draw_text(ImageData *image, const char *text, int x, int y, int font_scale, int thickness, int r, int g, int b);
        CAMERA_LIB_API int wait_key(int delay);
    }
    
    #endif // CAMERA_LIB_H
    
    ```

    This header file defines the `ImageData` struct to represent image data and declares functions for camera initialization, frame retrieval, frame display, and drawing operations.

2. Implement the camera library functions in the `camera_lib.cpp` file within the `src` directory:

    ```cpp
    #include "camera_lib.h"
    #include <opencv2/opencv.hpp>
    
    static cv::VideoCapture cap;
    
    int open_camera(int camera_index)
    {
        cap.open(camera_index);
        return cap.isOpened() ? 0 : -1;
    }
    
    void close_camera()
    {
        if (cap.isOpened())
        {
            cap.release();
        }
    }
    
    ImageData get_frame()
    {
        ImageData image = {0, 0, 0, PIXEL_FORMAT_BGR, nullptr};
        if (cap.isOpened())
        {
            cv::Mat frame;
            cap >> frame;
            if (!frame.empty())
            {
                image.width = frame.cols;
                image.height = frame.rows;
                image.stride = frame.step;
                image.pixel_format = frame.channels() == 3 ? PIXEL_FORMAT_BGR : PIXEL_FORMAT_GRAY;
                size_t data_size = frame.total() * frame.elemSize();
                image.data = new uint8_t[data_size];
                std::memcpy(image.data, frame.data, data_size);
            }
        }
        return image;
    }
    
    void release_frame(ImageData *image)
    {
        if (image->data)
        {
            delete[] image->data;
            image->data = nullptr;
        }
    }
    
    void display_frame(const char *name, const ImageData *image)
    {
        if (image && image->data)
        {
            int type = image->pixel_format == PIXEL_FORMAT_BGR ? CV_8UC3 : CV_8UC1;
            cv::Mat frame(image->height, image->width, type, image->data, image->stride);
            cv::imshow(name, frame);
        }
    }
    
    void draw_line(ImageData *image, int x1, int y1, int x2, int y2, int thickness, int r, int g, int b)
    {
        if (image && image->data)
        {
            int type = image->pixel_format == PIXEL_FORMAT_BGR ? CV_8UC3 : CV_8UC1;
            cv::Mat frame(image->height, image->width, type, image->data, image->stride);
            cv::line(frame, cv::Point(x1, y1), cv::Point(x2, y2), cv::Scalar(b, g, r), thickness);
            std::memcpy(image->data, frame.data, frame.total() * frame.elemSize());
        }
    }
    
    void draw_text(ImageData *image, const char *text, int x, int y, int font_scale, int thickness, int r, int g, int b)
    {
        if (image && image->data)
        {
            int type = image->pixel_format == PIXEL_FORMAT_BGR ? CV_8UC3 : CV_8UC1;
            cv::Mat frame(image->height, image->width, type, image->data, image->stride);
            cv::putText(frame, text, cv::Point(x, y), cv::FONT_HERSHEY_SIMPLEX, font_scale, cv::Scalar(b, g, r), thickness);
            std::memcpy(image->data, frame.data, frame.total() * frame.elemSize());
        }
    }
    
    int wait_key(int delay)
    {
        return cv::waitKey(delay);
    }
    
    ```


### Desktop Barcode Scanner Application
In the `example/main.cpp` file, follow these steps to complete the barcode scanner application:

1. Include the necessary headers and define a `BarcodeResult` struct to store barcode decoding results:

    ```cpp
    #include <iostream>
    #include <vector>
    #include <mutex>
    #include "camera_lib.h"
    
    #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
    {
        std::string type;
        std::string value;
        int x1, y1, x2, y2, x3, y3, x4, y4;
        int frameId;
    };
    ```

2. Set the license key for the Dynamsoft Barcode SDK in the `main` function:

    ```cpp
    int main()
    {
        if (open_camera(0) == 0)
        {
            std::cout << "Camera opened successfully." << std::endl;
    
            int iRet = -1;
            char szErrorMsg[256];
            iRet = CLicenseManager::InitLicense("LICENSE-KEY", szErrorMsg, 256);
            if (iRet != EC_OK)
            {
                std::cout << szErrorMsg << std::endl;
            }
    
            ...
        }
        return 0;
    }
    ```

3. Instantiate `CCaptureVisionRouter` and `CImageSourceAdapter` objects for buffering and processing video frames from the camera:

    ```cpp
    class MyVideoFetcher : public CImageSourceAdapter
    {
    public:
        MyVideoFetcher(){};
        ~MyVideoFetcher(){};
        bool HasNextImageToFetch() const override
        {
            return true;
        }
        void MyAddImageToBuffer(const CImageData *img, bool bClone = true)
        {
            AddImageToBuffer(img, bClone);
        }
    };

    ...
    CCaptureVisionRouter *cvr = new CCaptureVisionRouter;
    
    MyVideoFetcher *fetcher = new MyVideoFetcher();
    fetcher->SetMaxImageCount(4);
    fetcher->SetBufferOverflowProtectionMode(BOPM_UPDATE);
    fetcher->SetColourChannelUsageType(CCUT_AUTO);
    cvr->SetInput(fetcher);
    ...
    ```

4. Implement the `OnDecodedBarcodesReceived` callback function to receive barcode decoding results:

    ```cpp
    class MyCapturedResultReceiver : public CCapturedResultReceiver
    {
        virtual void OnDecodedBarcodesReceived(CDecodedBarcodesResult *pResult) override
        {
            std::lock_guard<std::mutex> lock(barcodeResultsMutex);
    
            if (pResult->GetErrorCode() != EC_OK)
            {
                std::cout << "Error: " << pResult->GetErrorString() << std::endl;
            }
            else
            {
                auto tag = pResult->GetOriginalImageTag();
                if (tag)
                    std::cout << "ImageID:" << tag->GetImageId() << std::endl;
                int count = pResult->GetItemsCount();
                std::cout << "Decoded " << count << " barcodes" << std::endl;
    
                barcodeResults.clear();
                for (int i = 0; i < count; i++)
                {
                    const CBarcodeResultItem *barcodeResultItem = pResult->GetItem(i);
                    if (barcodeResultItem != NULL)
                    {
                        std::cout << "Result " << i + 1 << std::endl;
                        std::cout << "Barcode Format: " << barcodeResultItem->GetFormatString() << std::endl;
                        std::cout << "Barcode Text: " << barcodeResultItem->GetText() << std::endl;
                        CPoint *points = barcodeResultItem->GetLocation().points;
    
                        BarcodeResult result;
                        result.type = barcodeResultItem->GetFormatString();
                        result.value = barcodeResultItem->GetText();
                        result.frameId = tag->GetImageId();
                        result.x1 = points[0][0];
                        result.y1 = points[0][1];
                        result.x2 = points[1][0];
                        result.y2 = points[1][1];
                        result.x3 = points[2][0];
                        result.y3 = points[2][1];
                        result.x4 = points[3][0];
                        result.y4 = points[3][1];
    
                        barcodeResults.push_back(result);
                    }
                }
            }
    
            std::cout << std::endl;
        }
    };

    ...
    CCapturedResultReceiver *capturedReceiver = new MyCapturedResultReceiver;
    cvr->AddResultReceiver(capturedReceiver);

    errorCode = cvr->StartCapturing(CPresetTemplate::PT_READ_BARCODES, false, errorMsg, 512);
    ...
    ```

5. Create an infinite loop to continuously append video frames to the `fetcher` object, and then draw the available barcode results on the video frames:

    ```cpp
    for (int i = 1;; ++i)
    {
        ImageData frame = get_frame();
        if (frame.data)
        {
            CFileImageTag tag(nullptr, 0, 0);
            tag.SetImageId(i);
            CImageData data(frame.height * frame.stride,
                            frame.data,
                            frame.width,
                            frame.height,
                            frame.stride,
                            IPF_RGB_888,
                            0,
                            &tag);
            fetcher->MyAddImageToBuffer(&data);

            {
                std::lock_guard<std::mutex> lock(barcodeResultsMutex);
                for (const auto &result : barcodeResults)
                {
                    // Draw the bounding box
                    draw_line(&frame, result.x1, result.y1, result.x2, result.y2, 2, 0, 255, 0);
                    draw_line(&frame, result.x2, result.y2, result.x3, result.y3, 2, 0, 255, 0);
                    draw_line(&frame, result.x3, result.y3, result.x4, result.y4, 2, 0, 255, 0);
                    draw_line(&frame, result.x4, result.y4, result.x1, result.y1, 2, 0, 255, 0);

                    // Draw the barcode type and value
                    std::string text = result.type + ": " + result.value;
                    draw_text(&frame, text.c_str(), result.x1, result.y1 - 10, 1, 2, 0, 255, 0);
                }
            }

            display_frame("1D/2D Barcode Scanner", &frame);
            if (wait_key(30) >= 0)
            { // Add a delay and check for key press
                release_frame(&frame);
                break; // Exit the loop if any key is pressed
            }
            release_frame(&frame);
        }
    }
    ```
    
6. Build the project using CMake:

    ```bash
    mkdir build
    cd build
    cmake ..
    cmake --build . --config Release
    ```

    ![desktop camera cpp barcode scanner](https://www.dynamsoft.com/codepool/img/2024/06/desktop-camera-barcode-qr-code-scanner.jpg)

## Source Code
[https://github.com/yushulx/cmake-cpp-barcode-qrcode/tree/main/camera_lib](https://github.com/yushulx/cmake-cpp-barcode-qrcode/tree/main/camera_lib)