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: You will receive a 30-day free trial license by email.
- Dynamsoft C++ Barcode SDK v10.x: 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:
-
Obtain the OpenCV source code from the official OpenCV repository on GitHub:
git clone https://github.com/opencv/opencv.git cd opencv
-
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.
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 ..
-
Once the configuration is complete, build OpenCV with the specified options.
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.
-
Set up the project directory structure as follows:
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
, andinclude
. Thesrc
directory houses the source files for the camera library, which leverages the tailored OpenCV libraries. Theexample
directory contains the main application file, showcasing how to use the camera library to implement barcode scanning functionality. Thelib
directory holds the OpenCV libraries for both Windows and Linux platforms. Finally, theinclude
directory contains the header files necessary for the camera library and OpenCV. -
Create the
CMakeLists.txt
file in the project root directory:
cmake_minimum_required(VERSION 3.10) project(camera_lib) add_subdirectory(src) add_subdirectory(example)
-
Create the
CMakeLists.txt
file in thesrc
directory:
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()
-
Create the
CMakeLists.txt
file in theexample
directory:
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
-
In the
src
directory, create thecamera_lib.h
header file:
#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. -
Implement the camera library functions in the
camera_lib.cpp
file within thesrc
directory:
#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:
-
Include the necessary headers and define a
BarcodeResult
struct to store barcode decoding results:
#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; };
-
Set the license key for the Dynamsoft Barcode SDK in the
main
function:
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; }
-
Instantiate
CCaptureVisionRouter
andCImageSourceAdapter
objects for buffering and processing video frames from the camera:
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); ...
-
Implement the
OnDecodedBarcodesReceived
callback function to receive barcode decoding results:
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); ...
-
Create an infinite loop to continuously append video frames to the
fetcher
object, and then draw the available barcode results on the video frames:
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); } }
-
Build the project using CMake:
mkdir build cd build cmake .. cmake --build . --config Release
Source Code
https://github.com/yushulx/cmake-cpp-barcode-qrcode/tree/main/camera_lib