自定義相機中如何實現二維碼掃描功能

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 音視頻入門項目

覺得不錯,點個在看唄~

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