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

相机流的实时条形码扫描

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2019年7月25日

CPOL
viewsIcon

11566

在本帖中,我们将讨论如何利用 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

新建

  • 重构了大部分模块,提供了一个灵活的条形码读取框架,允许对参数进行自定义,以适应各种条形码场景。
  • 允许在解码过程中访问中间结果(灰度图像、二值化图像、文本区域等)。
  • 添加了新的接口以支持视频解码和帧解码,以提高交互灵敏度。
  • 提供了在二值化、定位或条形码类型识别等不同阶段终止解码过程的方法。
  • 添加了一种新的条形码定位方法“直接扫描”,以显着减少高质量图像的解码时间。

改进

  • 增强了与许可证初始化失败相关的错误消息。
  • 改进了已解码条形码的详细结果,包括更多的条形码格式规范。
  • 改进了结果输出,以支持按置信度、条形码位置或格式的顺序输出条形码结果。

修复

  • 修复了有时条形码计算不正确的问题。
© . All rights reserved.