基于zbar的相机扫码性能优化实践

Android新兴扫码框架:XCodeScanner(持续更新) 一个Android平台上更快更简单更精准的条形码及二维码解析框架。采用ZBar解析图像数据,兼容Android4.0 (API14)及以上版本。GitHub地址 目录 示例demo 功能介绍 UML类图 集成方式 使用方式 更新计划 版本记录 关于作者 示例demo Demo下载 示例效果 点此下载 或扫描下面二维码 功能介绍 本项目... 阅读详情

基于zbar的,简单、易用、高性能的扫码器

1 需求

公司的多款工具类app都用到了相机扫码功能。近一年来,由于业务的快速发展,业务方对扫码模块的性能也有了更高要求,主要是3个方面:

  1. 由于使用中经常会遇到商品条码密集排列的情况,因此要求扫码识别区域要非常精确。举个例子,比如扫码界面中展示给用户的扫码框是一个200*100的矩形,那么真正被识别的图像数据就只能是这个矩形框中的内容。
  2. 针对多个条码连续扫描识别的情形,要求每个条码的识别时间尽可能地短,这样使用起来效率更高,用户体验更好。
  3. 扫码时相机的对焦区域(focus area)应该跟随视图中扫码框的位置和大小而变化,而不是始终位于预览画面中央(相机的默认设置)。

项目代码:SimpleScanner

2 优化效果

为了方便查看扫码识别速度,我们在相机预览的回调方法onPreviewFrame中添加了如下代码来打印耗时:

@Override
public void onPreviewFrame(byte[] data, Camera camera) {
	long startTime = System.currentTimeMillis();
	...

	//data数据处理及识别

	...
	Log.e("logg", String.format("图像处理及识别耗时: %d ms", System.currentTimeMillis() - startTime));
}

