65.9K
CodeProject 正在变化。 阅读更多。
Home

C++, Java 和 C# 中的 DotCode 条形码 SDK 编程

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2020年5月19日

CPOL

4分钟阅读

viewsIcon

7612

本文演示了如何使用 C++、Java 和 C# 构建 DotCode 摄像头扫描应用程序。

DotCode 是一种 2D 条形码符号,广泛应用于烟草行业。Dynamsoft 在最新的 条形码 SDK 版本 7.4 中添加了 DotCode 支持。作为全球顶尖的条形码算法公司,Dynamsoft 的条形码 SDK 涵盖了所有主流编程语言,旨在帮助软件开发人员加速各种自定义场景的开发。本文演示了如何使用 C++、Java 和 C# 构建 DotCode 摄像头扫描应用程序。

什么是 DotCode?

在 2019 年 7 月发布的 DotCode Revision 4.0 中,AIM 对 DotCode 的定义如下:

“DotCode 是一种公共领域的光学数据载体,旨在通过高速喷墨或点阵技术可靠读取。通过此标准,可以在生产线速度下以机器可读的形式将到期日期、批号或序列号等实时数据应用于产品。”

Dynamsoft 条形码阅读器如何解码 DotCode

本地化

  1. 输入二进制图像,找到包含圆形或方形轮廓的一组轮廓 contours_A。
  2. 根据轮廓大小的不同,将 contours_A 分为几个子集(contours_A1、contours_A2、... contours_An,它们可能代表不同的 DotCode 符号)。
  3. 对于每个子集,根据空间索引,找到具有最多轮廓的索引块 spatialBlock_Cn。
  4. 从 spatialBlock_Cn 开始,搜索相邻的块以查找轮廓以形成一个区域。
  5. 计算每两个点之间的角度。根据角度分布,如果存在两个峰值且差值为 90 度,则可以确定该符号为 DotCode。

解码

  1. 使用平均模块大小精炼 DotCode 条形码区域。
  2. 获取 DotCode 符号的行和列。
  3. 将 DotCode 符号区域映射到 (0, 1) 矩阵。
  4. 根据标准解码规则解码 DotCode 条形码。

有关更多信息,您可以参考 https://www.dynamsoft.com/Barcode-Types/DotCode.aspx。

下载和安装

DotCode C++

创建一个 CMake 项目。要快速生成初始项目骨架,您可以在 Visual Studio Code 中安装 CMake 扩展。

CMakeLists.txt 中配置依赖库

target_link_libraries (BarcodeReader "DBRx64" "opencv_core347d.lib" "opencv_highgui347d.lib" "opencv_videoio347d.lib" "opencv_imgcodecs347d.lib" "opencv_imgproc347d.lib")

注意:您需要根据您的 OpenCV 版本替换 OpenCV 库。

包含相关头文件

#include <opencv2/core.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
#include <vector>

#include "DynamsoftBarcodeReader.h"
#include "BarcodeReaderConfig.h"

Dynamsoft 提供灵活的 API,可将条形码算法应用于不同场景。在从网络摄像头视频帧解码条形码的情况下,您可以使用一组视频解码 API:StartFrameDecoding()StopFrameDecoding()AppendFrame()

实例化条形码阅读器并配置参数

// Get license from https://www.dynamsoft.com/CustomerPortal/Portal/Triallicense.aspx
CBarcodeReader reader = reader.InitLicense("LICENSE-LEY");
PublicRuntimeSettings runtimeSettings;
char szErrorMsg[256];
reader.InitRuntimeSettingsWithString("{\"ImageParameter\":{\"Name\":\"BestCoverage\",\"DeblurLevel\":9,\"ExpectedBarcodesCount\":512,\"ScaleDownThreshold\":100000,\"LocalizationModes\":[{\"Mode\":\"LM_CONNECTED_BLOCKS\"},{\"Mode\":\"LM_SCAN_DIRECTLY\"},{\"Mode\":\"LM_STATISTICS\"},{\"Mode\":\"LM_LINES\"},{\"Mode\":\"LM_STATISTICS_MARKS\"}],\"GrayscaleTransformationModes\":[{\"Mode\":\"GTM_ORIGINAL\"},{\"Mode\":\"GTM_INVERTED\"}]}}", CM_OVERWRITE, szErrorMsg, 256);
reader.GetRuntimeSettings(&runtimeSettings);
runtimeSettings.barcodeFormatIds = BF_ALL;
runtimeSettings.barcodeFormatIds_2 = BF2_POSTALCODE | BF2_DOTCODE;
runtimeSettings.intermediateResultTypes = IRT_ORIGINAL_IMAGE;
reader.UpdateRuntimeSettings(&runtimeSettings,szErrorMsg,256);
reader.SetTextResultCallback(textResultCallback,NULL);
reader.SetIntermediateResultCallback(intermediateResultCallback, NULL);
reader.SetErrorCallback(errorcb, NULL);

