C++, Java 和 C# 中的 DotCode 条形码 SDK 编程
本文演示了如何使用 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
本地化
- 输入二进制图像,找到包含圆形或方形轮廓的一组轮廓 contours_A。
- 根据轮廓大小的不同,将 contours_A 分为几个子集(contours_A1、contours_A2、... contours_An,它们可能代表不同的 DotCode 符号)。
- 对于每个子集,根据空间索引,找到具有最多轮廓的索引块 spatialBlock_Cn。
- 从 spatialBlock_Cn 开始,搜索相邻的块以查找轮廓以形成一个区域。
- 计算每两个点之间的角度。根据角度分布,如果存在两个峰值且差值为 90 度,则可以确定该符号为 DotCode。
解码
- 使用平均模块大小精炼 DotCode 条形码区域。
- 获取 DotCode 符号的行和列。
- 将 DotCode 符号区域映射到 (0, 1) 矩阵。
- 根据标准解码规则解码 DotCode 条形码。
有关更多信息,您可以参考 https://www.dynamsoft.com/Barcode-Types/DotCode.aspx。
下载和安装
- Dynamsoft 条形码阅读器 v7.4。安装程序包含一个免费的 30 天试用许可证。或者,您可以从在线门户申请有效许可证。
- OpenCV
- DirectShow.NET
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。