下面分别就扫描识别条形码、二维码,对本项目(SimpleScanner)和当前较为流行的扫码库barcodescanner (https://github.com/dm77/barcodescanner) 进行速度对比(测试平台为小米4,cpu为高通801):

条形码

barcodescanner

E/logg: 图像处理及识别耗时: 469 ms
E/logg: Contents = 1234567890123, Format = CODE128
E/logg: 图像处理及识别耗时: 439 ms
E/logg: Contents = 1234567890123, Format = CODE128
E/logg: 图像处理及识别耗时: 434 ms
E/logg: Contents = 1234567890123, Format = CODE128
E/logg: 图像处理及识别耗时: 446 ms
E/logg: Contents = 1234567890123, Format = CODE128
E/logg: 图像处理及识别耗时: 455 ms
E/logg: Contents = 1234567890123, Format = CODE128
E/logg: 图像处理及识别耗时: 439 ms
E/logg: Contents = 1234567890123, Format = CODE128

SimpleScanner

E/logg: 图像处理及识别耗时: 22 ms
E/logg: Contents = 1234567890123, Format = CODE128
E/logg: 图像处理及识别耗时: 20 ms
E/logg: Contents = 1234567890123, Format = CODE128
E/logg: 图像处理及识别耗时: 22 ms
E/logg: Contents = 1234567890123, Format = CODE128
E/logg: 图像处理及识别耗时: 23 ms
E/logg: Contents = 1234567890123, Format = CODE128
E/logg: 图像处理及识别耗时: 23 ms
E/logg: Contents = 1234567890123, Format = CODE128
E/logg: 图像处理及识别耗时: 22 ms
E/logg: Contents = 1234567890123, Format = CODE128

二维码

barcodescanner

E/logg: 图像处理及识别耗时: 448 ms
E/logg: Contents = 草料二维码, Format = QRCODE
E/logg: 图像处理及识别耗时: 469 ms
E/logg: Contents = 草料二维码, Format = QRCODE
E/logg: 图像处理及识别耗时: 453 ms
E/logg: Contents = 草料二维码, Format = QRCODE
E/logg: 图像处理及识别耗时: 473 ms
E/logg: Contents = 草料二维码, Format = QRCODE
E/logg: 图像处理及识别耗时: 511 ms
E/logg: Contents = 草料二维码, Format = QRCODE
E/logg: 图像处理及识别耗时: 461 ms
E/logg: Contents = 草料二维码, Format = QRCODE

SimpleScanner

E/logg: 图像处理及识别耗时: 58 ms
E/logg: Contents = 草料二维码, Format = QRCODE
E/logg: 图像处理及识别耗时: 61 ms
E/logg: Contents = 草料二维码, Format = QRCODE
E/logg: 图像处理及识别耗时: 56 ms
E/logg: Contents = 草料二维码, Format = QRCODE
E/logg: 图像处理及识别耗时: 62 ms
E/logg: Contents = 草料二维码, Format = QRCODE
E/logg: 图像处理及识别耗时: 58 ms
E/logg: Contents = 草料二维码, Format = QRCODE
E/logg: 图像处理及识别耗时: 62 ms
E/logg: Contents = 草料二维码, Format = QRCODE

可见SimpleScanner相比barcodescanner基本能有90%左右的性能提升。虽然400ms并不起眼,但在实际使用中,一个是对焦即识别,另一个是对焦后要稍微停顿一下才能识别,对于连续多次扫码的使用场景,SimpleScanner的使用体验要明显更好。

3 项目结构简介

项目中比较重要的类主要有5个:

ZBarScannerView

zbar扫码视图,可以直接在Activity中使用。

主要做了一件事:对相机预览的回调方法onPreviewFrame中的data(预览图像数据)进行一些预处理,并将处理后的数据交给zbar解码库去识别。

BarcodeScannerView

基本扫码视图,是ZBarScannerView的父类。

主要包含了对相机预览的相关操作。同时,BarcodeScannerView也是一个视图容器(本质上是一个FrameLayout),包含了CameraPreview(相机预览画面)和ViewFinderView(覆盖在相机预览画面上层的扫码框、阴影遮罩等)。

CameraPreview

这是一个SurfaceView,用于展示相机预览画面。

ViewFinderView

覆盖在相机预览上层的view,包含扫码框、扫描线、扫码框周围的阴影遮罩等。

可以直接修改以下字段来改变ViewFinderView的外观:

private float widthRatio = 0.8f;//扫码框宽度占view总宽度的比例
private float heightWidthRatio = 0.5f;//扫码框的高宽比
private int leftOffset = -1;//扫码框相对于左边的偏移量,若为负值,则扫码框会水平居中
private int topOffset = 200;//扫码框相对于顶部的偏移量,若为负值,则扫码框会竖直居中
private boolean isLaserEnabled = true;//是否显示扫描线

ViewFinderView本身的代码非常简单,并且与相机预览、扫码识别的逻辑几乎没有任何关联,因此修改起来十分容易。如果需要,可以自由地修改这个类来定制出自己需要的扫码界面。

ViewFinderView和CameraPreview的宽高都是match_parent,也就是说ViewFinderView和CameraPreview是等大的。

SimpleScannerActivity

ZBarScannerView的使用方法示例。

4 主要优化点简介

这里只对主要的3个优化点做个简要介绍,具体细节请查看项目源码。

(1)精确截取扫码识别区域

通常做法:

目前市面上大多数app对于扫码识别区域的处理方式都是:将整个预览图像交给核心解码库(即zbar或zxing)进行识别。也就是说(如上图所示),虽然在屏幕偏上方有一个扫码框,用户也会自然而然地将要扫的条码放到扫码框区域之内,但实际上,在app内部,传递给核心解码库进行识别的,是整个预览画面。

这种处理方式的好处是代码逻辑非常简单,而缺点有两个:

  • 首先是上面提到过的那种场景,当有多个排列较为紧密的条码,而你只想识别其中的某一个时,操作起来会比较困难。
  • 第二,因为传递给核心解码库的是整个预览画面,数据量较大,因此识别速度也会较慢。

优化:

public Rect getScaledRect(int previewWidth, int previewHeight) {
    if (scaledRect == null) {
        Rect framingRect = mViewFinderView.getFramingRect();//获得扫码框区域
        int viewFinderViewWidth = mViewFinderView.getWidth();
        int viewFinderViewHeight = mViewFinderView.getHeight();

        int width, height;
        if (DisplayUtils.getScreenOrientation(getContext()) == Configuration.ORIENTATION_PORTRAIT//竖屏使用
                && previewHeight < previewWidth) {
            width = previewHeight;
            height = previewWidth;
        } else if (DisplayUtils.getScreenOrientation(getContext()) == Configuration.ORIENTATION_LANDSCAPE//横屏使用
                && previewHeight > previewWidth) {
            width = previewHeight;
            height = previewWidth;
        } else {
            width = previewWidth;
            height = previewHeight;
        }

        scaledRect = new Rect(framingRect);
        scaledRect.left = scaledRect.left * width / viewFinderViewWidth;
        scaledRect.right = scaledRect.right * width / viewFinderViewWidth;
        scaledRect.top = scaledRect.top * height / viewFinderViewHeight;
        scaledRect.bottom = scaledRect.bottom * height / viewFinderViewHeight;
    }

    return scaledRect;
}

要明白这个方法的含义,首先需要知道的是(如上图所示):虽然相机预览画面(CameraPreview)和上层的ViewFinderView大小一样,但实际上,系统传递给我们的预览图像数据(也就是onPreviewFrame方法的data参数),其尺寸与ViewFinderView的尺寸通常是不同的。因此,为了在预览图像数据中截取正确的区域,我们必须根据预览图像数据和ViewFinderView的尺寸之比对扫码框区域进行缩放。举个简单的例子:假设ViewFinderView的尺寸是1280*720,扫码框的尺寸是100*200,而预览图像数据的尺寸是2560*1440,那么,要想实际截取的区域与我们所看到的区域一致,我们就必须用一个200*400的矩形框去预览图像数据中截取。也就是说,如果预览图像数据的尺寸是ViewFinderView的尺寸的两倍,那么我们用来截取预览图像数据的矩形框的尺寸也应该是ViewFinderView中扫码框尺寸的两倍。

上面getScaledRect(int previewWidth, int previewHeight)方法所做的事情就是根据ViewFinderView和预览图像数据的尺寸之比,对扫码框的矩形区域进行缩放。

  • 方法的参数previewWidth和previewHeight就是预览图像数据的宽和高。
  • mViewFinderView.getFramingRect()获得的是扫码框在ViewFinderView中的矩形区域。

(2)提高识别速度

事实上,要使实际截取的区域与我们所看到的区域一致,仅仅像上面那样缩放扫码框是不够的。这是因为,系统传递给我们的预览图像数据不仅尺寸可能会与ViewFinderView不同,其方向通常也与窗口的方向不同。比如,我们看到的预览图像是下面这样的(图中的黄色框是我画的扫码框示意,你可以把薄荷糖盒子想象成要扫的条码):

而实际上,系统传递给我们的预览图像数据很可能是这样的:

显然,在这种情况下,如果你直接拿缩放后的矩形框去预览图像数据中截取,那就完全截错了地方。那该怎么办呢?有两种解决办法可供选择:

  1. 将图像数据顺时针旋转90度
  2. 将矩形框逆时针旋转90度

目前我所知的扫码库采用的都是第一种办法,即将图像数据顺时针旋转90度,代码如下:

public byte[] rotateData(byte[] data, Camera camera) {
    Camera.Parameters parameters = camera.getParameters();
    int width = parameters.getPreviewSize().width;
    int height = parameters.getPreviewSize().height;

    int rotationCount = getRotationCount();
    for (int i = 0; i < rotationCount; i++) {
        byte[] rotatedData = new byte[data.length];
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++)
                rotatedData[x * height + height - y - 1] = data[x + y * width];
        }
        data = rotatedData;
        int tmp = width;
        width = height;
        height = tmp;
    }

    return data;
}

