身份證掃描主要需要用到文字識(shí)別技術(shù)(OCR)。這類(lèi)技術(shù)方案已經(jīng)很多了,本文介紹基于 CameraX + MLKit 的實(shí)現(xiàn)方式。其中 CameraX 用來(lái)實(shí)現(xiàn)相機(jī)的取景和預(yù)覽,MLKit 用來(lái)進(jìn)行圖片中的文字識(shí)別。
1. CameraX 實(shí)現(xiàn)相機(jī)預(yù)覽
1.1 CameraX 簡(jiǎn)介
Android 自 5.0 開(kāi)始引入了全新的相機(jī)框架 Camera2 ,相較于之前的 Camera1 對(duì)多攝像頭的支持更加友好,功能更加強(qiáng)大,但使用成本也更高。此背景下谷歌發(fā)布了 CameraX,它基于 Camera2 封裝,大大提高了 API 的易用性。我們可以用很少的代碼搭建出面向特定場(chǎng)景的相機(jī)應(yīng)用,OCR 就是一種典型的相機(jī)應(yīng)用場(chǎng)景 。
CameraX 引入 UseCase 的概念完成各種相機(jī)能力,UseCase 有利于功能模塊的解耦,聚焦特定領(lǐng)域進(jìn)行功能開(kāi)發(fā)。CameraX 默認(rèn)提供了幾個(gè)常用的 UseCase 實(shí)現(xiàn),能夠滿足大多數(shù)場(chǎng)景下的使用
Preview : 提供相機(jī)取景和預(yù)覽
ImageCapture:拍照并保存圖片
ImageAnalysis:處理預(yù)覽幀圖片
本文 OCR 場(chǎng)景中將會(huì)使用到 Preview 和 ImageAnalysis 這兩個(gè) UseCase。Preview 幫助我們實(shí)現(xiàn)相機(jī)的取景和預(yù)覽,ImageAnalysis 幫助我們將采集的圖片送入 OCR 分析。
接下來(lái)讓我們使用 CameraX 一步步完成相機(jī)預(yù)覽功能
1.2 工程引入 CameraX
首先,在 Gradle 中引入 CameraX 相關(guān)庫(kù)如下
implementation "androidx.camera:camera-lifecycle:1.2.0" implementation "androidx.camera:camera-view:1.2.0" implementation "androidx.camera:camera-camera2:1.2.0"
另外,需要使用相機(jī),所以在 AndroidManifest 中申請(qǐng)相機(jī)權(quán)限
<uses-permission android:name="android.permission.CAMERA" tools:ignore="PermissionImpliesUnsupportedChromeOsHardware" />
1.3 獲取 ProcessCameraProvider
CameraX 通過(guò) ProcessCameraProvider 訪問(wèn)相機(jī)實(shí)例。顧名思義,ProcessCamera 表示每個(gè) Application Process 期間可使用的相機(jī)服務(wù),所以 ProcessCameraProvider 是一個(gè)進(jìn)程單例,通過(guò) getInstance 創(chuàng)建并獲取。創(chuàng)建是一個(gè)異步過(guò)程,所以借助 CameraProviderFuture 異步返回:
// 通過(guò) cameraProviderFuture 異步返回創(chuàng)建的 ProcessCameraProvider 實(shí)例 val cameraProviderFuture = ProcessCameraProvider.getInstance(context) //監(jiān)聽(tīng) ProcessCameraProvider 獲取成功 cameraProviderFuture.addListener( Runnable { //獲取 cameraProvider val cameraProvider = cameraProviderFuture.get() ... }, ContextCompat.getMainExecutor(context) // Runnable 運(yùn)行的 Executor )
在 Runnable 中成功獲取 ProcessCameraProvider 單例,接下來(lái)可以用它來(lái)組裝 UseCase ,實(shí)現(xiàn)相機(jī)功能了。
CameraX 的一個(gè)重要特征是 LifecycleAware,相機(jī)可以根據(jù)應(yīng)用的前后臺(tái)情況自動(dòng)開(kāi)啟或關(guān)閉,降低開(kāi)發(fā)者的心智負(fù)擔(dān)。ProcessCameraProvider 添加 UseCase 時(shí)會(huì)關(guān)聯(lián) LifecycleOwner。
UseCase 根據(jù) Lifecycle 調(diào)用 onStateAttached / onStateDetatched,當(dāng)我們自定義 UseCase 時(shí),可以在這里進(jìn)行一些自定義前 / 后處理。
1.4 添加 Preview UseCase
//選擇后置鏡頭 val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() //添加 Preivew UseCase cameraProvider.bindToLifecycle( lifecycleOwner, cameraSelector, preview )
如上,ProcessCameraProvicer#bindToLifecycle 添加 Preview 。
Preview UseCase 的創(chuàng)建非常簡(jiǎn)單,如下:
val preview = Preview.Builder().build().ly { setSurfaceProvider(previewView.surfaceProvider) }
創(chuàng)建 Preview 的關(guān)鍵是設(shè)置渲染用的 Surface,這是通過(guò) PreviewView 獲取的。
PreviewView 是 CameraX 提供的用于顯示相機(jī)預(yù)覽流的自定義 View,它內(nèi)部可以根據(jù)需要切換 TexureView 或者 SurfaceView。
SurfaceView 有更好的性能,但在 Android 7.0 之前無(wú)法實(shí)現(xiàn)旋轉(zhuǎn)、透明、動(dòng)畫(huà)等常規(guī)自定義 View 的能力,此時(shí)需要使用 TextureView 替代。PreviewView 默認(rèn)使用性能優(yōu)先的 SurfaceView,如果如果需要其有更好的兼容性,則可以設(shè)置 previewView.implementationMode = PreviewView.ImplementationMode.COMPATIBLE
1.5 布局 PreviewView
我們可以像下面這樣在 xml 中布局使用 PreviewView
<FrameLayout android:id="@+id/container"> <androidx.camera.view.PreviewView android:id="@+id/previewView" /> </FrameLayout>
如果我們使用 Compose 渲染 UI ,可以借助 AndroidView 顯示 PreviewView,Compose 展示相機(jī)預(yù)覽的代碼大體如下所示:
@Composable fun CameraScreen() { //獲取 ProcessCameraProvider val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) } // 顯示預(yù)覽 AndroidView( modifier = Modifier.fillMaxSize(), factory = { ctx -> PreviewView(ctx).ly { cameraProviderFuture.addListener({ val cameraProvider = cameraProviderFuture.get() val preview = //略 val cameraSelector = //略 cameraProvider.unbindAll() cameraProvider.bindToLifecycle( LocalLifecycleOwner.current, cameraSelector, preview ) }, ContextCompat.getMainExecutor(previewView.context)) } }) }
2. MLKit 實(shí)現(xiàn)文字識(shí)別
2.1 MLKit 簡(jiǎn)介
MLKit 是谷歌的面向移動(dòng)端開(kāi)發(fā)者的機(jī)器學(xué)習(xí)庫(kù),幫助移動(dòng)應(yīng)用在離線狀態(tài)下使用各種端智能技術(shù),例如:
智能視覺(jué)處理:二維碼掃描、文字識(shí)別、人臉檢測(cè)、物體捕捉等;
自然語(yǔ)言處理:語(yǔ)言識(shí)別、智能回復(fù)、自動(dòng)翻譯等
這些端上的技術(shù)讓?xiě)?yīng)用變得更加智能的同時(shí)依然保持高性能,更重要的是這一切都是免費(fèi)的,且不依賴 GMS(Google Mobile Service)。
2.2 工程引入 MLKit
本文我們主要使用到 MLKit 的文字識(shí)別功能,只需要添加以下依賴即可:
implementation 'com.google.mlkit:text-recognition-chinese:16.0.0-6'
text-recognition-chinese 可以識(shí)別中文字符,另外也有其他的 Artifact 可以識(shí)別日文韓文等非拉丁系的語(yǔ)言。
2.3 CameraX 實(shí)現(xiàn)圖像分析
前面我們通過(guò) Preview 實(shí)現(xiàn)了相機(jī)預(yù)覽,接下來(lái)我們?yōu)?CameraProvider 添加 ImageAnalysis ,它可以接收相機(jī)的預(yù)覽幀用于圖像分析和處理。
val imageAnalysis = ImageAnalysis.Builder) .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).build() .ly //設(shè)置圖像分析器 setAnalyzer Executors.newSingleThreadExecutor(), OcrAnalyzer result: String - //基于 MLKit 處理 OCR,并返回 result cameraProvider.bindToLifecycle LocalLifecycleOwner.current, cameraSelector, preview, imageAnalysis // 增加 ImageAnalysis 能力,關(guān)聯(lián) Lifecycle
setBackpressureStrategy 是設(shè)置預(yù)覽幀的生產(chǎn)消費(fèi)的緩沖策略,其默認(rèn)值 ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST 表示在每一幀沒(méi)有分析結(jié)束之前,新的渲染幀會(huì)自動(dòng)丟棄,避免排隊(duì)。
ImageAnalysis#setAnalyzer 添加自定義圖像分析器,這里我們定義一個(gè) OcrAnalyzer,它基于 MLKit 實(shí)現(xiàn) OCR 功能。
2.4 自定義 OcrAnalyzer
class OcrAnalyzer( private val onRecognized : (result: String) -> Unit ) : ImageAnalysis.Analyzer { // 獲取可識(shí)別中文的 TextRecognition private val recognition = TextRecognition.getClient(ChineseTextRecognizerOptions.Builder().build()) // 對(duì) Image 進(jìn)行處理 override fun analyze(imageProxy: ImageProxy) { val image = imageProxy.image if (image != null) { val imageRotation = imageProxy.imageInfo.rotationDegrees val inputImage = InputImage.fromMediaImage(image, imageRotation) recognition.process(inputImage) .addOnSuccessListener { recognizedText -> val textBlocks = recognizedText.textBlocks //解析 textBlocks 獲取所需的信息并返回 extractText(textBlocks)?.let { onRecognized(it) } imageProxy.close() }.addOnFailureListener { imageProxy.close() } } } }
ImageAnalysis.Analyzer 返回的 ImageProxy 中包含了預(yù)覽幀信息:
imageProxy.image:圖像信息
ImageInfo.rotationDegrees:根據(jù)設(shè)備情況獲得的圖片旋轉(zhuǎn)角度。
InputImage.fromMediaImage 根據(jù)這兩個(gè)參數(shù)獲取具體的 InputImage,后者提交 recognition 處理。這里的 recognition 是一個(gè)可識(shí)別中文的 TextRecognition。
2.5 解析 TextBlocks
經(jīng)過(guò) TextRecognition 文字識(shí)別后將返回 Block / Line / Element 這樣的數(shù)據(jù)結(jié)構(gòu),這種結(jié)構(gòu)有利于進(jìn)一步細(xì)粒度的解析。
Block 代表一個(gè)自然段落,由若干 Line(行) 組成,每一個(gè) Line 又包含多個(gè) Element(單詞) 。
假設(shè)我們希望從身份證中獲取姓名以及身份證號(hào),雖然不確定身份證這樣的排版會(huì)被識(shí)別為怎樣的 Block,但是姓名和身份證號(hào)肯定處于不同 Line 中。我們定義 extractText 方法,將所有的 Block 下的 Line 聚合到一起,統(tǒng)一進(jìn)行解析:
private fun extractText(textBlocks: List<Text.TextBlock>): String { val lines = textBlocks.flatMap { it.lines } var name = "unknown" var id = "unknown" lines.forEach { val lineText = it.elements.joinToString { it.text } if (lineText.contains("姓名")) { name = lineText.substringAfter("姓名") } if (lineText.contains("公民身份證號(hào)碼")) { id = lineText.substringAfter("公民身份證號(hào)碼") } } return "$name\n$id" }
成功識(shí)別文字后的效果如下:
結(jié)束語(yǔ)
透過(guò)文字識(shí)別這樣一個(gè)小的應(yīng)用場(chǎng)景,我們切實(shí)感受到了 CameraX 以及 MLKit 開(kāi)箱即用般的的易用性。作為谷歌官方工具包,它們還與 Compose 等其他 Jetpack 組件有著不錯(cuò)的兼容性。感謝谷歌強(qiáng)大的開(kāi)發(fā)者生態(tài),讓開(kāi)發(fā)者們可以低成本地開(kāi)發(fā)自己的移動(dòng)應(yīng)用。
CameraX:https://developer.android.com/training/camerax
MLKit:https://developers.google.com/ml-kit
本文來(lái)自微信公眾號(hào):AndroidPub (ID:gh_e312d1adb6ec),作者:fundroid
廣告聲明:文內(nèi)含有的對(duì)外跳轉(zhuǎn)鏈接(包括不限于超鏈接、二維碼、口令等形式),用于傳遞更多信息,節(jié)省甄選時(shí)間,結(jié)果僅供參考,IT之家所有文章均包含本聲明。