相机流的实时条形码扫描
在本帖中,我们将讨论如何利用 Dynamsoft Barcode Reader 的视频解码 API 来实现摄像头预览场景下的条形码扫描功能。我们还将分别通过代码片段演示如何在桌面和移动平台上实现。
条形码检测和识别技术,尤其是实时条形码扫描的应用,对于仓储管理和移动支付等行业至关重要。Dynamsoft Barcode Reader 作为一个跨平台、跨编程语言的 SDK,对于致力于在短时间内构建企业级条形码解决方案的开发者来说,是救星。
在本帖中,我们将讨论如何利用 Dynamsoft Barcode Reader 的视频解码 API 来实现摄像头预览场景下的条形码扫描功能。我们还将分别通过代码片段演示如何在桌面和移动平台上实现。
摄像头实时条形码扫描的挑战
读取条形码的过程包含各种计算机视觉算法,其中一些是 CPU 密集型的。为了使摄像头预览流畅运行,您必须在工作线程中异步执行密集的计算。这种情况下需要一个多线程模型,开发者必须在线程调度、共享内存、状态同步和锁上花费更多时间。在如此复杂的情况下,开发者很容易出错。
为了消除多线程编程的复杂性并提高开发效率,Dynamsoft Barcode Reader 7.0 引入了一套基于线程的 API,用于解码连续帧。
Dynamsoft Barcode Reader 概述
Dynamsoft 的 Barcode Reader SDK 使您能够仅用几行代码就能高效地将条形码读取功能嵌入到您的 Web、桌面或移动应用程序中,从而节省您的开发时间和成本。使用该 SDK,您可以创建高速可靠的条形码扫描仪软件,以满足您的业务需求。
许可证
获取 免费 30 天试用许可证。
支持的条码符号体系
线性条码 (1D):Code 39、Code 93、Code 128、Codabar、Interleaved 2 of 5、EAN-8、EAN-13、UPC-A、UPC-E、Industrial 2 of 5。
2D 条码:QR Code、DataMatrix、PDF417 和 Aztec Code。
开发者指南
https://www.dynamsoft.com/help/Barcode-Reader/devguide/index.html
API 文档
https://www.dynamsoft.com/help/Barcode-Reader/index.html
代码库
https://www.dynamsoft.com/Downloads/Dynamic-Barcode-Reader-Sample-Download.aspx
在线演示
https://demo.dynamsoft.com/DBR/BarcodeReaderDemo.aspx
条形码视频解码 API 如何工作
视频解码模型包含用户主线程、解码线程和结果消费者线程。
用户主线程
当调用 StartFrameDecoding()
时,将启动用户主线程。在视频帧回调函数中调用 AppendFrame()
将帧添加到自动管理的帧队列中。
解码线程
解码线程承担所有 CPU 密集型工作,运行条形码算法。它使用一些策略来选择和丢弃帧,以避免卡顿。
结果消费者线程
此线程管理注册的回调函数以获取结果。
桌面条形码扫描
要快速构建桌面条形码应用程序,我们建议使用 Python 和 OpenCV。
突破 Python 的 GIL
Python 多线程代码的性能,特别是计算密集型任务,由于 Python 的 GIL(全局解释器锁)而受到限制。Dynamsoft Barcode Reader 的新视频解码 API 基于原生 C/C++ 线程,不受 Python 虚拟机的影响。
让我们一窥 Python 示例代码
import cv2 import dbr import time import os # The callback function for receiving barcode results def onBarcodeResult(format, text): print("Type: " + format) print("Value: " + text + "\n") def read_barcode(): video_width = 640 video_height = 480 vc = cv2.VideoCapture(0) vc.set(3, video_width) #set width vc.set(4, video_height) #set height if vc.isOpened(): dbr.initLicense('LICENSE-KEY') rval, frame = vc.read() else: return windowName = "Barcode Reader" max_buffer = 2 max_results = 10 barcodeTypes = 0x3FF | 0x2000000 | 0x4000000 | 0x8000000 | 0x10000000 # 1D, PDF417, QRCODE, DataMatrix, Aztec Code image_format = 1 # 0: gray; 1: rgb888 dbr.startVideoMode(max_buffer, max_results, video_width, video_height, image_format, barcodeTypes, onBarcodeResult) while True: cv2.imshow(windowName, frame) rval, frame = vc.read() start = time.time() try: ret = dbr.appendVideoFrame(frame) except: pass cost = (time.time() - start) * 1000 print('time cost: ' + str(cost) + ' ms') # 'ESC' for quit key = cv2.waitKey(1) if key == 27: break dbr.stopVideoMode() dbr.destroy() cv2.destroyWindow(windowName) if __name__ == "__main__": print("OpenCV version: " + cv2.__version__) read_barcode()
dbr 模块 是使用 Dynamsoft Barcode Reader 构建的 Python 扩展。我们创建了三个方法:startVideoMode()
、appendVideoFrame()
和 stopVideoMode()
。相应的 C/C++ 代码如下。
static PyObject * startVideoMode(PyObject *self, PyObject *args) { printf("Start the video mode\n"); CHECK_DBR(); PyObject *callback = NULL; int maxListLength, maxResultListLength, width, height, imageformat, iFormat, stride; if (!PyArg_ParseTuple(args, "iiiiiiO", &maxListLength, &maxResultListLength, &width, &height, &imageformat, &iFormat, &callback)) { return NULL; } updateFormat(iFormat); if (!PyCallable_Check(callback)) { PyErr_SetString(PyExc_TypeError, "parameter must be callable"); return NULL; } else { Py_XINCREF(callback); /* Add a reference to new callback */ Py_XDECREF(py_callback); /* Dispose of previous callback */ py_callback = callback; } ImagePixelFormat format = IPF_RGB_888; if (imageformat == 0) { stride = width; format = IPF_GRAYSCALED; } else { stride = width * 3; format = IPF_RGB_888; } DBR_SetTextResultCallback(hBarcode, onResultCallback, NULL); int ret = DBR_StartFrameDecoding(hBarcode, maxListLength, maxResultListLength, width, height, stride, format, ""); return Py_BuildValue("i", ret); }
appendVideoFrame()
static PyObject * appendVideoFrame(PyObject *self, PyObject *args) { CHECK_DBR(); PyObject *o; if (!PyArg_ParseTuple(args, "O", &o)) return NULL; #if defined(IS_PY3K) //Refer to numpy/core/src/multiarray/ctors.c Py_buffer *view; int nd; PyObject *memoryview = PyMemoryView_FromObject(o); if (memoryview == NULL) { PyErr_Clear(); return -1; } view = PyMemoryView_GET_BUFFER(memoryview); unsigned char *buffer = (unsigned char *)view->buf; nd = view->ndim; int len = view->len; int stride = view->strides[0]; int width = view->strides[0] / view->strides[1]; int height = len / stride; #else PyObject *ao = PyObject_GetAttrString(o, "__array_struct__"); if ((ao == NULL) || !PyCObject_Check(ao)) { PyErr_SetString(PyExc_TypeError, "object does not have array interface"); return NULL; } PyArrayInterface *pai = (PyArrayInterface*)PyCObject_AsVoidPtr(ao); if (pai->two != 2) { PyErr_SetString(PyExc_TypeError, "object does not have array interface"); Py_DECREF(ao); return NULL; } // Get image information unsigned char *buffer = (unsigned char *)pai->data; // The address of image data int width = (int)pai->shape[1]; // image width int height = (int)pai->shape[0]; // image height int stride = (int)pai->strides[0]; // image stride #endif // Initialize Dynamsoft Barcode Reader TextResultArray *paryResult = NULL; // Detect barcodes ImagePixelFormat format = IPF_RGB_888; if (width == stride) { format = IPF_GRAYSCALED; } else if (width == stride * 3) { format = IPF_RGB_888; } else if (width == stride * 4) { format = IPF_ARGB_8888; } int frameId = DBR_AppendFrame(hBarcode, buffer); return 0; }
stopVideoMode()
static PyObject * stopVideoMode(PyObject *self, PyObject *args) { printf("Stop the video mode\n"); if (hBarcode) { int ret = DBR_StopFrameDecoding(hBarcode); return Py_BuildValue("i", ret); } return 0; }
当在原生线程上执行回调函数时,您必须在 PyGILState_Ensure()
和 PyGILState_Release()
之间调用 Python 代码。
void onResultCallback(int frameId, TextResultArray *pResults, void * pUser) { // Get barcode results int count = pResults->resultsCount; int i = 0; // https://docs.pythonlang.cn/2/c-api/init.html PyGILState_STATE gstate; gstate = PyGILState_Ensure(); for (; i < count; i++) { // https://docs.pythonlang.cn/2.5/ext/callingPython.html PyObject *result = PyObject_CallFunction(py_callback, "ss", pResults->results[i]->barcodeFormatString, pResults->results[i]->barcodeText); if (result == NULL) return NULL; Py_DECREF(result); } PyGILState_Release(gstate); ///////////////////////////////////////////// // Release memory DBR_FreeTextResults(&pResults); }
源代码
https://github.com/dynamsoft-dbr/python/tree/7.x
Android 条形码扫描
使用 Android 摄像头 API 和 Dynamsoft Barcode Reader 构建一个简单的 Android 条形码扫描应用程序。
cameraView.addFrameProcessor(new FrameProcessor() { @SuppressLint("NewApi") @Override public void process(@NonNull final Frame frame) { try { if (isDetected && isCameraOpen) { YuvImage yuvImage = new YuvImage(frame.getData(), ImageFormat.NV21, frame.getSize().getWidth(), frame.getSize().getHeight(), null); if (width == 0) { width = yuvImage.getWidth(); height = yuvImage.getHeight(); stride = yuvImage.getStrides()[0]; reader.setErrorCallback(errorCallback, null); reader.setTextResultCallback(textResultCallback, null); reader.setIntermediateResultCallback(intermediateResultCallback, null); reader.startFrameDecoding(10, 10, width, height, stride, EnumImagePixelFormat.IPF_NV21, ""); } else { PublicRuntimeSettings s = reader.getRuntimeSettings(); int frameid = reader.appendFrame(yuvImage.getYuvData()); Log.i("FrameId", frameid + ""); } if (bUpateDrawBox) { bUpateDrawBox = false; Message message = handler.obtainMessage(); Rect imageRect = new Rect(0, 0, width, height); message.obj = imageRect; message.what = 0x001; handler.sendMessage(message); } isDetected = true; } } catch (Exception e) { e.printStackTrace(); } } });
调用 startDecodingFrame()
初始化环境,并重复调用 appendFrame()
来添加预览图像。
源代码
https://github.com/dynamsoft-dbr/android-barcode-decode-video
iOS 条形码扫描
使用 iOS 摄像头 API 和 Dynamsoft Barcode Reader 构建一个简单的 iOS 条形码扫描应用程序。
(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection; { isCurrentFrameDecodeFinished = NO; CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly); int bufferSize = (int)CVPixelBufferGetDataSize(imageBuffer); void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer); CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly); NSData * buffer = [NSData dataWithBytes:baseAddress length:bufferSize]; startRecognitionDate = [NSDate date]; if (width != (int)CVPixelBufferGetWidth(imageBuffer)) { width = (int)CVPixelBufferGetWidth(imageBuffer); height = (int)CVPixelBufferGetHeight(imageBuffer); stride = CVPixelBufferGetBytesPerRow(imageBuffer); [m_barcodeReader setDBRErrorDelegate:self userData:nil]; [m_barcodeReader setDBRTextResultDelegate:self userData:nil]; [m_barcodeReader setDBRIntermediateResultDelegate:self userData:nil]; [m_barcodeReader startFrameDecoding:10 maxResultQueueLength:100 width:width height:height stride:stride format:EnumImagePixelFormatARGB_8888 templateName:@"" error:nil]; } else { [m_barcodeReader appendFrame:buffer]; } }
调用以下方法初始化环境,并重复调用 -(NSInteger)appendFrame:(NSData*) bufferBytes
来添加预览图像。
(void)startFrameDecoding:(NSInteger)maxQueueLength maxResultQueueLength:(NSInteger)maxResultQueueLength width:(NSInteger)width height:(NSInteger)height stride:(NSInteger)stride format:(EnumImagePixelFormat)format templateName:(NSString* _Nonnull)templateName error:(NSError* _Nullable * _Nullable)error
源代码
https://github.com/dynamsoft-dbr/ios-barcode-decode-video
技术支持
如果您对 Dynamsoft Barcode Reader SDK 有任何疑问,请随时联系 support@dynamsoft.com。
发布历史
v7.0, 2019/07/12
新建
- 重构了大部分模块,提供了一个灵活的条形码读取框架,允许对参数进行自定义,以适应各种条形码场景。
- 允许在解码过程中访问中间结果(灰度图像、二值化图像、文本区域等)。
- 添加了新的接口以支持视频解码和帧解码,以提高交互灵敏度。
- 提供了在二值化、定位或条形码类型识别等不同阶段终止解码过程的方法。
- 添加了一种新的条形码定位方法“直接扫描”,以显着减少高质量图像的解码时间。
改进
- 增强了与许可证初始化失败相关的错误消息。
- 改进了已解码条形码的详细结果,包括更多的条形码格式规范。
- 改进了结果输出,以支持按置信度、条形码位置或格式的顺序输出条形码结果。
修复
- 修复了有时条形码计算不正确的问题。