调用 StartFrameDecoding() 启动视频解码线程

reader.StartFrameDecoding(10, 10, width, height, frame.step.p[0], IPF_RGB_888, "");

在视频捕获循环中,将帧连续添加到条形码阅读器的内置帧队列中

for (;;)
{
    int key = waitKey(10);
    if ((key & 0xff) == 27/*ESC*/) break;
    capture >> frame; // read the next frame from camera
    if (frame.empty())
    {
        cerr << "ERROR: Can't grab camera frame." << endl;
        break;
    }   
    reader.AppendFrame(frame.data);
 
    imshow("Dynamsoft Barcode Reader", frame);
     
}

textResultCallback() 函数中获取解码结果

void textResultCallback(int frameId, TextResultArray *pResults, void * pUser)
{
    char * pszTemp = NULL;
    char * pszTemp1 = NULL;
    char * pszTemp2 = NULL;
    pszTemp = (char*)malloc(4096);
    for (int iIndex = 0; iIndex < pResults->resultsCount; iIndex++)
    {
        snprintf(pszTemp, 4096, "Barcode %d:\r\n", iIndex + 1);
        printf(pszTemp);
        snprintf(pszTemp, 4096, "    Type: %s\r\n", pResults->results[iIndex]->barcodeFormatString_2);
        printf(pszTemp);
        snprintf(pszTemp, 4096, "    Value: %s\r\n", pResults->results[iIndex]->barcodeText);
        printf(pszTemp);
 
        pszTemp1 = (char*)malloc(pResults->results[iIndex]->barcodeBytesLength * 3 + 1);
        pszTemp2 = (char*)malloc(pResults->results[iIndex]->barcodeBytesLength*3 + 100);
        ToHexString(pResults->results[iIndex]->barcodeBytes, pResults->results[iIndex]->barcodeBytesLength, pszTemp1);
        snprintf(pszTemp2, pResults->results[iIndex]->barcodeBytesLength*3 + 100, "    Hex Data: %s\r\n", pszTemp1);
        printf(pszTemp2);
        free(pszTemp1);
        free(pszTemp2);
    }
    free(pszTemp);
}

此外,您可以在 intermediateResultCallback() 函数中获取相应的帧,然后绘制条形码位置。

void intermediateResultCallback(int frameId, IntermediateResultArray *pResults, void * pUser)
{
    if (id == frameId)
    {
        if (results->resultsCount > 0) 
        {
            int thickness = 2;
            Scalar color(0, 255, 0);

            ImageData* tempImageData = (ImageData*)(pResults->results[0]->results[0]);
            resultImage = Mat(tempImageData->height, tempImageData->width, CV_8UC3, tempImageData->bytes);  
            for (int i = 0; i < results->resultsCount; ++i)
            {
                TextResult *barcode = results->results[i];
                int x1 = barcode->localizationResult->x1;
                int y1 = barcode->localizationResult->y1;
                int x2 = barcode->localizationResult->x2;
                int y2 = barcode->localizationResult->y2;
                int x3 = barcode->localizationResult->x3;
                int y3 = barcode->localizationResult->y3;
                int x4 = barcode->localizationResult->x4;
                int y4 = barcode->localizationResult->y4;
                line( resultImage, Point(x1, y1), Point(x2, y2), color, thickness);
                line( resultImage, Point(x2, y2), Point(x3, y3), color, thickness);
                line( resultImage, Point(x3, y3), Point(x4, y4), color, thickness);
                line( resultImage, Point(x4, y4), Point(x1, y1), color, thickness);
            }
            CBarcodeReader::FreeTextResults(&results);
            isResultReady = true;
        }
    }
}

注意:您必须在 OpenCV 线程中渲染最终图像。

在终止应用程序之前,调用 StopFrameDecoding()

reader.StopFrameDecoding();

通过命令行工具构建并运行程序

mkdir build
cd build
cmake -G"Visual Studio 15 2017 Win64" ..
cmake --build .
.\debug\BarcodeReader.exe

DotCode Java

opencv-<version>\opencv\build\java 找到 OpenCV Java 库(例如 opencv-430.jar 和 opencv_java430.dll)。如果您倾向于使用 Maven,则需要提前将 jar 文件安装到 Maven 本地存储库。

mvn install:install-file -Dfile=opencv-430.jar -DgroupId=org -DartifactId=opencv -Dversion=4.3.0 -Dpackaging=jar

之后,将 OpenCV 依赖项添加到 pom.xml 文件中

<dependency>
  <groupId>org</groupId>
  <artifactId>opencv</artifactId>
  <version>4.3.0</version>