简单的说,就是根据位置关系对图像数据中的每一个点进行映射赋值。对于尺寸为1920*1080的图像预览数据,rotatedData[x * height + height - y - 1] = data[x + y * width]这样的运算要重复200多万次,这显然是一个十分耗时的过程。

而另一种选择就是将矩形框逆时针旋转90度,其代码如下:

public Rect getRotatedRect(int previewWidth, int previewHeight, Rect rect) {
    if (rotatedRect == null) {
        int rotationCount = getRotationCount();
        rotatedRect = new Rect(rect);

        if (rotationCount == 1) {//若相机图像需要顺时针旋转90度,则将扫码框逆时针旋转90度
            rotatedRect.left = rect.top;
            rotatedRect.top = previewHeight - rect.right;
            rotatedRect.right = rect.bottom;
            rotatedRect.bottom = previewHeight - rect.left;
        } else if (rotationCount == 2) {//若相机图像需要顺时针旋转180度,则将扫码框逆时针旋转180度
            rotatedRect.left = previewWidth - rect.right;
            rotatedRect.top = previewHeight - rect.bottom;
            rotatedRect.right = previewWidth - rect.left;
            rotatedRect.bottom = previewHeight - rect.top;
        } else if (rotationCount == 3) {//若相机图像需要顺时针旋转270度,则将扫码框逆时针旋转270度
            rotatedRect.left = previewWidth - rect.bottom;
            rotatedRect.top = rect.left;
            rotatedRect.right = previewWidth - rect.top;
            rotatedRect.bottom = rect.right;
        }
    }

    return rotatedRect;
}

