掌握使用 C++ 扫描包裹:条形码和 OCR 文本提取
在本文中,我们将继续介绍包裹扫描技术系列,深入研究 C++ 世界。在之前的 JavaScript 教程的基础上,我们将探索如何使用 C++ 实现条形码扫描和 OCR 文本提取。本指南将介绍如何设置开发环境、集成必要的库,并提供分步教程,以使用 C++ 为Windows和Linux平台创建强大的包裹扫描应用程序。
本文是本系列文章的第二部分。
- 第 1 部分 - Web 包裹管理:扫描一维条形码并识别任意方向的周围文本
- 第 2 部分 - 掌握使用 C++ 进行包裹扫描:条形码和 OCR 文本提取
先决条件
-
获取Dynamsoft Capture Vision的30 天试用许可证密钥。
Dynamsoft条形码阅读器和Dynamsoft 标签识别器是 Dynamsoft Capture Vision Framework 的组成部分。条形码阅读器专为条形码扫描而设计,而标签识别器则专注于 OCR 文本识别。这些工具共享几个通用组件和头文件。要有效地同时使用这两个工具,只需将两个包合并到一个目录中即可。以下是您的设置的最终目录结构。
<span style="color:#212529"><span style="background-color:#eaeaea"><code>|- SDK
|- include
|- DynamsoftBarcodeReader.h
|- DynamsoftCaptureVisionRouter.h
|- DynamsoftCodeParser.h
|- DynamsoftCore.h
|- DynamsoftDocumentNormalizer.h
|- DynamsoftImageProcessing.h
|- DynamsoftLabelRecognizer.h
|- DynamsoftLicense.h
|- DynamsoftUtility.h
|- platforms
|- linux
|- libDynamicImage.so
|- libDynamicPdf.so
|- libDynamicPdfCore.so
|- libDynamsoftBarcodeReader.so
|- libDynamsoftCaptureVisionRouter.so
|- libDynamsoftCore.so
|- libDynamsoftImageProcessing.so
|- libDynamsoftLabelRecognizer.so
|- libDynamsoftLicense.so
|- libDynamsoftNeuralNetwork.so
|- libDynamsoftUtility.so
|- win
|- bin
|- DynamicImagex64.dll
|- DynamicPdfCorex64.dll
|- DynamicPdfx64.dll
|- DynamsoftBarcodeReaderx64.dll
|- DynamsoftCaptureVisionRouterx64.dll
|- DynamsoftCorex64.dll
|- DynamsoftImageProcessingx64.dll
|- DynamsoftLabelRecognizerx64.dll
|- DynamsoftLicensex64.dll
|- DynamsoftNeuralNetworkx64.dll
|- DynamsoftUtilityx64.dll
|- vcomp140.dll
|- lib
|- DynamsoftBarcodeReaderx64.lib
|- DynamsoftCaptureVisionRouterx64.lib
|- DynamsoftCorex64.lib
|- DynamsoftImageProcessingx64.lib
|- DynamsoftLabelRecognizerx64.lib
|- DynamsoftLicensex64.lib
|- DynamsoftNeuralNetworkx64.lib
|- DynamsoftUtilityx64.lib
|- CharacterModel
|- DBR-PresetTemplates.json
|- DLR-PresetTemplates.json
</code></span></span>
配置 CMakeLists.txt 以构建 C++ 应用程序
目标项目依赖头文件、共享库、JSON格式的模板以及神经网络模型。为了简化构建过程,我们可以使用CMake来管理依赖项并生成不同平台的项目文件。
-
指定头文件和共享库的目录:
if(WINDOWS) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") link_directories("${PROJECT_SOURCE_DIR}/../sdk/platforms/win/bin/") else() link_directories("${PROJECT_SOURCE_DIR}/../sdk/platforms/win/lib/") endif() elseif(LINUX) if (CMAKE_SYSTEM_PROCESSOR STREQUAL x86_64) MESSAGE( STATUS "Link directory: ${PROJECT_SOURCE_DIR}/../sdk/platforms/linux/" ) link_directories("${PROJECT_SOURCE_DIR}/../sdk/platforms/linux/") endif() endif() include_directories("${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/../sdk/include/")
-
链接所需的库:
add_executable(${PROJECT_NAME} main.cxx) if(WINDOWS) if(CMAKE_CL_64) target_link_libraries (${PROJECT_NAME} "DynamsoftCorex64" "DynamsoftLicensex64" "DynamsoftCaptureVisionRouterx64" "DynamsoftUtilityx64" ${OpenCV_LIBS}) endif() else() target_link_libraries (${PROJECT_NAME} "DynamsoftCore" "DynamsoftLicense" "DynamsoftCaptureVisionRouter" "DynamsoftUtility" pthread ${OpenCV_LIBS}) endif()
-
将所需的共享库、JSON 文件和神经网络模型复制到输出目录:
if(WINDOWS) add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${PROJECT_SOURCE_DIR}/../sdk/platforms/win/bin/" $<TARGET_FILE_DIR:main>) endif() add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/../sdk/DBR-PresetTemplates.json" $<TARGET_FILE_DIR:main>/DBR-PresetTemplates.json) add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/../sdk/DLR-PresetTemplates.json" $<TARGET_FILE_DIR:main>/DLR-PresetTemplates.json)
使用 C++ 实现条形码扫描和 OCR 文本提取
为了快速开始使用 API,我们可以参考 Dynamsoft 提供的条形码示例代码VideoDecoding.cpp。示例代码演示了如何使用 OpenCV 从视频帧解码条形码。基于此示例代码,我们可以使用 Dynamsoft Label Recognizer 添加 OCR 文本提取功能。让我们逐步了解如何在 C++ 中实现条形码扫描和 OCR 文本提取。
步骤 1:包含 SDK 头文件
要将Dynamsoft Barcode Reader和Dynamsoft Label Recognizer一起使用,我们需要包含两个头文件:DynamsoftCaptureVisionRouter.h
和DynamsoftUtility.h
,它们包含所有必要的类和函数。OpenCV 头文件可能因您使用的版本而异。以下代码片段显示了示例代码中使用的头文件和命名空间:
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/videoio.hpp"
#include "opencv2/core/utility.hpp"
#include "opencv2/imgcodecs.hpp"
#include <iostream>
#include <vector>
#include <chrono>
#include <iostream>
#include <string>
#include "DynamsoftCaptureVisionRouter.h"
#include "DynamsoftUtility.h"
using namespace std;
using namespace dynamsoft::license;
using namespace dynamsoft::cvr;
using namespace dynamsoft::dlr;
using namespace dynamsoft::dbr;
using namespace dynamsoft::utility;
using namespace dynamsoft::basic_structures;
using namespace cv;
第 2 步:设置许可证密钥
申请试用许可证密钥时,您可以选择单个产品密钥或多个产品的组合密钥。在此示例中,我们需要设置一个组合许可证密钥来激活两个 SDK 模块:
int iRet = CLicenseManager::InitLicense("LICENSE-KEY");
步骤 3:创建用于缓冲视频帧的类
类CImageSourceAdapter
用于获取和缓冲图像帧。我们可以创建一个继承自的自定义类CImageSourceAdapter
。
class MyVideoFetcher : public CImageSourceAdapter
{
public:
MyVideoFetcher(){};
~MyVideoFetcher(){};
bool HasNextImageToFetch() const override
{
return true;
}
void MyAddImageToBuffer(const CImageData *img, bool bClone = true)
{
AddImageToBuffer(img, bClone);
}
};
MyVideoFetcher *fetcher = new MyVideoFetcher();
步骤4:图像处理和结果处理
该类CCaptureVisionRouter
是图像处理的核心类。它提供了一个内置线程池,用于异步处理对象获取的图像CImageSourceAdapter
。
CCaptureVisionRouter *cvr = new CCaptureVisionRouter;
cvr->SetInput(fetcher);
为了处理条形码和 OCR 文本结果,我们需要注册一个从对象继承的自定义CCapturedResultReceiver
类CCaptureVisionRouter
。
class MyCapturedResultReceiver : public CCapturedResultReceiver
{
virtual void OnRecognizedTextLinesReceived(CRecognizedTextLinesResult *pResult) override
{
}
virtual void OnDecodedBarcodesReceived(CDecodedBarcodesResult *pResult) override
{
}
};
CCapturedResultReceiver *capturedReceiver = new MyCapturedResultReceiver;
cvr->AddResultReceiver(capturedReceiver);
步骤 5:流式传输视频帧并馈送到图像处理器
OpenCV 提供了一种从摄像头捕获视频帧的简单方法。我们可以使用该类VideoCapture
打开摄像头并连续获取帧。同时,我们调用对象的StartCapturing()
和StopCapturing()
方法CCaptureVisionRouter
来打开和关闭处理任务。
string settings = R"(
{
"CaptureVisionTemplates": [
{
"Name": "ReadBarcode&AccompanyText",
"ImageROIProcessingNameArray": [
"roi-read-barcodes-only", "roi-read-text"
]
}
],
"TargetROIDefOptions": [
{
"Name": "roi-read-barcodes-only",
"TaskSettingNameArray": ["task-read-barcodes"]
},
{
图像处理任务支持预设模板和自定义设置。如果我们只需要扫描一维和二维条码,可以使用模板CPresetTemplate::PT_READ_BARCODES
。如果我们想在提取条码的同时提取 OCR 文本标签,则需要高度自定义的模板。
步骤6:处理结果并显示
由于结果是从工作线程返回的,为了将它们显示在主线程中的视频帧上,我们创建一个向量来存储它们并使用互斥锁来保护共享资源。
struct BarcodeResult
{
std::string type;
std::string value;
std::vector<cv::Point> localizationPoints;
int frameId;
string line;
std::vector<cv::Point> textLinePoints;
};
std::vector<BarcodeResult> barcodeResults;
std::mutex barcodeResultsMutex;
OnRecognizedTextLinesReceived
以下是和回调函数的实现OnDecodedBarcodesReceived
:
virtual void OnRecognizedTextLinesReceived(CRecognizedTextLinesResult *pResult) override
{
std::lock_guard<std::mutex> lock(barcodeResultsMutex);
barcodeResults.clear();
const CFileImageTag *tag = dynamic_cast<const CFileImageTag *>(pResult->GetOriginalImageTag());
if (pResult->GetErrorCode() != EC_OK)
{
cout << "Error: " << pResult->GetErrorString() << endl;
}
else
{
int lCount = pResult->GetItemsCount();
for (int li = 0; li < lCount; ++li)
{
以下代码片段展示了如何在视频帧上绘制条形码和OCR文本结果:
{
std::lock_guard<std::mutex> lock(barcodeResultsMutex);
for (const auto &result : barcodeResults)
{
// Draw the bounding box
if (result.localizationPoints.size() == 4)
{
for (size_t i = 0; i < result.localizationPoints.size(); ++i)
{
cv::line(frame, result.localizationPoints[i],
result.localizationPoints[(i + 1) % result.localizationPoints.size()],
cv::Scalar(0, 255, 0), 2);
}
}
// Draw the barcode type and value
构建并运行应用程序
视窗
mkdir build
cd build
cmake -DCMAKE_GENERATOR_PLATFORM=x64 ..
cmake --build . --config Release
Release\main.exe
Linux
mkdir build
cd build
cmake ..
cmake --build . --config Release
./main