設(shè)置
  • 日夜間
    隨系統(tǒng)
    淺色
    深色
  • 主題色

CameraX + MLKit 打造超簡(jiǎn)單 OCR 方案

AndroidPub 2023/1/18 18:43:06 責(zé)編:夢(mèng)澤

身份證掃描主要需要用到文字識(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ì)使用到 PreviewImageAnalysis 這兩個(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之家所有文章均包含本聲明。

相關(guān)文章

關(guān)鍵詞:圖像,文字

軟媒旗下網(wǎng)站: IT之家 最會(huì)買(mǎi) - 返利返現(xiàn)優(yōu)惠券 iPhone之家 Win7之家 Win10之家 Win11之家

軟媒旗下軟件: 軟媒手機(jī)APP應(yīng)用 魔方 最會(huì)買(mǎi) 要知