Click here to Skip to main content
14,550,787 members

DotCode Barcode SDK Programming in C++, Java, and C#

19 May 2020CPOL
This article demonstrates how to build DotCode webcam scan apps using C++, Java, and C#.
This article covers things like, how Dynamsoft Barcode Reader decodes a DotCode, how to set of video decoding APIs like StartFrameDecoding(), StopFrameDecoding(), and AppendFrame(), and using DirectShow to get more webcam parameters on Windows.

This article is in the Product Showcase section for our sponsors at CodeProject. These articles are intended to provide you with information on products and services that we consider useful and of value to developers.

DotCode is a kind of 2D barcode symbology used broadly in the tobacco industry. Dynamsoft added DotCode support in the latest barcode SDK version 7.4. As the top barcode algorithm company in the world, Dynamsoft's barcode SDK covers all mainstream programming languages, aiming to help software developers expedite their development for various custom scenarios. This article demonstrates how to build DotCode webcam scan apps using C++, Java, and C#.

What is DotCode?

In the DotCode Revision 4.0 released in July 2019, AIM defines DotCode as follows:

"DotCode is a public domain optical data carrier designed to be reliably readable when printed by high-speed inkjet or laser dot technologies. With this standard, real-time data like expiration date, lot number, or serial number can be applied to products in a machine-readable form at production line speeds."

How Dynamsoft Barcode Reader Decodes a DotCode

Localization

  1. Input a binary image and find a set contours_A containing circular-shaped or square-shaped contours.
  2. Split contours_A into several subsets (contours_A1, contours_A2, … contours_An, which may represent different DotCode symbols) in accordance with different contour sizes.
  3. For every subset, based on the spatial index, find the indexed block spatialBlock_Cn with the most contours.
  4. Starting from spatialBlock_Cn, search adjacent blocks for contours to form an area.
  5. Calculate the angle between every two dots. According to the angular distribution, if there are two peaks and the difference is 90 degrees, we can conclude that the symbol is DotCode.

Decoding

  1. Use the average module size to refine the DotCode barcode region.
  2. Get the rows and columns of the DotCode symbol.
  3. Map the DotCode symbol region to a (0, 1) matrix.
  4. Decode the DotCode barcode according to the standard decoding rules.

For more information, you can refer to https://www.dynamsoft.com/Barcode-Types/DotCode.aspx.

Download and Installation

DotCode C++

Create a CMake project. To generate an initial project skeleton quickly, you can install the CMake extension in Visual Studio Code.

Image 1

Configure dependent libraries in CMakeLists.txt:

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

Note: You need to substitute the OpenCV libs according to your OpenCV version.

Include the relevant header files:

#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 provides flexible APIs for adapting barcode algorithms to different scenarios. In the case of decoding barcodes from webcam video frames, you can use the set of video decoding APIs: StartFrameDecoding(), StopFrameDecoding() and AppendFrame(),

Instantiate barcode reader and configure parameters:

// 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);

Call StartFrameDecoding() to start a video decoding thread:

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

Continuously append frames to the built-in frame queue of the barcode reader in the video capture loop:

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);
     
}

Get the decoding results in the textResultCallback() function:

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);
}

In addition, you can get the corresponding frame in the intermediateResultCallback() function, and then draw the barcode positions:

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;
        }
    }
}

Note: you must render the final image in OpenCV thread.

Before terminating the app, call StopFrameDecoding():

reader.StopFrameDecoding();

Build and run the program via a command-line tool:

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

Image 2

DotCode Java

Find the OpenCV Java library (E.g. opencv-430.jar and opencv_java430.dll) at opencv-<version>\opencv\build\java. If you prefer using Maven, you need to install the jar file to the Maven local repository beforehand:

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

After that, add the OpenCV dependency to pom.xml file:

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

The DLL file can be loaded automatically if it is under Java library paths. So put the DLL file under the project root directory.

Compose the GUI with Java Swing. Not like C++, there is no imshow() method in Java. Therefore, we need to successively grab frames and render them in 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);

To avoid blocking UI, create a new thread for decoding 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();
        }
    }

}

Create a custom class that extends JLable for painting:

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();
}

Build and run the Java DotCode reader:

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

Image 3

DotCode C#

OpenCV only implements basic camera APIs. To get more webcam parameters on Windows, we have to use DirectShow.

Enumerate camera information by calling DirectShow APIs:

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));
    }
}

Create a callback function to receive webcam frames:

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;
        }

Decode DotCode from bitmap:

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 });
    }
}

Press F5 to run the .NET DotCode reader:

Image 4

Q&A

Can I use the sample code to scan other barcode types?

Yes. You can configure 1D and 2D barcode types.

Can I build Android and iOS apps to scan DotCode?

Yes. You can download mobile barcode SDKs.

How to convert bytes to Mat type?

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

How to convert OpenCV Mat to Java byte array?

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;
}

How to convert Java byte array to 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;
}

Source Code

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

Technical Support

If you have any questions about the Dynamsoft Barcode Reader SDK, please feel free to contact support@dynamsoft.com.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Xiao Ling
Technical Writer Dynamsoft
Canada Canada
Xiao Ling is A technical content writer at Dynamsoft, the leading company of document capture and image processing SDKs.
He is in charge of managing Dynamsoft blog site codepool.biz and Dynamsoft GitHub community.

Comments and Discussions

 
-- There are no messages in this forum --
Article
Posted 19 May 2020

Stats

3.1K views
7 bookmarked