分爲兩步
一.獲取圖像數據
二.利用zxing分析圖像數據
下面先看下識別效果
這個demo是直接基於官方demo修改而成的,有興趣的小夥伴可以直接上官網下載去
一.獲取圖像數據
首先我們需要知道Camerax功能分成三個用例了,分別是:預覽,分析,拍照,由於我們做的是二維碼分析,所以只需要用到預覽和分析兩個用例
1.導入CameraX所需用到的包
//CameraX依賴模塊
def camerax_version = '1.0.0-beta04'
implementation "androidx.camera:camera-core:$camerax_version"
// CameraX Camera2 拓展
implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle library
implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View
implementation 'androidx.camera:camera-view:1.0.0-alpha11'
2.佈局,這邊佈局很簡單就一個PreviewView
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/camera_container"
android:background="@android:color/black"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.camera.view.PreviewView
android:id="@+id/view_finder"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
3.初始化相機,和Camera2比起來,確實少了很多代碼,而且不需要自己管理相機的生命週期,屬實有點舒服,這裏權限申請什麼的我就不細說了
private fun bindCameraUseCases() {
// 獲取用於設置全屏分辨率相機的屏幕值
val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
//獲取使用的屏幕比例分辨率屬性
val screenAspectRatio = aspectRatio(metrics.widthPixels / 2, metrics.heightPixels / 2)
val width = viewFinder.measuredWidth
val height = if (screenAspectRatio == AspectRatio.RATIO_16_9) {
(width * RATIO_16_9_VALUE).toInt()
} else {
(width * RATIO_4_3_VALUE).toInt()
}
val size = Size(width, height)
//獲取旋轉角度
val rotation = viewFinder.display.rotation
//和Fragment的生命週期綁定
val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()//設置所選相機
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener(Runnable {
val cameraProvider = cameraProviderFuture.get()
// 預覽用例
preview = Preview.Builder()
.setTargetResolution(size)
.setTargetRotation(rotation)
.build()
//拍照用例
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.setTargetResolution(size)
.setTargetRotation(rotation)
.build()
// 圖像分析用例
imageAnalyzer = ImageAnalysis.Builder()
.setTargetResolution(size)
.setTargetRotation(rotation)
.build()
.apply {//核心類BarCodeAnalyzer
setAnalyzer(cameraExecutor, BarCodeAnalyzer(object : OnBack {
override fun back(code: String) {
activity?.apply {
runOnUiThread {
Toast.makeText(this, code, Toast.LENGTH_SHORT).show()
}
}
}
}))
}
// 必須在重新綁定用例之前取消之前綁定
cameraProvider.unbindAll()
try {
//獲取相機實例
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture, imageAnalyzer)
//設置預覽的view
preview?.setSurfaceProvider(viewFinder.createSurfaceProvider())
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(requireContext()))
}
這些代碼是初始化相機的代碼,因爲是官方demo,所以比較詳細,一共包含了preview,imageCupture,imageAnalyzer三個用例,分別是預覽用例,拍照用例,分析用例,我們二維碼分析只需要使用preview,imageAnalyzer用例,此時我們初始化的環節已經做好了,重點部分來了,我們來看看我們的測試用例類BaeCodeAnalyzer.class,代碼如下
private class BarCodeAnalyzer(val onBack: OnBack) : ImageAnalysis.Analyzer {
private val reader: MultiFormatReader = initReader()
/**
* 將buffer寫入數組
*/
private fun ByteBuffer.toByteArray(): ByteArray {
rewind()
val data = ByteArray(remaining())
get(data)
return data
}
override fun analyze(image: ImageProxy) {//圖片分析
//如果不是yuv_420_888格式直接不處理
if (ImageFormat.YUV_420_888 != image.format) {
Log.e("BarcodeAnalyzer", "expect YUV_420_888, now = ${image.format}")
image.close()
return
}
//將buffer數據寫入數組
val data = image.planes[0].buffer.toByteArray()
//獲取圖片寬高
val height = image.height
val width = image.width
//將圖片旋轉,這是豎屏掃描的關鍵一步,因爲默認輸出圖像是橫的,我們需要將其旋轉90度
val rotationData = ByteArray(data.size)
Log.i(TAG, "rotationDataSize :${data.size} ## height:$height ## width:$width")
var j: Int
var k: Int
for (y in 0 until height) {
for (x in 0 until width) {
j = x * height + height - y - 1
k = x + y * width
rotationData[j] = data[k]
}
}
//zxing核心解碼塊,因爲圖片旋轉了90度,所以寬高互換,最後一個參數是左右翻轉
val source = PlanarYUVLuminanceSource(rotationData, height, width, 0, 0, height, width, false)
val bitmap = BinaryBitmap(HybridBinarizer(source))
try {
val result = reader.decode(bitmap)
Log.i("Resultkkk", ":掃碼成功: ${result.text}")
onBack.back(result.text)
} catch (e: Exception) {
image.close()
} finally {
image.close()
}
}
private fun initReader(): MultiFormatReader {
val formatReader = MultiFormatReader()
val hints = Hashtable<DecodeHintType, Any>()
val decodeFormats = Vector<BarcodeFormat>()
//添加一維碼解碼格式
decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS)
//這個不知道幹啥的,可以不加
decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS)
//添加二維碼解碼格式
decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS)
hints[DecodeHintType.POSSIBLE_FORMATS] = decodeFormats
//設置解碼的字符類型
hints[DecodeHintType.CHARACTER_SET] = "UTF-8"
//這邊是焦點回調,就是找到那個條碼的所在位置,這裏我不處理
// hints[DecodeHintType.NEED_RESULT_POINT_CALLBACK] = mPointCallBack
formatReader.setHints(hints)
return formatReader
}
}
我們目前只需要關注analyze這個方法,這是個抽象方法,這個方法的就類似於傳統相機的Camera.PreviewCallback的回調,用於分析圖片,這是個默認是個阻塞式方法,裏面包含的ImageProxy就是我們需要使用的了,此時我們已經完成了圖像數據獲取了
二.分析利用zxing分析圖像數據
1.初始化Reader解碼器
這裏我們設置一下我們需要解析的條碼類型
private val reader: MultiFormatReader = initReader()
private fun initReader(): MultiFormatReader {
val formatReader = MultiFormatReader()
val hints = Hashtable<DecodeHintType, Any>()
val decodeFormats = Vector<BarcodeFormat>()
//添加一維碼解碼格式
decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS)
//這個不知道幹啥的,可以不加
decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS)
//添加二維碼解碼格式
decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS)
hints[DecodeHintType.POSSIBLE_FORMATS] = decodeFormats
//設置解碼的字符類型
hints[DecodeHintType.CHARACTER_SET] = "UTF-8"
//這邊是焦點回調,就是找到那個條碼的所在位置,這裏我不處理
// hints[DecodeHintType.NEED_RESULT_POINT_CALLBACK] = mPointCallBack
formatReader.setHints(hints)
return formatReader
}
2.開始解析
在Analyze方法中,首先我們將ImageProxy包含的數據獲取下來,此處默認返回類型是YUV_420_888,如果不是這種格式直接返回,如果是這種格式,那麼我們獲取它的數據寫入到數組當中,並且獲取寬高,此時注意我們寫入的數據是橫屏的圖片數據
//如果不是yuv_420_888格式直接不處理
if (ImageFormat.YUV_420_888 != image.format) {
Log.e("BarcodeAnalyzer", "expect YUV_420_888, now = ${image.format}")
image.close()
return
}
//將buffer數據寫入數組
val data = image.planes[0].buffer.toByteArray()
//獲取圖片寬高
val height = image.height
val width = image.width
將圖片旋轉90度
//將圖片旋轉,這是豎屏掃描的關鍵一步,因爲默認輸出圖像是橫的,我們需要將其旋轉90度
val rotationData = ByteArray(data.size)
Log.i(TAG, "rotationDataSize :${data.size} ## height:$height ## width:$width")
var j: Int
var k: Int
for (y in 0 until height) {
for (x in 0 until width) {
j = x * height + height - y - 1
k = x + y * width
rotationData[j] = data[k]
}
}
此時我們獲取到了rotationData,這就是旋轉之後的數據了,我們現在開始使用我們的reader開始解析
//zxing核心解碼塊,因爲圖片旋轉了90度,所以寬高互換,最後一個參數是左右翻轉
val source = PlanarYUVLuminanceSource(rotationData, height, width, 0, 0, height, width, false)
val bitmap = BinaryBitmap(HybridBinarizer(source))
try {
val result = reader.decode(bitmap)
Log.i("Resultkkk", ":掃碼成功: ${result.text}")
onBack.back(result.text)
} catch (e: Exception) {
image.close()
} finally {
image.close()
}
當沒有異常拋出的時候,這時候我們就是解析成功了,此時大功告成。