zxing_zxing java - CSDN
  • Zxing的使用

    千次阅读 2019-06-22 18:17:10
    二维码zxing使用-集成zxing 参考文档:https://github.com/open-android/Zxing 二维码zxing使用-生成二维码 public void click(View view) { String str = et.getText().toString().trim(); if ...
        public void click(View view) {
            String str = et.getText().toString().trim();
            if (TextUtils.isEmpty(str)) {
                Toast.makeText(this, "数据不能为空!!!", Toast.LENGTH_SHORT).show();
            } else {
                try {
                    Bitmap bitmap = BitmapUtils.create2DCode(str);
                    iv.setImageBitmap(bitmap);
                } catch (WriterException e) {
                    e.printStackTrace();
                }
            }
        }

     

    • 二维码zxing使用-扫码
        public void click(View view) {
            Intent intent = new Intent(this, CaptureActivity.class);
            startActivityForResult(intent,rc);
        }
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            if (requestCode==rc){
                String qrcode_result = data.getStringExtra("qrcode_result");
                tv.setText(qrcode_result);
                if (qrcode_result.contains("http")){
                     /*      <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="http" />
                <data android:scheme="https" />
                <data android:scheme="about" />
                <data android:scheme="javascript" />
            </intent-filter>*/
                    Intent intent = new Intent();
                    intent.setAction("android.intent.action.VIEW");
                    intent.addCategory("android.intent.category.DEFAULT");
                    intent.addCategory("android.intent.category.BROWSABLE");
                    intent.setData(Uri.parse("http:"+qrcode_result));
                    startActivity(intent);
                }
            }
        }

     

    • 二维码zxing使用-处理扫码结果
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            if (requestCode==rc){
                String qrcode_result = data.getStringExtra("qrcode_result");
                tv.setText(qrcode_result);
                if (qrcode_result.contains("http")){
                     /*      <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="http" />
                <data android:scheme="https" />
                <data android:scheme="about" />
                <data android:scheme="javascript" />
            </intent-filter>*/
                    Intent intent = new Intent();
                    intent.setAction("android.intent.action.VIEW");
                    intent.addCategory("android.intent.category.DEFAULT");
                    intent.addCategory("android.intent.category.BROWSABLE");
                    intent.setData(Uri.parse("http:"+qrcode_result));
                    startActivity(intent);
                }
            }
        }

     

    转载于:https://www.cnblogs.com/nangongyibin/p/10391144.html

    展开全文
  • 随着微信的到来,二维码越来越火爆,随处能看到二维码,比如商城里面,肯德基,餐厅等等,对于二维码扫描我们使用的是google的开源框架Zxing,我们可以去http://code.google.com/p/zxing/下载源码和Jar包,之前我项目...
  • Zxing简明教程

    2019-07-29 21:28:04
    如何获取依赖? Grandle依赖: // https://mvnrepository.com/artifact/com.google.zxing/core compile group: 'com.google.zxing', name: '...github地址:https://github.com/zxing/zxing 如何使用? 新建一个Zxi...

    如何获取依赖?

    Grandle依赖:

    // https://mvnrepository.com/artifact/com.google.zxing/core
    compile group: 'com.google.zxing', name: 'core', version: '3.4.0'
    

    最新地址

    如何使用?

    1. 新建一个Zxing的Config类
    ZxingConfig config = new ZxingConfig();
    
    1. 配置参数
            config.setPlayBeep(true);//是否播放扫描声音 默认为true
            config.setShake(true);//是否震动  默认为true
            config.setDecodeBarCode(true);//是否扫描条形码 默认为true
            config.setReactColor(R.color.colorAccent);//设置扫描框四个角的颜色 默认为白色
            config.setFrameLineColor(R.color.colorAccent);//设置扫描框边框颜色 默认无色
            config.setScanLineColor(R.color.colorAccent);//设置扫描线的颜色 默认白色
            config.setFullScreenScan(true);//是否全屏扫描  默认为true  设为false则只会在扫描框中扫描
    
    1. 启动意图
            Intent intent = new Intent(MainActivity.this, CaptureActivity.class);
            intent.putExtra(Constant.INTENT_ZXING_CONFIG, config);
            startActivityForResult(intent, 1);
    
    1. 在onActicityResult( )方法中获取结果
    String scanResult=data.getStringExtra(Constant.CODED_CONTENT);
    

    源码学习

    如果想深入的话,可以继续学习(挖坑…)

    时序图

    在这里插入图片描述

    类图

    在这里插入图片描述

    展开全文
  • 这两天做毕业设计,需要用到二维码所以就要用到zxing,在网上查了一圈大部分都是自己部署的,篇幅很长有的年代也很久远了,就懒得看了,所以干脆自己来吧。在此记录一下。 1.先在GitHub上将demo下载下来...

    这两天做毕业设计,需要用到二维码所以就要用到zxing,在网上查了一圈大部分都是自己部署的,篇幅很长有的年代也很久远了,就懒得看了,所以干脆自己来吧。在此记录一下。

    1.先在GitHub上将demo下载下来 https://github.com/zxing/zxing

    2.下载完成后是一个压缩包,解压完成是一个zxing-master的文件,目录结构为:

    这里我们只要android和android-core两个文件就可以了。

    3.下载jar包

    http://central.maven.org/maven2/com/google/zxing/core/3.3.3/core-3.3.3.jar

    4.新建一个Android studio项目

    点击File——New——Import Moudel将下载下来的android文件导入。

    5.改错之旅正式开始

    (1).项目导入之后出现:

    这是因为不允许在manifest中设置关于sdk版本。

    将选中的代码删除即可。

    (2).删除后点击try again不会报错了,千万别开心得太早

    打开android moudel中的build gradel将sdk版本与app中的sdk版本保持一致。

    我这里编译版本使用的是28,根据你使用的来设置。

    (3).然后你就发现android模块中会出现大量的错误(可以点击打开看看,不打开是不显示的),这个时候就要用到我们刚才下载好的jar包了。

    选中android,右键单击new——directory,名字设置为libs,将下载好的jar复制到ibs中,然后再右键单击jar包,点击Add As Library。

    这样错误就会几乎全部消失了,但是在CameraConfigurationManager这个类中还是有错,显示的是找不到CameraConfigurationUtils,怎么办呢,没有这个类就添加一个呗。回到我们下载好的zxing-master这个文件中,这次我们就用到了android-core这个文件了。

    将这个类复制到android的camera下。语法这样错误就全部消失了。那么这个模块是不是就直接能使用了呢,当然不是。我们继续。
    

    (4).添加依赖

    点击这个图标

    然后点击左侧的app,然后再点击Dependencies,再点击右侧的加号,选择module dependency,选中android点击ok。

     之后又出错了,来看看吧。

    这是因为android没有被当作一个library来使用,打开build gradle,将第一行的

    apply plugin: 'com.android.application'改为 apply plugin: 'com.android.library'

    然后try again,再一次出错了,错误信息:

    这次错误就很明显了,Library项目不能设置applicationId,连我这个英语这么渣的人都能看懂,相信大家都能看懂。

    删除选中的这行代码。再点击try again就没有错误了。 改错之旅差不多了,下面先运行一下看看吧。

     6.修改app中的MainActivity的代码和布局文件。

    MianAtivity:

    import android.content.Intent;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    
    import com.google.zxing.client.android.CaptureActivity;
    
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Button button = (Button)findViewById(R.id.scanner_QRcode);
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(MainActivity.this, CaptureActivity.class);
                    startActivity(intent);
                }
            });
        }
    }

    CaptureActivity就是扫描二维码的活动。

    activity_main.layout:

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <Button
            android:id="@+id/scanner_QRcode"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="扫描二维码"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
    </android.support.constraint.ConstraintLayout>

    很简单的布局。接下来运行项目。又出错了,继续改吧。

    这是我们android这个library又出错了。打开其中一个: 

    系统提示是:Resource IDs cannot be used in a switch statement in Android library modules 

    就是在library项目中不允许使用id.既然不能用id,也就不能用switch了,只有使用if..elseif了。选中switch然后alt+enter. 点击Replace 'switch' with 'if'。把错误出错的文件都进行这样的修改。

    然后Rebuild项目。

    恭喜又出错了,真让人刺激,看看错误信息:

     似乎是两个Manifest文件中有些属性冲突了,看下面的suggestion,可以使用添加tools:replace="android:icon"来解决。如:

    在app的manifest中添加:

    我这里直接把android中的关于application的属性全部都删除了(反正只是一个library又不用他运行)。
     
    再一次rebuild,就不会出错了。点击运行按钮。看看还有没有surprise。
    果然不出所料,事情肯定不会这么顺利。
    
     

    这是因为我们没有给应用授予照相机权限的原因。因为是敏感权限所以需要动态申请权限。

    上代码:修改MainActivity.class代码:

    import android.Manifest;
    import android.content.Intent;
    import android.content.pm.PackageManager;
    import android.support.annotation.NonNull;
    import android.support.v4.app.ActivityCompat;
    import android.support.v4.content.ContextCompat;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import android.widget.Toast;
    
    import com.google.zxing.client.android.CaptureActivity;
    
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Button button = (Button)findViewById(R.id.scanner_QRcode);
            if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA)
                    != PackageManager.PERMISSION_GRANTED){
                ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CAMERA},1);
            }
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(MainActivity.this, CaptureActivity.class);
                    startActivity(intent);
                }
            });
        }
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            switch (requestCode){
                case 1:
                    if(grantResults.length>0){
                        if(grantResults[0]!=PackageManager.PERMISSION_GRANTED){
                            Toast.makeText(MainActivity.this,"必须授权才能进行",Toast.LENGTH_SHORT).show();
                            finish();
                            return;
                        }
                    }else {
                        Toast.makeText(MainActivity.this,"发生未知错误",Toast.LENGTH_SHORT).show();
                        finish();
                    }
            }
        }
    }

    授予权限之后就可以顺利使用了。

    7.一些其他的问题

    (1).我们运行项目后会发现生成两个app,名字都是条码扫描器。这是因为我们的android也生成了app,想让它不生成的话,修改代码:

    打开android中的Manifest文件。

    删除选中的这行代码。再次运行。

    (2).扫描活动是横屏的

    打开android中的Manifest文件,将Capture Activity中的screenOrientation设置为portrait。

    这样zxing就部署到一个Android项目中了。如有不足希望大家理解,同时也希望大神出来指点。

     

    展开全文
  • zxing的使用及优化

    万次阅读 多人点赞 2017-01-06 13:56:58
    zxing项目是谷歌推出的用来识别多种格式条形码的开源项目,项目地址为https://github.com/zxing/zxingzxing有多个人在维护,覆盖主流编程语言,也是目前还在维护的较受欢迎的二维码扫描开源项目之一。 zxing的项目...

    二维码介绍

    zxing项目是谷歌推出的用来识别多种格式条形码的开源项目,项目地址为https://github.com/zxing/zxing,zxing有多个人在维护,覆盖主流编程语言,也是目前还在维护的较受欢迎的二维码扫描开源项目之一。

    zxing的项目很庞大,主要的核心代码在core文件夹里面,也可以单独下载由这个文件夹打包而成的jar包,具体地址在http://mvnrepository.com/artifact/com.google.zxing/core,直接下载jar包也省去了通过maven编译的麻烦,如果喜欢折腾的,可以从https://github.com/zxing/zxing/wiki/Getting-Started-Developing获取帮助文档。


    zxing基本使用

    官方提供了zxing在Android机子上的使用例子,https://github.com/zxing/zxing/tree/master/android,作为官方的例子,zxing-android考虑了各种各样的情况,包括多种解析格式、解析得到的结果分类、长时间无活动自动销毁机制等。有时候我们需要根据自己的情况定制使用需求,因此会精简官方给的例子。在项目中,我们仅仅用来实现扫描二维码和识别图片二维码两个功能。为了实现高精度的二维码识别,在zxing原有项目的基础上,本文做了大量改进,使得二维码识别的效率有所提升。先来看看工程的项目结构。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    .
    ├── QrCodeActivity.java
    ├── camera
    │   ├── AutoFocusCallback.java
    │   ├── CameraConfigurationManager.java
    │   ├── CameraManager.java
    │   └── PreviewCallback.java
    ├── decode
    │   ├── CaptureActivityHandler.java
    │   ├── DecodeHandler.java
    │   ├── DecodeImageCallback.java
    │   ├── DecodeImageThread.java
    │   ├── DecodeManager.java
    │   ├── DecodeThread.java
    │   ├── FinishListener.java
    │   └── InactivityTimer.java
    ├── utils
    │   ├── QrUtils.java
    │   └── ScreenUtils.java
    └── view
        └── QrCodeFinderView.java
    

    源码比较简单,这里不做过多地讲解,大部分方法都有注释。主要分为几大块,

    • camera

    主要实现相机的配置和管理,相机自动聚焦功能,以及相机成像回调(通过byte[]数组返回实际的数据)。

    • decode

    图片解析相关类。通过相机扫描二维码和解析图片使用两套逻辑。前者对实时性要求比较高,后者对解析结果要求较高,因此采用不同的配置。相机扫描主要在DecodeHandler里通过串行的方式解析,图片识别主要通过线程DecodeImageThread异步调用返回回调的结果。FinishListenerInactivityTimer用来控制长时间无活动时自动销毁创建的Activity,避免耗电。

    扫描精度问题

    使用过zxing自带的二维码扫描程序来识别二维码的童鞋应该知道,zxing二维码的扫描程序很慢,而且有可能扫不出来。zxing在配置相机参数和二维码扫描程序参数的时候,配置都比较保守,兼顾了低端手机,并且兼顾了多种条形码的识别。如果说仅仅是拿zxing项目来扫描和识别二维码的话,完全可以对项目中的一些配置做精简,并针对二维码的识别做优化。

    PlanarYUVLuminanceSource

    官方的解码程序主要是下边这段代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    private void decode(byte[] data, int width, int height) {
        long start = System.currentTimeMillis();
        Result rawResult = null;
        // 构造基于平面的YUV亮度源,即包含二维码区域的数据源
        PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height);
        if (source != null) {
            // 构造二值图像比特流,使用HybridBinarizer算法解析数据源
            BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
            try {
                // 采用MultiFormatReader解析图像,可以解析多种数据格式
                rawResult = multiFormatReader.decodeWithState(bitmap);
            } catch (ReaderException re) {
                // continue
            } finally {
                multiFormatReader.reset();
            }
        }
    	···
    	// Hanlder处理解析失败或成功的结果
    	···
    }
    

    再来看看YUV亮度源是怎么构造的,在CameraManager里,首先获取预览图像的聚焦框矩形getFramingRect(),这个聚焦框的矩形大小是根据屏幕的宽高值来做计算的,官方定义了最小和最大的聚焦框大小,分别是240*2401200*675,即最多的聚焦框大小为屏幕宽高的5/8。获取屏幕的聚焦框大小后,还需要做从屏幕分辨率到相机分辨率的转换才能得到预览聚焦框的大小,这个转换在getFramingRectInPreview()里完成。这样便完成了亮度源的构造。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    
    private static final int MIN_FRAME_WIDTH = 240;
    private static final int MIN_FRAME_HEIGHT = 240;
    private static final int MAX_FRAME_WIDTH = 1200; // = 5/8 * 1920
    private static final int MAX_FRAME_HEIGHT = 675; // = 5/8 * 1080
    
    /**
     * A factory method to build the appropriate LuminanceSource object based on the format of the preview buffers, as
     * described by Camera.Parameters.
     *
     * @param data A preview frame.
     * @param width The width of the image.
     * @param height The height of the image.
     * @return A PlanarYUVLuminanceSource instance.
     */
    public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
        // 取得预览框内的矩形
        Rect rect = getFramingRectInPreview();
        if (rect == null) {
            return null;
        }
        // Go ahead and assume it's YUV rather than die.
        return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top, rect.width(), rect.height(),
            false);
    }
    
    /**
     * Like {@link #getFramingRect} but coordinates are in terms of the preview frame, not UI / screen.
     *
     * @return {@link Rect} expressing barcode scan area in terms of the preview size
     */
    public synchronized Rect getFramingRectInPreview() {
        if (framingRectInPreview == null) {
            Rect framingRect = getFramingRect();
            if (framingRect == null) {
                return null;
            }
            // 获取相机分辨率和屏幕分辨率
            Rect rect = new Rect(framingRect);
            Point cameraResolution = configManager.getCameraResolution();
            Point screenResolution = configManager.getScreenResolution();
            if (cameraResolution == null || screenResolution == null) {
                // Called early, before init even finished
                return null;
            }
            // 根据相机分辨率和屏幕分辨率的比例对屏幕中央聚焦框进行调整
            rect.left = rect.left * cameraResolution.x / screenResolution.x;
            rect.right = rect.right * cameraResolution.x / screenResolution.x;
            rect.top = rect.top * cameraResolution.y / screenResolution.y;
            rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y;
            framingRectInPreview = rect;
        }
        return framingRectInPreview;
    }
    
    /**
     * Calculates the framing rect which the UI should draw to show the user where to place the barcode. This target
     * helps with alignment as well as forces the user to hold the device far enough away to ensure the image will be in
     * focus.
     *
     * @return The rectangle to draw on screen in window coordinates.
     */
    public synchronized Rect getFramingRect() {
        if (framingRect == null) {
            if (camera == null) {
                return null;
            }
            // 获取屏幕的尺寸像素
            Point screenResolution = configManager.getScreenResolution();
            if (screenResolution == null) {
                // Called early, before init even finished
                return null;
            }
            // 根据屏幕的宽高找到最合适的矩形框宽高值
            int width = findDesiredDimensionInRange(screenResolution.x, MIN_FRAME_WIDTH, MAX_FRAME_WIDTH);
            int height = findDesiredDimensionInRange(screenResolution.y, MIN_FRAME_HEIGHT, MAX_FRAME_HEIGHT);
    
            // 取屏幕中间的,宽为width,高为height的矩形框
            int leftOffset = (screenResolution.x - width) / 2;
            int topOffset = (screenResolution.y - height) / 2;
            framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
            Log.d(TAG, "Calculated framing rect: " + framingRect);
        }
        return framingRect;
    }
    
    private static int findDesiredDimensionInRange(int resolution, int hardMin, int hardMax) {
        int dim = 5 * resolution / 8; // Target 5/8 of each dimension
        if (dim < hardMin) {
            return hardMin;
        }
        if (dim > hardMax) {
            return hardMax;
        }
        return dim;
    }
    

    这段代码并没有什么问题,也完全符合逻辑。但为什么在扫描的时候这么难扫到二维码呢,原因在于官方为了减少解码的数据,提高解码效率和速度,采用了裁剪无用区域的方式。这样会带来一定的问题,整个二维码数据需要完全放到聚焦框里才有可能被识别,并且在buildLuminanceSource(byte[],int,int)这个方法签名中,传入的byte数组便是图像的数据,并没有因为裁剪而使数据量减小,而是采用了取这个数组中的部分数据来达到裁剪的目的。对于目前CPU性能过剩的大多数智能手机来说,这种裁剪显得没有必要。如果把解码数据换成采用全幅图像数据,这样在识别的过程中便不再拘束于聚焦框,也使得二维码数据可以铺满整个屏幕。这样用户在使用程序来扫描二维码时,尽管不完全对准聚焦框,也可以识别出来。这属于一种策略上的让步,给用户造成了错觉,但提高了识别的精度。

    解决办法很简单,就是不仅仅使用聚焦框里的图像数据,而是采用全幅图像的数据。

    1
    2
    3
    4
    
    public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
        // 直接返回整幅图像的数据,而不计算聚焦框大小。
        return new PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height, false);
    }
    

    DecodeHintType

    在使用zxing解析二维码时,允许事先进行相关配置,这个文件通过Map<DecodeHintType, ?>键值对来保存,然后使用方法public void setHints(Map<DecodeHintType,?> hints)来设置到相应的解码器中。DecodeHintType是一个枚举类,其中有几个重要的枚举值,

    • POSSIBLE_FORMATS(List.class)

    用于列举支持的解析格式,一共有17种,在com.google.zxing.BarcodeFormat里定义。官方默认支持所有的格式。

    • TRY_HARDER(Void.class)

    是否使用HARDER模式来解析数据,如果启用,则会花费更多的时间去解析二维码,对精度有优化,对速度则没有。

    • CHARACTER_SET(String.class)

    解析的字符集。这个对解析也比较关键,最好定义需要解析数据对应的字符集。

    如果项目仅仅用来解析二维码,完全没必要支持所有的格式,也没有必要使用MultiFormatReader来解析。所以在配置的过程中,我移除了所有与二维码不相关的代码。直接使用QRCodeReader类来解析,字符集采用utf-8,使用Harder模式,并且把可能的解析格式只定义为BarcodeFormat.QR_CODE,这对于直接二维码扫描解析无疑是帮助最大的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    private final Map<DecodeHintType, Object> mHints;
    DecodeHandler(QrCodeActivity activity) {
        this.mActivity = activity;
        mQrCodeReader = new QRCodeReader();
        mHints = new Hashtable<>();
        mHints.put(DecodeHintType.CHARACTER_SET, "utf-8");
        mHints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
        mHints.put(DecodeHintType.POSSIBLE_FORMATS, BarcodeFormat.QR_CODE);
    }
    

    二维码图像识别精度探究

    图像/像素编码格式

    Android相机预览的时候支持几种不同的格式,从图像的角度(ImageFormat)来说有NV16、NV21、YUY2、YV12、RGB_565和JPEG,从像素的角度(PixelFormat)来说,有YUV422SP、YUV420SP、YUV422I、YUV420P、RGB565和JPEG,它们之间的对应关系可以从Camera.Parameters.cameraFormatForPixelFormat(int)方法中得到。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    private String cameraFormatForPixelFormat(int pixel_format) {
        switch(pixel_format) {
        case ImageFormat.NV16:      return PIXEL_FORMAT_YUV422SP;
        case ImageFormat.NV21:      return PIXEL_FORMAT_YUV420SP;
        case ImageFormat.YUY2:      return PIXEL_FORMAT_YUV422I;
        case ImageFormat.YV12:      return PIXEL_FORMAT_YUV420P;
        case ImageFormat.RGB_565:   return PIXEL_FORMAT_RGB565;
        case ImageFormat.JPEG:      return PIXEL_FORMAT_JPEG;
        default:                    return null;
        }
    }
    

    目前大部分Android手机摄像头设置的默认格式是yuv420sp,其原理可参考文章《图文详解YUV420数据格式》。编码成YUV的所有像素格式里,yuv420sp占用的空间是最小的。既然如此,zxing当然会考虑到这种情况。因此针对YUV编码的数据,有PlanarYUVLuminanceSource这个类去处理,而针对RGB编码的数据,则使用RGBLuminanceSource去处理。在下节介绍的图像识别算法中我们可以知道,大部分二维码的识别都是基于二值化的方法,在色域的处理上,YUV的二值化效果要优于RGB,并且RGB图像在处理中不支持旋转。因此,一种优化的思路是讲所有ARGB编码的图像转换成YUV编码,再使用PlanarYUVLuminanceSource去处理生成的结果。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    
    /**
     * RGB转YUV420sp
     *
     * @param yuv420sp inputWidth * inputHeight * 3 / 2
     * @param argb inputWidth * inputHeight
     * @param width image width
     * @param height image height
     */
    private static void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width, int height) {
        // 帧图片的像素大小
        final int frameSize = width * height;
        // ---YUV数据---
        int Y, U, V;
        // Y的index从0开始
        int yIndex = 0;
        // UV的index从frameSize开始
        int uvIndex = frameSize;
    
        // ---颜色数据---
        int R, G, B;
        int rgbIndex = 0;
    
        // ---循环所有像素点,RGB转YUV---
        for (int j = 0; j < height; j++) {
            for (int i = 0; i < width; i++) {
    
                R = (argb[rgbIndex] & 0xff0000) >> 16;
                G = (argb[rgbIndex] & 0xff00) >> 8;
                B = (argb[rgbIndex] & 0xff);
                //
                rgbIndex++;
    
                // well known RGB to YUV algorithm
                Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
                U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
                V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;
    
                Y = Math.max(0, Math.min(Y, 255));
                U = Math.max(0, Math.min(U, 255));
                V = Math.max(0, Math.min(V, 255));
    
                // NV21 has a plane of Y and interleaved planes of VU each sampled by a factor of 2
                // meaning for every 4 Y pixels there are 1 V and 1 U. Note the sampling is every other
                // pixel AND every other scan line.
                // ---Y---
                yuv420sp[yIndex++] = (byte) Y;
                // ---UV---
                if ((j % 2 == 0) && (i % 2 == 0)) {
                    //
                    yuv420sp[uvIndex++] = (byte) V;
                    //
                    yuv420sp[uvIndex++] = (byte) U;
                }
            }
        }
    }
    

    Android中读取一张图片一般是通过BitmapFactory.decodeFile(imgPath, options)这个方法去得到这张图片的Bitmap数据,Bitmap是由ARGB值编码得到的,因此如果需要转换成YUV,还需要做一点小小的变换。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
    private static byte[] yuvs;
    /**
     * 根据Bitmap的ARGB值生成YUV420SP数据。
     *
     * @param inputWidth image width
     * @param inputHeight image height
     * @param scaled bmp
     * @return YUV420SP数组
     */
    public static byte[] getYUV420sp(int inputWidth, int inputHeight, Bitmap scaled) {
        int[] argb = new int[inputWidth * inputHeight];
    
        scaled.getPixels(argb, 0, inputWidth, 0, 0, inputWidth, inputHeight);
    
        /**
         * 需要转换成偶数的像素点,否则编码YUV420的时候有可能导致分配的空间大小不够而溢出。
         */
        int requiredWidth = inputWidth % 2 == 0 ? inputWidth : inputWidth + 1;
        int requiredHeight = inputHeight % 2 == 0 ? inputHeight : inputHeight + 1;
    
        int byteLength = requiredWidth * requiredHeight * 3 / 2;
        if (yuvs == null || yuvs.length < byteLength) {
            yuvs = new byte[byteLength];
        } else {
            Arrays.fill(yuvs, (byte) 0);
        }
    
        encodeYUV420SP(yuvs, argb, inputWidth, inputHeight);
    
        scaled.recycle();
    
        return yuvs;
    }
    

    这里面有几个坑,在方法里已经列出来了。首先,如果每次都生成新的YUV数组,不知道在扫一扫解码时要进行GC多少次。。。所以就采用了静态的数组变量来存储数据,只有当当前的长宽乘积超过数组大小时,才重新生成新的yuvs。其次,如果鉴于YUV的特性,长宽只能是偶数个像素点,否则可能会造成数组溢出(不信可以尝试)。最后,使用完了Bitmap要记得回收,那玩意吃内存不是随便说说的。

    二维码图像识别算法选择

    二维码扫描精度和许多因素有关,最关键的因素是扫描算法。目前在图形识别领域中,较常用的二维码识别算法主要有两种,分别是HybridBinarizerGlobalHistogramBinarizer,这两种算法都是基于二值化,即将图片的色域变为黑白两个颜色,然后提取图形中的二维码矩阵。实际上,zxing中的HybridBinarizer继承自GlobalHistogramBinarizer,并在此基础上做了功能性的改进。援引官方介绍:

    This Binarizer(GlobalHistogramBinarizer) implementation uses the old ZXing global histogram approach. It is suitable for low-end mobile devices which don’t have enough CPU or memory to use a local thresholding algorithm. However, because it picks a global black point, it cannot handle difficult shadows and gradients. Faster mobile devices and all desktop applications should probably use HybridBinarizer instead.

    This class(HybridBinarizer) implements a local thresholding algorithm, which while slower than the GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for high frequency images of barcodes with black data on white backgrounds. For this application, it does a much better job than a global blackpoint with severe shadows and gradients. However it tends to produce artifacts on lower frequency images and is therefore not a good general purpose binarizer for uses outside ZXing. This class extends GlobalHistogramBinarizer, using the older histogram approach for 1D readers, and the newer local approach for 2D readers. 1D decoding using a per-row histogram is already inherently local, and only fails for horizontal gradients. We can revisit that problem later, but for now it was not a win to use local blocks for 1D. ···

    GlobalHistogramBinarizer算法适合于低端的设备,对手机的CPU和内存要求不高。但它选择了全部的黑点来计算,因此无法处理阴影和渐变这两种情况。HybridBinarizer算法在执行效率上要慢于GlobalHistogramBinarizer算法,但识别相对更有效。它专门为以白色为背景的连续黑色块二维码图像解析而设计,也更适合用来解析具有严重阴影和渐变的二维码图像。

    网上对这两种算法的解析并不多,目前仅找到一篇文章详解了GlobalHistogramBinarizer算法,详见[http://kuangjianwei.blog.163.com/blog/static/190088953201361015055110/]()。有时间再看一下相关源码。

    zxing项目官方默认使用的是HybridBinarizer二值化方法。在实际的测试中,和官方的介绍大致一样。然而目前的大部分二维码都是黑色二维码,白色背景的。不管是二维码扫描还是二维码图像识别,使用GlobalHistogramBinarizer算法的效果要稍微比HybridBinarizer好一些,识别的速度更快,对低分辨的图像识别精度更高。

    除了这两种算法,我相信在图像识别领域肯定还有更好的算法存在,目前受限于知识水平,对二值化算法这一块还比较陌生,期待以后能够深入理解并改进目前的开源算法(*^__^*)……

    图像大小对识别精度的影响

    这点是测试中无意发现的。现在的手机摄像头拍照出现的照片像素都很高,动不动就1200W像素,1600W像素,甚至是2000W都不稀奇,但照片的成像质量不一定高。将一张高分辨率的图片按原分辨率导入Android手机,很容易产生OOM。我们来计算一下,导入一张1200W像素的图片需要的内存,假设图片是4000px*3000px,如果导入的图片采用ARGB_8888编码形式,则每个像素需要占用4个Bytes(分别存储ARGB值)来存储,则需要4000*3000*4bytes=45.776MB的内存,这在有限的移动资源里,显然是不能忍受的。

    通过上一节对图像算法的简单研究,在GlobalHistogramBinarizer中,是从图像中均匀取5行(覆盖整个图像高度),每行取中间五分之四作为样本;以灰度值为X轴,每个灰度值的像素个数为Y轴建立一个直方图,从直方图中取点数最多的一个灰度值,然后再去给其他的灰度值进行分数计算,按照点数乘以与最多点数灰度值的距离的平方来进行打分,选分数最高的一个灰度值。接下来在这两个灰度值中间选取一个区分界限,取的原则是尽量靠近中间并且要点数越少越好。界限有了以后就容易了,与整幅图像的每个点进行比较,如果灰度值比界限小的就是黑,在新的矩阵中将该点置1,其余的就是白,为0。(摘自zxing源码分析——QR码部分

    根据算法的实现,可以知道图像的分辨率对二维码的取值是有影响的。并不是图像的分辨率越高就越容易取到二维码。高分辨率的图像对Android的内存资源占用也很可怕。所以在测试的过程中,我尝试将图片压缩成不同大小分辨率,然后再进行图片的二维码识别。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    
    /**
     * 根据给定的宽度和高度动态计算图片压缩比率
     * 
     * @param options Bitmap配置文件
     * @param reqWidth 需要压缩到的宽度
     * @param reqHeight 需要压缩到的高度
     * @return 压缩比
     */
    public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
    
        if (height > reqHeight || width > reqWidth) {
    
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;
    
            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }
    
        return inSampleSize;
    }
    
    /**
     * 将图片根据压缩比压缩成固定宽高的Bitmap,实际解析的图片大小可能和#reqWidth、#reqHeight不一样。
     * 
     * @param imgPath 图片地址
     * @param reqWidth 需要压缩到的宽度
     * @param reqHeight 需要压缩到的高度
     * @return Bitmap
     */
    public static Bitmap decodeSampledBitmapFromFile(String imgPath, int reqWidth, int reqHeight) {
    
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(imgPath, options);
    
        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    
        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(imgPath, options);
    }
    

    Android图片优化需要通过在解析图片的时候,设置BitmapFactory.Options.inSampleSize的值,根据比例压缩图片大小。在进行图片二维码解析的线程中,通过设置不同的图片大小,来测试二维码的识别率。这个测试过程我忘记保存了,只记得测试了压缩成最大宽高值为2048、1024、512、256和128像素的包含二维码的图片,但实际的测试结果是,当MAX_PICTURE_PIXEL=256的时候识别率最高。

    此结论不具备理论支持,有兴趣的童鞋可以自己动手尝试。^_^

    相机预览倍数设置及聚焦时间调整

    如果使用zxing默认的相机配置,会发现需要离二维码很近才能够识别出来,但这样会带来一个问题——聚焦困难。解决办法就是调整相机预览倍数以及减小相机聚焦的时间。

    通过测试可以发现,每个手机的最大放大倍数几乎是不一样的,这可能和摄像头的型号有关。如果设置成一个固定的值,那可能会产生在某些手机上过度放大,某些手机上放大的倍数不够。索性相机的参数设定里给我们提供了最大的放大倍数值,通过取放大倍数值的N分之一作为当前的放大倍数,就完美地解决了手机的适配问题。

    1
    2
    3
    4
    5
    6
    
    // 需要判断摄像头是否支持缩放
    Parameters parameters = camera.getParameters();
    if (parameters.isZoomSupported()) {
    	// 设置成最大倍数的1/10,基本符合远近需求
        parameters.setZoom(parameters.getMaxZoom() / 10);
    }
    

    zxing默认的相机聚焦时间是2s,可以根据扫描的视觉适当调整。聚焦时间的调整也很简单,在AutoFocusCallback这个类里,调整AUTO_FOCUS_INTERVAL_MS这个值就可以了。

    二维码扫描视觉调整

    二维码扫描视觉的绘制在ViewfinderView.java完成,官方是继承了View然后在onDraw()方法中实现了视图的绘制。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    
    @Override
    public void onDraw(Canvas canvas) {
        if (cameraManager == null) {
            return; // not ready yet, early draw before done configuring
        }
        Rect frame = cameraManager.getFramingRect();
        Rect previewFrame = cameraManager.getFramingRectInPreview();
        if (frame == null || previewFrame == null) {
            return;
        }
        int width = canvas.getWidth();
        int height = canvas.getHeight();
    
        // 绘制聚焦框外的暗色透明层
        paint.setColor(resultBitmap != null ? resultColor : maskColor);
        canvas.drawRect(0, 0, width, frame.top, paint);
        canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
        canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint);
        canvas.drawRect(0, frame.bottom + 1, width, height, paint);
    
        if (resultBitmap != null) {
            // 如果扫描结果不为空,则把扫描的结果填充到聚焦框中
            paint.setAlpha(CURRENT_POINT_OPACITY);
            canvas.drawBitmap(resultBitmap, null, frame, paint);
        } else {
    
            // 画一根红色的激光线表示二维码解码正在进行
            paint.setColor(laserColor);
            paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
            scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
            int middle = frame.height() / 2 + frame.top;
            canvas.drawRect(frame.left + 2, middle - 1, frame.right - 1, middle + 2, paint);
    
            float scaleX = frame.width() / (float) previewFrame.width();
            float scaleY = frame.height() / (float) previewFrame.height();
    
            List<ResultPoint> currentPossible = possibleResultPoints;
            List<ResultPoint> currentLast = lastPossibleResultPoints;
            int frameLeft = frame.left;
            int frameTop = frame.top;
    
            // 绘制解析过程中可能扫描到的关键点,使用黄色小圆点表示
            if (currentPossible.isEmpty()) {
                lastPossibleResultPoints = null;
            } else {
                possibleResultPoints = new ArrayList<>(5);
                lastPossibleResultPoints = currentPossible;
                paint.setAlpha(CURRENT_POINT_OPACITY);
                paint.setColor(resultPointColor);
                synchronized (currentPossible) {
                    for (ResultPoint point : currentPossible) {
                        canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
                            frameTop + (int) (point.getY() * scaleY), POINT_SIZE, paint);
                    }
                }
            }
            if (currentLast != null) {
                paint.setAlpha(CURRENT_POINT_OPACITY / 2);
                paint.setColor(resultPointColor);
                synchronized (currentLast) {
                    float radius = POINT_SIZE / 2.0f;
                    for (ResultPoint point : currentLast) {
                        canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
                            frameTop + (int) (point.getY() * scaleY), radius, paint);
                    }
                }
            }
            // 重绘聚焦框里的内容,不需要重绘整个界面。
            postInvalidateDelayed(ANIMATION_DELAY, frame.left - POINT_SIZE, frame.top - POINT_SIZE,
                frame.right + POINT_SIZE, frame.bottom + POINT_SIZE);
        }
    }
    

    我给它做了一点小改变,效果差不多,代码更简洁一些。由于代码中我不是根据屏幕的宽高动态计算聚焦框的大小,因此这里省去了从CameraManager获取FramingRect和FramingRectInPreview这两个矩形的过程。我在聚焦框外加了四个角,目前大部分二维码产品基本都是这么设计的吧,当然也可以使用图片来代替。总之视觉定制是因人而异,这里不做过多介绍。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    
    @Override
    public void onDraw(Canvas canvas) {
        if (isInEditMode()) {
            return;
        }
        Rect frame = mFrameRect;
        if (frame == null) {
            return;
        }
        int width = canvas.getWidth();
        int height = canvas.getHeight();
    
        // 绘制焦点框外边的暗色背景
        mPaint.setColor(mMaskColor);
        canvas.drawRect(0, 0, width, frame.top, mPaint);
        canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, mPaint);
        canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, mPaint);
        canvas.drawRect(0, frame.bottom + 1, width, height, mPaint);
    
        drawFocusRect(canvas, frame);
        drawAngle(canvas, frame);
        drawText(canvas, frame);
        drawLaser(canvas, frame);
    
        // Request another update at the animation interval, but only repaint the laser line,
        // not the entire viewfinder mask.
        postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top, frame.right, frame.bottom);
    }
    
    /**
     * 画聚焦框,白色的
     * 
     * @param canvas
     * @param rect
     */
    private void drawFocusRect(Canvas canvas, Rect rect) {
        // 绘制焦点框(黑色)
        mPaint.setColor(mFrameColor);
        // 上
        canvas.drawRect(rect.left + mAngleLength, rect.top, rect.right - mAngleLength, rect.top + mFocusThick, mPaint);
        // 左
        canvas.drawRect(rect.left, rect.top + mAngleLength, rect.left + mFocusThick, rect.bottom - mAngleLength,
            mPaint);
        // 右
        canvas.drawRect(rect.right - mFocusThick, rect.top + mAngleLength, rect.right, rect.bottom - mAngleLength,
            mPaint);
        // 下
        canvas.drawRect(rect.left + mAngleLength, rect.bottom - mFocusThick, rect.right - mAngleLength, rect.bottom,
            mPaint);
    }
    
    /**
     * 画粉色的四个角
     * 
     * @param canvas
     * @param rect
     */
    private void drawAngle(Canvas canvas, Rect rect) {
        mPaint.setColor(mLaserColor);
        mPaint.setAlpha(OPAQUE);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(mAngleThick);
        int left = rect.left;
        int top = rect.top;
        int right = rect.right;
        int bottom = rect.bottom;
        // 左上角
        canvas.drawRect(left, top, left + mAngleLength, top + mAngleThick, mPaint);
        canvas.drawRect(left, top, left + mAngleThick, top + mAngleLength, mPaint);
        // 右上角
        canvas.drawRect(right - mAngleLength, top, right, top + mAngleThick, mPaint);
        canvas.drawRect(right - mAngleThick, top, right, top + mAngleLength, mPaint);
        // 左下角
        canvas.drawRect(left, bottom - mAngleLength, left + mAngleThick, bottom, mPaint);
        canvas.drawRect(left, bottom - mAngleThick, left + mAngleLength, bottom, mPaint);
        // 右下角
        canvas.drawRect(right - mAngleLength, bottom - mAngleThick, right, bottom, mPaint);
        canvas.drawRect(right - mAngleThick, bottom - mAngleLength, right, bottom, mPaint);
    }
    
    private void drawText(Canvas canvas, Rect rect) {
        int margin = 40;
        mPaint.setColor(mTextColor);
        mPaint.setTextSize(getResources().getDimension(R.dimen.text_size_13sp));
        String text = getResources().getString(R.string.qr_code_auto_scan_notification);
        Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
        float fontTotalHeight = fontMetrics.bottom - fontMetrics.top;
        float offY = fontTotalHeight / 2 - fontMetrics.bottom;
        float newY = rect.bottom + margin + offY;
        float left = (ScreenUtils.getScreenWidth(mContext) - mPaint.getTextSize() * text.length()) / 2;
        canvas.drawText(text, left, newY, mPaint);
    }
    
    private void drawLaser(Canvas canvas, Rect rect) {
        // 绘制焦点框内固定的一条扫描线(红色)
        mPaint.setColor(mLaserColor);
        mPaint.setAlpha(SCANNER_ALPHA[mScannerAlpha]);
        mScannerAlpha = (mScannerAlpha + 1) % SCANNER_ALPHA.length;
        int middle = rect.height() / 2 + rect.top;
        canvas.drawRect(rect.left + 2, middle - 1, rect.right - 1, middle + 2, mPaint);
    
    }
    

    总结

    使用zxing进行二维码的编解码是非常方便的,zxing的API覆盖了多种主流编程语言,具有良好的扩展性和可定制性。文中进行了二维码基本功能介绍,zxing项目基本使用方法,zxing项目中目前存在的缺点及改进方案,以及自己在进行zxing项目二次开发的摸索过程中总结出的提高二维码扫描的方法。文中还有许多不足的地方,对源码的理解还不够深,特别是二维码解析关键算法(GlobalHistogramBinarizerHybridBinarizer)。这些算法需要投入额外的时间去理解,对于目前以业务为导向的App开发来说,还存在优化的空间,期待将来有一天能像微信的二维码扫描一样快速,精确。

    转自:http://iluhcm.com/2016/01/08/scan-qr-code-and-recognize-it-from-picture-fastly-using-zxing/
    展开全文
  • zxing-3.2.1_java_Android

    2020-07-19 23:33:58
    zxing 自己项目里使用的, 可以正常使用
  • ZXing的使用

    千次阅读 2016-07-03 15:59:29
    本文转自:这里呀 感谢原文博主的无私分享!...我们通常使用的开源框架是zxing。在github上的开源地址:https://github.com/zxing/zxing,目前在做的项目中也用到这个框架, 所以自己做了个demo,方便学习及下次使用。
  • Android ZXing 解析

    万次阅读 热门讨论 2018-09-05 21:54:47
    参考: http://blog.csdn.net/eclipsexys/article/details/47834865 ...扫描截取界面的计算ZXingZXing作者的github地址: https://github.com/zxing/zxing通过git clone git@git
  • zxing-zxing-3.2.0 下载 官方绿色版最新jar包

    千次下载 热门讨论 2020-07-30 23:33:23
    zxing-zxing-3.2.0 最新版本jar包下载
  • zxing

    2019-07-31 10:25:58
    zxing 项目地址:yuzhiqiang1993/zxing 简介:基于 zxing 的扫一扫,优化了扫描二维码速度,集成最新版本的 jar 包(zxing-core.jar 3.3.3),集成简单,速度快,可配置颜色,还有闪光灯,解析二维码图片,生成...
  • ZXing框架介绍

    千次阅读 2016-10-23 09:28:10
    引言: 二维码的普及速度令人咋舌,现在随处的实体广告在边角处都附上一张二维码,提示用户扫描相关信息,了解更多,并且张小龙说,PC的入口在于搜索框,而手机的入口在于二维码. 因为二维码解决了手机输入信息的困难....
  • 项目中有需求使用zxing实现二维码扫描,但是不想使用UI组件,所以将zxingcore重新编译成jar,可引用到项目里,把yuv数据送入扫描接口,实现二维码扫描。 一、在项目里引入zxingcore.jar(以Android studio工程为例...
  • Google zxing实现二维码扫描完美解决方案

    万次阅读 多人点赞 2016-08-24 16:22:38
    网上找了很多资料,最后决定使用Google的zxing来实现。实现的过程遇到了很多的坑,也是因为这些坑在网上没有具体的解决方案,今天就把我的实现过程分享给大家。 我会分为两步来和大家分享: (1)项目中如何集成...
  • 之前我写过一篇博客是关于如何将zxing集成到Android Studio中,以及简单的实现扫一扫功能。 详情请看:Android Studio集成Zxing扫一扫 但是,上面那篇博客只有有一个扫一扫功能,而且在低分辨率手机上会出现变形的...
  • unityZXing二维码的生成与扫描

    万次阅读 多人点赞 2019-01-07 10:50:02
    借鉴自某位大佬不记得了 using System.Collections;...using ZXing; using UnityEngine.UI; /// &lt;summary&gt; /// 二维码扫描识别功能 /// &lt;/summary&gt; public class Test...
  • Android二维码功能实现,在程序内嵌入ZXing项目

    万次阅读 多人点赞 2016-10-19 11:31:40
    最近二维码真是越来越火了,随便电视上、网络上、商场里,到处都是二维码。而内嵌二维码扫描功能的软件也越来越多...目前在二维码这一领域名气最大的开源项目就是ZXing了(Zebra Crossing),它提供了多个平台的二维码扫
  • 如何在visual studio下编译zxing cpp,以及zxing c++的使用

    万次阅读 热门讨论 2018-09-12 20:21:30
    刚开始请允许我发一下牢骚,刚开始学c++,然后,在使用zxing c++解析二维码的时候,需要找资料。但是,我所找的资料都有问题,下载zxing cpp之后,编译的时候各种问题,当时头都大了。而且,网上关于zxing c++二维码...
  • Android实战——Zxing实现二维码扫描

    万次阅读 多人点赞 2016-10-02 21:36:51
    教你用Zxing实现二维码扫描 前言: 本篇文章从初学者的角度出发,从一个不知道对二维码扫描怎么下手的工作者,需要一个简单的扫描功能的话,可以阅读该篇文章。作为Google开源框架Zxing,里面的文件很大,这里主要...
  • zxing开源库的基本使用

    千次阅读 2018-10-25 20:39:36
    如果你的项目中有模块跟二维码相关的话,那你一定听过或者用过大名鼎鼎的zxing开源库。 什么是zxingZXing是一个开源的,用Java实现的多种格式的1D/2D条码图像处理库,它包含了联系到其他语言的端口。zxing可以...
  • ZXing是一个开放源码的,用Java实现的多种格式的1D/2D条码图像处理库。 ZXing.Net是基于ZXing的.NET平台下关于条形码和二维码的工具,使用非常方便。 ZXing.Net is a port of ZXing, an open-source, multi-format ...
1 2 3 4 5 ... 20
收藏数 16,313
精华内容 6,525
关键字:

zxing