旋转矩形框的原理很简单,假设矩形框的left、top、right、bottom分别为a、b、c、d,如下图所示:

以下面这种方向的预览图像数据为例:

我们需要得到下面这样的一个矩形框,也就是将矩形框逆时针旋转了90度:

由图可知旋转前后矩形框的对应关系为:

–lefttoprightbottom
旋转前abcd
旋转后bheight-cdheight-a

有了这张表格那么下面这段代码就很容易理解了:

if (rotationCount == 1) {//若相机图像需要顺时针旋转90度,则将扫码框逆时针旋转90度
    rotatedRect.left = rect.top;
    rotatedRect.top = previewHeight - rect.right;
    rotatedRect.right = rect.bottom;
    rotatedRect.bottom = previewHeight - rect.left;
}

需要注意的是:第二种处理方法(旋转矩形框,而不旋转预览图像数据)只适用核心解码库为zbar的情形。如果核心解码库为zxing,由于zxing不能识别竖直排列的条形码,因此只能采用旋转预览图像数据这种方式。

(3)根据扫码框的位置和大小调整相机的对焦区域

在实际应用中,扫码页中除了扫码框之外,可能还需要放入很多其他内容(如文字、列表、按钮等),因此有时候扫码框并不位于界面中央,比如在我们的一个app中,扫码框就是位于画面中十分偏上的位置。另一方面,android相机默认是以预览画面的中央为目标来调整焦距的,这就会造成一个问题,如下图所示:

近处是一个香烟盒的侧面,上面有一个条形码;远处是雨伞和地面。我们将香烟盒放在扫码框中是想要扫取香烟盒上的条形码,然而因为地面和雨伞位于画面中央,因此相机便将焦点放在了地面和雨伞上,结果就是近处的香烟盒因为失焦而模糊,上面的条形码自然也就无法被正常地识别。

为了解决这一问题,我们需要将扫码框所在的区域作为相机的对焦区域。Camera.Parameters提供了setFocusAreas (List<Camera.Area> focusAreas)方法来实现这一功能。

在定义对焦区域的时候需要注意,这里坐标系的(0,0)位于预览画面的中央,预览画面左上角的坐标为(-1000,-1000),预览画面右下角的坐标为(1000,1000),如下图所示:

基于与上面优化点(1)、(2)中一样的原理,这里我们也需要通过对扫码框区域做缩放和旋转操作来得到对应的对焦区域,但是这样还不够,因为对焦区域坐标系的坐标原点在画面中央,左上角为(-1000,-1000),我们还需要对缩放、旋转后的区域进行平移。

设置对焦区域的主要代码如下:

/**
 * 设置对焦区域
 */