</dependency>

如果 DLL 文件位于 Java 库路径下,则可以自动加载。因此,将 DLL 文件放在项目根目录下。

使用 Java Swing 组合 GUI。与 C++ 不同,Java 中没有 imshow() 方法。因此,我们需要依次捕获帧并在 JLabel 中渲染它们。

public void updateViewer(final BufferedImage image) {
        if (!SwingUtilities.isEventDispatchThread()) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    mImage.setIcon(new ImageIcon(image));
                }
            });
            return;
        }
    }
 
Runnable frameGrabber = new Runnable() {
 
                    @Override
                    public void run() {
                        Mat frame = grabFrame();
                        byte[] data = Utils.matToByteArray(frame);
 
                        if (!status.get()) {
                            status.set(true);
                            barcodeTimer.schedule(new BarcodeRunnable(frame, mBarcodeReader, callback, status), 0, TimeUnit.MILLISECONDS);
                        }
                     
                        BufferedImage bufferedImage = Utils.byteToBufferedImage(data, frame.width(), frame.height(), frame.channels());
                        if (isRunning) updateViewer(bufferedImage);
                    }
                };
this.timer = Executors.newSingleThreadScheduledExecutor();
this.timer.scheduleAtFixedRate(frameGrabber, 0, 33, TimeUnit.MILLISECONDS);

为避免阻塞 UI,请为解码 DotCode 创建新线程。

barcodeTimer = Executors.newSingleThreadScheduledExecutor();
public class BarcodeRunnable implements Runnable {
    private Mat frame;
    private BarcodeReader reader;
    private BarcodeCallback callback;
    private AtomicBoolean status;

    public BarcodeRunnable(Mat frame, BarcodeReader reader, BarcodeCallback callback, AtomicBoolean status) {
        this.frame = frame;
        this.reader = reader;
        this.callback = callback;
        this.status = status;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            TextResult[] results = reader.decodeBuffer(Utils.matToByteArray(frame), frame.width(), frame.height(), (int)frame.step1(), EnumImagePixelFormat.IPF_BGR_888, "");
            if (results != null && results.length > 0) {
                if (callback != null) {
                    callback.onResult(results, Utils.matToBufferedImage(frame));
                }
            }
            else {
                status.set(false);
            }
            
        } catch (BarcodeReaderException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        }
    }

}

创建一个扩展 JLable 的自定义类用于绘图

private ArrayList<Point[]> data = new ArrayList<>();
 
@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g.create();
    if (data.size() > 0) {
        g2d.setColor(Color.RED);
        for (Point[] points : data) {
            for (int i = 0; i < points.length; ++i) {
                if (i == 3) {
                    g2d.drawLine(points[i].x, points[i].y, points[0].x, points[0].y);
                } else {
                    g2d.drawLine(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y);
                }
            }
        }
 
    }
    g2d.dispose();
}
 
public void appendPoints(Point[] points) {
    data.add(points);
}
 
public void clearPoints() {
    data.clear();
}

构建并运行 Java DotCode 阅读器

mvn clean install assembly:assembly -Dmaven.test.skip=true
java -cp target/opencv-dotcode-1.0-SNAPSHOT-jar-with-dependencies.jar com.java.barcode.App

DotCode C#

OpenCV 只实现了基本的摄像头 API。要在 Windows 上获取更多摄像头参数,我们必须使用 DirectShow。

通过调用 DirectShow API 枚举摄像头信息

private List<Resolution> GetAllAvailableResolution(DsDevice vidDev)
{
    try
    {
        int hr, bitCount = 0;
 
        IBaseFilter sourceFilter = null;
 
        var m_FilterGraph2 = new FilterGraph() as IFilterGraph2;
        hr = m_FilterGraph2.AddSourceFilterForMoniker(vidDev.Mon, null, vidDev.Name, out sourceFilter);
        var pRaw2 = DsFindPin.ByCategory(sourceFilter, PinCategory.Capture, 0);
        var AvailableResolutions = new List<Resolution>();
 
        VideoInfoHeader v = new VideoInfoHeader();
        IEnumMediaTypes mediaTypeEnum;
        hr = pRaw2.EnumMediaTypes(out mediaTypeEnum);
 
        AMMediaType[] mediaTypes = new AMMediaType[1];
        IntPtr fetched = IntPtr.Zero;
        hr = mediaTypeEnum.Next(1, mediaTypes, fetched);
 
        while (fetched != null && mediaTypes[0] != null)
        {
            Marshal.PtrToStructure(mediaTypes[0].formatPtr, v);
            if (v.BmiHeader.Size != 0 && v.BmiHeader.BitCount != 0)
            {
                if (v.BmiHeader.BitCount > bitCount)
                {
                    AvailableResolutions.Clear();
                    bitCount = v.BmiHeader.BitCount;
                }
                AvailableResolutions.Add(new Resolution(v.BmiHeader.Width, v.BmiHeader.Height));
            }
            hr = mediaTypeEnum.Next(1, mediaTypes, fetched);
        }
        return AvailableResolutions;
    }
    catch (Exception ex)
    {
        //MessageBox.Show(ex.Message);
        Console.WriteLine(ex.ToString());
        return new List<Resolution>();
    }
}


