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.
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
- Input a binary image and find a set contours_A containing circular-shaped or square-shaped contours.
- Split contours_A into several subsets (contours_A1, contours_A2, … contours_An, which may represent different DotCode symbols) in accordance with different contour sizes.
- For every subset, based on the spatial index, find the indexed block spatialBlock_Cn with the most contours.
- Starting from spatialBlock_Cn, search adjacent blocks for contours to form an area.
- 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
- Use the average module size to refine the DotCode barcode region.
- Get the rows and columns of the DotCode symbol.
- Map the DotCode symbol region to a (0, 1) matrix.
- 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.
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
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
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:
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.