private void setupFocusAreas() {
    /*
     * 1.根据ViewFinderView和2000*2000的尺寸之比,缩放对焦区域
     */
    Rect framingRect = viewFinderView.getFramingRect();//获得扫码框区域
    int viewFinderViewWidth = viewFinderView.getWidth();
    int viewFinderViewHeight = viewFinderView.getHeight();
    int width = 2000, height = 2000;

    Rect scaledRect = new Rect(framingRect);
    scaledRect.left = scaledRect.left * width / viewFinderViewWidth;
    scaledRect.right = scaledRect.right * width / viewFinderViewWidth;
    scaledRect.top = scaledRect.top * height / viewFinderViewHeight;
    scaledRect.bottom = scaledRect.bottom * height / viewFinderViewHeight;

    /*
     * 2.旋转对焦区域
     */
    Rect rotatedRect = new Rect(scaledRect);
    int rotationCount = getRotationCount();
    if (rotationCount == 1) {//若相机图像需要顺时针旋转90度,则将扫码框逆时针旋转90度
        rotatedRect.left = scaledRect.top;
        rotatedRect.top = 2000 - scaledRect.right;
        rotatedRect.right = scaledRect.bottom;
        rotatedRect.bottom = 2000 - scaledRect.left;
    } else if (rotationCount == 2) {//若相机图像需要顺时针旋转180度,则将扫码框逆时针旋转180度
        rotatedRect.left = 2000 - scaledRect.right;
        rotatedRect.top = 2000 - scaledRect.bottom;
        rotatedRect.right = 2000 - scaledRect.left;
        rotatedRect.bottom = 2000 - scaledRect.top;
    } else if (rotationCount == 3) {//若相机图像需要顺时针旋转270度,则将扫码框逆时针旋转270度
        rotatedRect.left = 2000 - scaledRect.bottom;
        rotatedRect.top = scaledRect.left;
        rotatedRect.right = 2000 - scaledRect.top;
        rotatedRect.bottom = scaledRect.right;
    }

    /*
     * 3.坐标系平移
     */
    Rect rect = new Rect(rotatedRect.left - 1000, rotatedRect.top - 1000, rotatedRect.right - 1000, rotatedRect.bottom - 1000);

    /*
     * 4.设置对焦区域
     */
    Camera.Parameters parameters = cameraWrapper.camera.getParameters();
    if (parameters.getMaxNumFocusAreas() > 0) {
        Camera.Area area = new Camera.Area(rect, 1000);
        ArrayList<Camera.Area> areaList = new ArrayList<>();
        areaList.add(area);
        parameters.setFocusAreas(areaList);
        cameraWrapper.camera.setParameters(parameters);
    } else {
        Log.e(TAG, "不支持设置对焦区域");
    }

    Log.e(TAG, "对焦区域:" + rect.toShortString());
}

这当中涉及的细节比较多,限于篇幅原因就不再细说了,如果希望更一进步了解的话可以参考Camera的官方文档中关于对焦区域的部分(这里有我之前翻译的一个中文版)以及Camera.Parameters.getFocusAreas()方法的官方文档。

经过以上调整,我们便可以得到一个正确的对焦效果了,如下图所示:

当扫码框的位置和大小变化时,对焦区域也会跟着变化。

5 最后

相机扫码涉及的知识点比较多,限于篇幅无法在此一一讲清。建议有兴趣深入的朋友先对Camera类、相机预览机制、各种方向的概念(窗口方向、设备自然方向、相机方向)等做一个大概了解,然后再去阅读本项目(SimpleScanner)的源码。

当然,即便没有阅读源码,你也可以很容易地将本项目移植到你自己的app中使用——只需要修改ViewFinderView类来定制你需要的扫码界面即可。具体使用方式参考SimpleScannerActivity类。

参考

ZBar项目简介及安装配置 2021SC@SDUSC 目录 一、ZBar项目简介 一、ZBar项目简介 1. ZBar是什么 ZBar条形码阅读器是一个开源软件,适用于读取不同来源的条形码,如视频流、图像文件和原始强度传感器。它支持EAN-13/UPC-A, UPC-E, EAN-8, Code 128,Code 39, Codabar, Interleaved 2/5和二维码。包括在库是解码捕获的条形码图像和图像的基本应用程序,使用视频设备(如网络摄像头)作为条形码扫描仪。对于开发者,支持语言绑定包括C... 阅读详情

相关推荐

介绍一下这个库zbar

支持多种条形码格式 :ZBar 支持包括 QR 码、EAN-13、UPC-A、EAN-8、Code 128、Code 39 等常见的条形码格式。2. libzbar :一个用于开发的库,提供了扫描和解码条形码的功能。- 高效的解码 :ZBar 具有高效的解码算法,能够快速识别条形码。- **库存管理**:通过扫描条形码快速识别和管理库存物品。- **物流管理**:包裹和货物的条形码扫描与追踪。- **支付系统**:扫码支付和条形码支付系统。- **票务系统**:电子票的扫码验证。

weixin_44532539的博客 1695

使用zbar库识别条形码与二维码

