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 實現高幀率預覽錄製(附源碼)
覺得不錯,點個在看唄~