当前位置: 首页 > news >正文

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

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 音视频入门项目

觉得不错,点个在看呗~

相关文章:

  • 渐变过渡的相册(shader)
  • 【C++11新特性】 C++11 智能指针之shared_ptr
  • 【C++11新特性】 C++11智能指针之weak_ptr
  • 5 个 IDEA 必备插件,让效率成为习惯
  • 【C++11新特性】 C++11智能指针之 unique_ptr
  • 关于JVM,你必须知道的那些玩意儿
  • OpenGL ES 实现动态(水波纹)涟漪效果
  • 盘点Android常用Hook技术
  • 推荐一款强大的 Android OpenGL ES 调试工具
  • MediaCodec 解码后数据对齐导致的绿边问题
  • JNI编程如何巧妙获取JNIEnv
  • 最新 Android 面试点梳理,我收藏了你呢?
  • 三年Android开发,跳槽腾讯音乐,历经三面终获Offer,定级T2-1(超全面试题+学习经验总结)...
  • 「Android音视频编码那点破事」序章
  • Android短文:理解插值器和估值器
  • (三)从jvm层面了解线程的启动和停止
  • 【399天】跃迁之路——程序员高效学习方法论探索系列(实验阶段156-2018.03.11)...
  • 【React系列】如何构建React应用程序
  • 【笔记】你不知道的JS读书笔记——Promise
  • canvas绘制圆角头像
  • ES2017异步函数现已正式可用
  • log4j2输出到kafka
  • nodejs调试方法
  • SegmentFault 2015 Top Rank
  • 编写高质量JavaScript代码之并发
  • 湖南卫视:中国白领因网络偷菜成当代最寂寞的人?
  • 判断客户端类型,Android,iOS,PC
  • 浅谈Golang中select的用法
  • 如何在GitHub上创建个人博客
  • 算法-图和图算法
  • 译自由幺半群
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • #Lua:Lua调用C++生成的DLL库
  • #我与Java虚拟机的故事#连载19:等我技术变强了,我会去看你的 ​
  • (02)Hive SQL编译成MapReduce任务的过程
  • (1)SpringCloud 整合Python
  • (js)循环条件满足时终止循环
  • (板子)A* astar算法,AcWing第k短路+八数码 带注释
  • (附源码)ssm高校社团管理系统 毕业设计 234162
  • (附源码)SSM环卫人员管理平台 计算机毕设36412
  • (附源码)计算机毕业设计高校学生选课系统
  • (完整代码)R语言中利用SVM-RFE机器学习算法筛选关键因子
  • (一)Thymeleaf用法——Thymeleaf简介
  • ***检测工具之RKHunter AIDE
  • *setTimeout实现text输入在用户停顿时才调用事件!*
  • .equals()到底是什么意思?
  • .NET Framework 4.6.2改进了WPF和安全性
  • .NET 将混合了多个不同平台(Windows Mac Linux)的文件 目录的路径格式化成同一个平台下的路径
  • .Net(C#)常用转换byte转uint32、byte转float等
  • .net2005怎么读string形的xml,不是xml文件。
  • .net实现客户区延伸至至非客户区
  • /dev/VolGroup00/LogVol00:unexpected inconsistency;run fsck manually
  • ??myeclipse+tomcat
  • @Async注解的坑,小心
  • @transaction 提交事务_【读源码】剖析TCCTransaction事务提交实现细节