QR 码是一种高密度矩阵条码,Quirc是一个基于C/C++的一个二维码库 ,从图像中提取和解码QR 码。其部分功能还需依赖OpenCV库共同实现,比如quirc-demo-opencv(即图形的显示);inspect-opencv(测试)。1.足够快,可以用于实时视频:在现代x86内核上,从VGA帧提取和解码大约需要50毫秒。2.具有鲁棒且宽容的识别算法。可以正确识别和解码旋转和/或倾斜于相机的QR码。它还可以区分和解码同一图像中的多个代码。

m0_74259636的博客 7854

解码背后的艺术:当ZXing遇见ZBar,一场关于效率与兼容性的技术博弈

本文深入探讨了ZXing与ZBar在Android扫码开发中的技术博弈,分析了二者在效率与兼容性上的核心差异。通过整合两种方案并利用NDK进行性能优化,实现了兼顾解码速度和格式支持的混合解决方案,显著提升了扫一扫功能的用户体验。

jupi8的博客 620

Zbar的安装及环境配置(环境:VS2022、64位Win10、OpenCV3.6.0)

本教程是针对Zbar的安装及环境配置(安装时使用的环境与软件:VS2022、64位Win10、OpenCV3.6.0)

weixin_44913824的博客 5426

C# ZBar解码经验分享及常见问题解答

而ZBar作为一个开源的条形码和二维码解码库,提供了丰富的功能和易于使用的接口,广受开发者的青睐。在解码过程中,如果出现结果不准确或缺失的情况,可能是由于图像质量不佳、码的损坏或误差容忍度较低等原因造成的。ZBar是一个跨平台的条形码和二维码解码库,它提供了许多编程语言的接口,包括C/C++、Python和Java等。ZBar是一款功能强大的解码库,但当处理大型图像或多个码时可能会导致解码速度较慢。如果遇到无法解码的情况,可以尝试使用其他解码库或将图像传递给云端的解码服务进行处理。在解码后,可以通过遍历。

ByteLegend的博客 745

Zbar实战:Android扫码集成、踩坑与性能优化全攻略

二维码识别与条形码扫描是移动应用中高频出现的实用功能,尤其在仓库管理、工业手持终端等场景下,扫码的实时性与资源占用直接影响用户体验。常见的开源方案中,ZXing因文档完善而广受欢迎,而Zbar则凭借基于C语言实现的图像金字塔快速定位算法,在低端设备上表现出更优的识别速度与离线解码能力。Zbar体积小、原生支持多种一维/二维条码,适用于对包体积敏感或硬件配置受限的Android项目。然而集成过程中常遇到相机预览适配、中文乱码、识别率优化等问题。本文从工程实践出发,梳理Zbar的接入流程、生命周期管理、性能调优

weixin_34204722的博客 499

LBXScan错误处理与调试技巧:快速定位扫码问题终极指南

LBXScan是一款功能强大的iOS二维码与条形码扫描库,支持系统原生API、ZXing和ZBar三种扫码引擎。在实际开发中,扫码功能可能会遇到各种问题,本文为您提供完整的错误处理与调试技巧,帮助您快速定位并解决扫码问题。😊 ## 📱 常见扫码错误类型与排查方法 ### 1. 相机权限问题处理 相机权限是扫码功能的基础,LBXScan提供了完善的权限检查机制。在[LBXScan/UI/LB

gitblog_00823的博客 430

构建自定义二维码扫码功能

在现代信息技术不断发展的今天,二维码已经成为一种普遍的编码技术,它能够在有限的空间内存储大量的信息。二维码的出现,极大地提升了数据交换的效率,被广泛应用于商品追溯、移动支付、信息传递等众多领域。然而,二维码的解读需要依赖于强大的解码库,其中ZBar库就是一个在开源领域广受欢迎的解码工具,它能快速准确地解析多种类型的条形码和二维码。在本章中,我们将首先简要介绍二维码的基本原理,然后详细探讨ZBar库的核心功能和使用方法。

weixin_42620563的博客 1017

Android扫码技术深度指南:从基础集成到高级优化

现代App为何需要扫码功能?从移动支付到身份验证,从商品溯源到社交分享,扫码已成为移动应用中不可或缺的核心功能。Android扫码技术从最初的简单识别发展到如今的高性能多格式支持,为开发者提供了丰富的选择。本文将从技术选型、集成实战到性能优化,带你全面掌握Android条形码识别技术。 ## 🔍 如何选择扫码库:ZBar vs ZXing对比分析 在Android扫码技术中,ZBar和ZXi

