自定义相机中如何实现二维码扫描功能

Android平台中要实现二维码扫描功能的话,最常用的开源库要推zxing和zbar了。不过zbar已经好几年没有更新了,而zxing由Google开源并持续维护,所以本文就选择采用zxing来实现二维码扫描功能。

依赖

在zxing的github主页上查看接入指南,发现只有maven的依赖导入

<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>(the current version)</version>
</dependency>

在Android工程中一般都是通过gradle管理依赖,所以根据maven和gradle的依赖管理规则对应关系,我们通过如下方式导入最新的库:

implementation "com.google.zxing:core:3.4.0"

因为墙的原因,依赖库可能下载不下来,我们可以从zxing的github主页中将core这个目录copy到自己的工程中,也可以去下载core.jar包

非相机应用

非相机app中要引入二维码扫描功能的话,zxing的使用是非常简单的,Google已经做了很完善的封装。除了core库的引入外,我们只需要将android目录copy到自己的工程,或者根据自己的需求单独引入android目录下的代码文件和资源文件

在需要打开扫描界面的地方直接跳转到CaptureActivity

Intent intent = new Intent(MainActivity.this, CaptureActivity.class);
startActivityForResult(intent, REQUEST_CODE_SCAN);

在onActivityResult的回调中即可获取扫描内容

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_SCAN && resultCode == RESULT_OK) {
        if (data != null) {
            //返回的文本内容
            String content = data.getStringExtra(DECODED_CONTENT_KEY);
            //返回的Bitmap图像
            Bitmap bitmap = data.getParcelableExtra(DECODED_BITMAP_KEY);
        }
    }
}

当然还需要动态申请相机权限,注册activity等

自定义相机

在自定义相机中,我们如何通过zxing实现二维码扫描功能呢?

流程分析

我们先参考一下官方的封装,看看整个流程是如何实现的。

在android/camera目录下看到,关于camera的封装采用的是camera1的api,拿到每一帧的预览数据后发送到解码线程去做识别。识别二维码核心代码为DecodeHandler#decode方法:

  /**
   * Decode the data within the viewfinder rectangle, and time how long it took. For efficiency,
   * reuse the same reader objects from one decode to the next.
   *
   * @param data   The YUV preview frame.
   * @param width  The width of the preview frame.
   * @param height The height of the preview frame.
   */
  private void decode(byte[] data, int width, int height) {
      // ...省略部分代码
      PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height);
      if (source != null) {
          BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
          try {
              rawResult = multiFormatReader.decodeWithState(bitmap);
          } catch (ReaderException re) {
              // continue
          } finally {
              multiFormatReader.reset();
          }
       }
      // ...省略部分代码
  }

可以看到识别过程主要分为四个步骤:

第一步,构建Source,将数据源转为灰度图;

// 通过YUV进行二维码识别使用PlanarYUVLuminanceSource,
// 可以传一个Rect进行裁剪,对裁剪区进行识别以提高速度
PlanarYUVLuminanceSource(yuvData, width, height, top, left, width, height, false)


// 通过RGB进行二维码识别的话使用RGBLuminanceSource,
// 这个类在构造方法中将RGB转为灰度图
luminances[offset] = (byte) ((r + g2 + b) / 4);

第二步,选择识别算法;

目前在图形识别领域中,较常用的二维码识别算法主要有两种:

  • GlobalHistogramBinarizer:适合于低端的设备,对手机的CPU和内存要求不高。它选择了全部的黑点来计算,因此无法处理阴影和渐变这两种情况;

  • HybridBinarizer:在执行效率上要慢于GlobalHistogramBinarizer算法,但识别相对更有效。它专门为以白色为背景的连续黑色块二维码图像解析而设计,也更适合用来解析具有严重阴影和渐变的二维码图像。

这两种算法都是基于二化,即将图片的色域变为黑白两个颜色,然后提取图形中的二维码矩阵。

zxing中的HybridBinarizer继承自GlobalHistogramBinarizer,并在此基础上做了一些改进;

第三步,将二维码矩阵转为位图;

第四步,识别

以上的流程梳理清楚了,要在自定义相机中实现二维码功能就很简单了。

自定义扫描View

扫描UI主要需要绘制三个部分:半透明背景,扫描框和扫描条。

扫描框一般都是和相机预览界面居中对齐,如果我们需要在扫描框内做二维码识别的话,就需要根据扫描框的位置对预览YUV进行裁剪,为了方便映射UI和预览区域进行计算,就偷懒啦,所以我们的ScannerView会根据previewSize重新测量宽高,最后的效果如下(扫描条没截到,就这样吧。。。):

启动预览

打开Camera,启动预览的步骤参考Android Camera2详解

获取预览YUV数据

Camera2中获取预览YUV数据参考Android Camera2中如何获取预览YUV数据

二维码扫描
将每一帧预览数据按照之前分析的四个步骤进行就ok了,

核心代码:

val yuvData = ByteArray(width * height * 3 / 2)
CommonUtil.readYuvDataToBuffer(image, ImageFormat.NV21, yuvData)
image.close()
 
val frameRect = qrScannerView.getFrameRect()
// 此处需要注意的是,预览YUV数据是横屏的,UI是竖屏的
// 所以在扫描框和预览区域居中对齐的时候,
// 裁剪区域的left,top参数为扫描框rect的top,left
val planarYUVLuminanceSource = PlanarYUVLuminanceSource(yuvData, width, height,
                frameRect.top, frameRect.left, frameRect.width(), frameRect.height(),
                false)
val binaryBitmap = BinaryBitmap(HybridBinarizer(planarYUVLuminanceSource))
var decodeResult: Result? = null
try {
     decodeResult = multiFormatReader?.decodeWithState(binaryBitmap)
 } catch (ex: ReaderException) {
     // not found, throw ReaderException
 } finally {
     multiFormatReader?.reset()
 }
Log.d(TAG, "decode result = ${decodeResult?.text}")

如果想要拿到识别到的二维码图片的话,通过Source对象获取:

val pixels = planarYUVLuminanceSource.renderThumbnail()
val thumbnailWidth = planarYUVLuminanceSource.thumbnailWidth
val thumbnailHeight = planarYUVLuminanceSource.thumbnailHeight
val bitmap = Bitmap.createBitmap(pixels, 0, thumbnailWidth, 
              thumbnailWidth, thumbnailHeight, Bitmap.Config.ARGB_8888)

DEMO

传送门: 

https://github.com/sifutang/Camera2BasicKotlin.git

推荐阅读:

Android Camera2 实现高帧率预览录制(附源码)

移动端技术交流喊你入群啦~~~

推荐几个堪称教科书级别的 Android 音视频入门项目

觉得不错,点个在看呗~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章