DsDevice[] devices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);
if (devices != null)
{
    cameras = new List<CameraInfo>();
    foreach (DsDevice device in devices)
    {
        List<Resolution> resolutions = GetAllAvailableResolution(device);
        cameras.Add(new CameraInfo(device, resolutions));
    }
}

创建回调函数以接收网络摄像头帧

private void ConfigureSampleGrabber(ISampleGrabber sampleGrabber)
        {
            AMMediaType media;
            int hr;

            // Set the media type to Video/RBG24
            media = new AMMediaType();
            media.majorType = MediaType.Video;
            media.subType = MediaSubType.RGB24;
            media.formatType = FormatType.VideoInfo;

            hr = sampleGrabber.SetMediaType(media);
            DsError.ThrowExceptionForHR(hr);

            DsUtils.FreeAMMediaType(media);
            media = null;

            hr = sampleGrabber.SetCallback(this, 1);
            DsError.ThrowExceptionForHR(hr);
        }

        public int BufferCB(double SampleTime, IntPtr pBuffer, int BufferLen)
        {
            Bitmap bitmap = new Bitmap(_previewWidth, _previewHeight, _previewStride,
                PixelFormat.Format24bppRgb, pBuffer);
            bitmap.RotateFlip(RotateFlipType.Rotate180FlipX);
            if (callback != null)
            {
                callback(bitmap);
            }
            
            return 0;
        }

从位图中解码 DotCode

TaskCompletedCallBack callback = FrameCallback;
private volatile bool isFinished = true;
public void FrameCallback(Bitmap bitmap)
{
    if (isFinished)
    {
        this.BeginInvoke((MethodInvoker)delegate
        {
            isFinished = false;
            ReadFromFrame(bitmap);
            isFinished = true;
        });
}
 
private void ReadFromFrame(Bitmap bitmap)
{
    UpdateRuntimeSettingsWithUISetting();
    TextResult[] textResults = null;
    int timeElapsed = 0;
 
    try
    {
        DateTime beforeRead = DateTime.Now;
 
        textResults = mBarcodeReader.DecodeBitmap(bitmap, "");
 
        DateTime afterRead = DateTime.Now;
        timeElapsed = (int)(afterRead - beforeRead).TotalMilliseconds;
 
        if (textResults == null || textResults.Length <= 0)
        {
            return;
        }
 
        if (textResults != null)
        {
            mDSManager.StopCamera();
            Bitmap tempBitmap = ((Bitmap)(bitmap)).Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat);
            this.BeginInvoke(mPostShowFrameResults, tempBitmap, textResults, timeElapsed, null);
        }
 
    }
    catch (Exception ex)
    {
        this.Invoke(mPostShowFrameResults, new object[] { bitmap, textResults, timeElapsed, ex });
    }
}

按 F5 运行 .NET DotCode 阅读器

问答

我可以使用示例代码扫描其他条形码类型吗?

是的。您可以配置一维和二维条形码类型。

我可以构建 Android 和 iOS 应用来扫描 DotCode 吗?

是的。您可以下载移动条形码 SDK。

如何将字节转换为 Mat 类型?

Mat resultImage = Mat(tempImageData->height, tempImageData->width, CV_8UC3, tempImageData->bytes);

如何将 OpenCV Mat 转换为 Java 字节数组?

public static byte[] matToByteArray(Mat original)
{
    int width = original.width(), height = original.height(), channels = original.channels();
    byte[] sourcePixels = new byte[width * height * channels];
    original.get(0, 0, sourcePixels);
    return sourcePixels;
}

如何将 Java 字节数组转换为 Java BufferedImage?

public static BufferedImage byteToBufferedImage(byte[] sourcePixels, int width, int height, int channels)
{
    BufferedImage image = null;
     
    if (channels > 1)
    {
        image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
    }
    else
    {
        image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
    }
    final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
    System.arraycopy(sourcePixels, 0, targetPixels, 0, sourcePixels.length);
     
    return image;
}

源代码

https://github.com/Dynamsoft/webcam-barcode-reader

技术支持

如果您对 Dynamsoft 条形码阅读器 SDK 有任何疑问,请随时联系 support@dynamsoft.com

© . All rights reserved.