gitblog_00142的博客 1134

ZBar二维码扫描技术深度解析及优化实践

htmltable {th, td {th {pre {简介:二维码扫描技术在移动设备和物联网中发挥重要作用。ZBar库提供了跨平台的二维码及条形码读取功能,本文章深入探讨了ZBar的使用和常见的问题修复,以及对官方DEMO的修改和优化建议。内容涵盖内存泄漏修复、扫描速度和准确性提升、用户界面优化、多线程处理、错误处理和权限管理等。文章还强调了兼容性测试的重要性,以确保二维码扫描应用在不同设备和操作系统版本上都能稳定运行。

weixin_42578963的博客 388

Windows10下MATLAB2017a安装与破解实战:手把手教你搞定双镜像加载

本文详细介绍了在Windows 10系统下安装和破解MATLAB 2017a的完整步骤,包括双镜像加载、许可证配置及常见问题解决方案。通过手把手教程,帮助用户顺利完成安装并优化性能,特别适合工程计算领域的初学者和专业人士。

weixin_29053383的博客 419

Zbar扫码优化

项目中有用到二维码扫描的功能,集成zbar后,一切看起来都挺好的,功能都好使,直到使用乐固进行了加固。 加固后的表现简直令人难受,扫描动画几乎停滞,整个界面卡的看起来和使用20kB的网速观看高清视频一样,点击按钮和返回键甚至都没反应或者过了很久才反应。很明显,主线程时间被大幅度占用了。 开始怀疑是加固方有问题,就换用了几个其他的加固方案,比如360加固,发现表现都差不多。然后直接联系乐固的客服

同志,我还能抢救一下! 6375

SpringDoc 基本使用指南

SpringDoc 凭借零配置启动注解简洁和深度兼容 Spring 生态的优势,已成为 Spring Boot API 文档的首选工具。自动化- 减少手动维护文档的成本;标准化- 严格遵循 OpenAPI 3 规范;可扩展- 分组、安全控制灵活适配复杂项目。访问即可查看交互式文档(默认路径)

墨鸦的博客 1万+

跨平台扫码组件的架构思考:从Android到多端统一的演进之路

本文探讨了跨平台扫码组件的架构演进,从Android平台出发,分析了ZXing和ZBar等解码引擎的技术选型与性能优化策略。重点介绍了如何通过NDK编译原生代码、插件化设计实现多端统一,并整合热词“扫一扫”技术,提升扫码体验和系统可维护性。

assembly8low的博客 834

双内核架构 Xenomai 4 安装教程

双内核 Xenomai 4 安装指南

^_^ 4469

从零搭建医疗化验单识别系统:深度学习+OCR实战教程(附数据集)

本文详细介绍了如何从零开始构建一个医疗化验单智能识别系统。通过融合深度学习和OCR技术,系统能够应对版式多样、内容专业的医疗单据,实现从文本检测、识别到关键信息结构化抽取的全流程。文章提供了数据集处理、模型选型、训练微调及工程化部署的实战指南,并分享了避坑经验与性能优化策略,为开发者解决医疗信息化痛点提供了完整的技术方案。

k9l0m1的博客 492

从零构建Android扫码引擎:ZXing与ZBar的融合与性能调优实战

本文详细介绍了如何从零构建高性能Android扫码引擎,通过融合ZXing和ZBar双解码库的优势,结合NDK进行底层优化。内容涵盖环境配置、双引擎架构设计、图像处理性能调优、中文编码兼容性处理以及实战性能监控,帮助开发者打造高效、稳定的扫一扫功能。

z2a3b4c5d的博客 307

Claude Code(7):从零创建你的第一个 Skill

在前面的文章,我们介绍了 Skills 的基本概念,这篇我们深入实战,从零开始创建你的第一个 Skill,并逐步解锁所有配置项,让 Claude Code 真正熟悉你的测试规范和习惯。

blues_C的博客 649

Android条形码扫描实战:从零构建高效扫码应用

还在为Android应用集成复杂的条形码扫描功能而烦恼吗?BarcodeScanner库为你提供了开箱即用的完整解决方案,支持QR码、条形码、Data Matrix等多种格式的快速识别。这个开源项目采用模块化设计,让开发者能够轻松实现高性能的扫码体验,无论是电商应用的商品识别,还是票务系统的入场验证,都能完美胜任。 ## 🎯 解决扫码集成的三大痛点 传统Android扫码开发常面临集成复杂、

gitblog_00830的博客 936

Epicor ERP 界面新增客制化字段操作方法

我发现国内对于epicor这个erp软件的教程非常少,所以录制一些我实际遇到的需求,整理了实现过程,提供给大家参考。

cqxianggang的专栏 412

基于SiamFC的红外目标跟踪

初始化阶段:在第一帧中,通过用户标注获得目标框,从目标区域中提取一个固定大小的模板(称为 exemplar)。搜索阶段:在后续帧中,在以当前目标中心为中心的较大区域(称为 instance)内进行搜索,将模板与搜索区域通过卷积操作得到响应图。位置更新:在响应图中找到峰值位置,并转换到原图坐标,用以更新目标中心。部分实现中还会考虑多尺度搜索和目标尺寸更新。import cv2# 定义 SiamFC 简易网络(示例)# 特征提取部分,注意红外图像通常为单通道,使用较少的卷积核数提高实时性。

weixin_43745234的博客 774

ZBar性能优化指南:如何提高条形码识别速度和准确率

ZBar是一款强大的开源条形码识别库,支持多种平台和编程语言。本文将分享实用的ZBar性能优化技巧,帮助你显著提升条形码识别速度和准确率,让应用在各种场景下都能高效工作。 ## 一、优化图像质量:提升识别基础 高质量的图像是快速准确识别的基础,以下是几个关键优化方向: ### 1.1 调整分辨率至最佳范围 ZBar识别的理想分辨率是每个条形码模块(最窄的条或空的宽度)对应3个像素左右。低于

gitblog_00498的博客 658

RK3568平台(USB篇)USB HUB集线器

hub,集线器,连接在host与device之间的一种用于usb接口扩展的usb设备。可以将一个usb上行接口扩展为多个下行接口,使得一个host可以同时与多个device连接。一般来说,一块hub桥接芯片可扩展4个usb接口,而市面上的一拖七hub,其实使用了两块hub桥接芯片,其中一块hub桥接芯片的上行端口连接到另一块hub桥接芯片的下行端口。hub的上行端口面向host,下行端口面向device。在下行端口上,hub提供了设备接入检测和设备移除检测的能力,并给下行端口供电。

weixin_49303682的博客 964

zBarLibary源码架构解析:深入理解zbar与zxing的完美结合

你是否曾为Android应用中的二维码扫描速度慢而烦恼?或者为条形码识别准确率低而头疼?今天,我将为你揭秘一个优秀的开源解决方案——**zBarLibary**,这是一个巧妙结合了zbar快速扫描和zxing强大生成能力的Android二维码(条形码)库。在本文中,我们将深入解析这个项目的源码架构,了解它如何将两个优秀库的优势完美融合,为开发者提供便捷高效的二维码功能。 ## 🔥 项目核心优势

gitblog_01075的博客 653

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

Android条码扫描性能优化:BarcodeScanner最佳实践终极指南

🚀 想要为你的Android应用添加快速、准确的条码扫描功能吗?BarcodeScanner库正是你需要的终极解决方案!这个强大的开源项目提供了完整的条码扫描能力,支持多种扫描引擎和灵活的集成方式。 ## 📱 为什么选择BarcodeScanner? BarcodeScanner是一个专为Android设计的条码扫描库,支持ZXing和ZBar两种主流的扫描引擎。无论你是开发电商应用、库存

gitblog_00752的博客 954

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Android条形码扫描终极指南:深度解析ZBar引擎与原生库集成

在Android应用开发中,实现高效的条形码扫描功能是一个常见需求。BarcodeScanner库提供了基于ZXing和ZBar的强大扫描解决方案,特别适合需要高性能、多格式支持的商业应用。本文将深入探讨ZBar引擎的核心原理、JNI原生库的集成方法以及最佳实践,帮助开发者快速掌握这一强大的条形码扫描技术。 ## 🔍 项目简介与核心功能 BarcodeScanner是一个开源的Android

gitblog_00940的博客 1013

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

上一篇: Android Camera详解
下一篇: ViewPager导航控件——SimpleViewpagerIndicator
三杯两盏
博客等级 码龄10年 87粉丝 27原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值