Android 开发 CameraX + ML Kit 实时文字识别 从零到一完整实战指南

发布时间:2026/7/14 12:26:32
Android 开发 CameraX + ML Kit 实时文字识别 从零到一完整实战指南 1. 环境准备与依赖配置在开始之前我们需要确保开发环境满足基本要求。Android Studio是最推荐的开发工具建议使用最新稳定版本。创建一个新的Android项目选择Empty Activity模板即可。关键依赖配置 打开app/build.gradle文件添加以下依赖项// CameraX核心库 def camerax_version 1.3.0 implementation androidx.camera:camera-core:${camerax_version} implementation androidx.camera:camera-camera2:${camerax_version} implementation androidx.camera:camera-lifecycle:${camerax_version} implementation androidx.camera:camera-view:1.3.0 // ML Kit文字识别库支持中文 implementation com.google.mlkit:text-recognition-chinese:16.0.0 // 其他工具库 implementation com.google.guava:guava:31.1-android注意minSdkVersion需要设置为21或更高。同步项目后检查是否成功下载了所有依赖项。2. CameraX相机配置CameraX是Jetpack组件中的相机库它简化了相机功能的实现并自动处理设备兼容性问题。相机权限声明 在AndroidManifest.xml中添加uses-permission android:nameandroid.permission.CAMERA / uses-feature android:nameandroid.hardware.camera / uses-feature android:nameandroid.hardware.camera.autofocus /相机预览实现 创建一个CameraPreview类继承自PreviewViewclass CameraPreview(context: Context, attrs: AttributeSet) : PreviewView(context, attrs) { private val executor Executors.newSingleThreadExecutor() fun startCamera(lifecycleOwner: LifecycleOwner) { val cameraProviderFuture ProcessCameraProvider.getInstance(context) cameraProviderFuture.addListener({ val cameraProvider cameraProviderFuture.get() val preview Preview.Builder().build().also { it.setSurfaceProvider(surfaceProvider) } val cameraSelector CameraSelector.DEFAULT_BACK_CAMERA try { cameraProvider.unbindAll() cameraProvider.bindToLifecycle( lifecycleOwner, cameraSelector, preview ) } catch(exc: Exception) { Log.e(CameraPreview, 相机绑定失败, exc) } }, ContextCompat.getMainExecutor(context)) } }在Activity布局中添加这个自定义视图com.yourpackage.CameraPreview android:idid/cameraPreview android:layout_widthmatch_parent android:layout_heightmatch_parent /3. 图像分析与文字识别这是核心部分我们将配置ImageAnalysis用例来处理相机帧。分析器实现class TextAnalyzer( private val onTextDetected: (String) - Unit, private val onError: (Exception) - Unit ) : ImageAnalysis.Analyzer { private val recognizer TextRecognition.getClient( ChineseTextRecognizerOptions.Builder().build() ) ExperimentalGetImage override fun analyze(imageProxy: ImageProxy) { val mediaImage imageProxy.image if (mediaImage ! null) { val image InputImage.fromMediaImage( mediaImage, imageProxy.imageInfo.rotationDegrees ) recognizer.process(image) .addOnSuccessListener { visionText - val detectedText processTextBlocks(visionText.textBlocks) onTextDetected(detectedText) } .addOnFailureListener { e - onError(e) } .addOnCompleteListener { imageProxy.close() } } else { imageProxy.close() } } private fun processTextBlocks(blocks: ListText.TextBlock): String { return blocks.joinToString(\n) { block - block.lines.joinToString( ) { line - line.text } } } }配置分析用例private fun setupImageAnalysis() { val imageAnalysis ImageAnalysis.Builder() .setTargetResolution(Size(1280, 720)) .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build() .also { it.setAnalyzer( ContextCompat.getMainExecutor(this), TextAnalyzer( onTextDetected { text - binding.resultTextView.text text }, onError { e - Log.e(MainActivity, 识别错误, e) } ) ) } val cameraProviderFuture ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener({ val cameraProvider cameraProviderFuture.get() val cameraSelector CameraSelector.DEFAULT_BACK_CAMERA try { cameraProvider.unbindAll() cameraProvider.bindToLifecycle( this, cameraSelector, imageAnalysis, binding.cameraPreview.preview ) } catch(exc: Exception) { Log.e(MainActivity, 用例绑定失败, exc) } }, ContextCompat.getMainExecutor(this)) }4. 性能优化与实用技巧实时文字识别对性能要求较高以下优化措施可以显著提升体验1. 分辨率控制设置合适的目标分辨率推荐720p或1080p过高分辨率会增加处理时间过低则影响识别精度.setTargetResolution(Size(1280, 720))2. 帧率控制 通过分析间隔避免处理每一帧.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)3. 预处理优化 在分析器中添加简单的图像预处理private fun enhanceImage(image: Bitmap): Bitmap { val matrix ColorMatrix() matrix.setSaturation(0f) // 转为灰度图 val filter ColorMatrixColorFilter(matrix) val result Bitmap.createBitmap(image.width, image.height, image.config) val canvas Canvas(result) val paint Paint() paint.colorFilter filter canvas.drawBitmap(image, 0f, 0f, paint) return result }4. 结果去抖动 实现简单的文本稳定性算法class TextStabilizer(private val threshold: Int 3) { private val recentResults ArrayDequeString() private var stableResult fun process(newText: String): String { if (recentResults.size threshold) { recentResults.removeFirst() } recentResults.addLast(newText) if (recentResults.all { it newText }) { stableResult newText } return stableResult } }5. 内存管理 确保及时释放资源override fun onDestroy() { super.onDestroy() recognizer.close() }5. 完整实现与调试将所有部分整合到Activity中class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private val textStabilizer TextStabilizer() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) PackageManager.PERMISSION_GRANTED) { startCamera() } else { ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION ) } } private fun startCamera() { binding.cameraPreview.startCamera(this) setupImageAnalysis() } override fun onRequestPermissionsResult( requestCode: Int, permissions: Arrayout String, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode REQUEST_CAMERA_PERMISSION grantResults.firstOrNull() PackageManager.PERMISSION_GRANTED) { startCamera() } else { Toast.makeText(this, 需要相机权限, Toast.LENGTH_SHORT).show() } } companion object { private const val REQUEST_CAMERA_PERMISSION 1001 } }常见问题排查黑屏问题检查相机权限是否授予识别率低确保光线充足文字清晰性能问题降低分辨率或减少分析频率旋转问题确认正确处理了图像旋转角度6. 扩展功能多语言支持 可以动态加载不同语言的识别模型// 在build.gradle中添加其他语言依赖 implementation com.google.mlkit:text-recognition-japanese:16.0.0 implementation com.google.mlkit:text-recognition-korean:16.0.0 // 动态创建识别器 fun createRecognizer(language: String): TextRecognizer { return when(language) { ja - TextRecognition.getClient( JapaneseTextRecognizerOptions.Builder().build() ) ko - TextRecognition.getClient( KoreanTextRecognizerOptions.Builder().build() ) else - TextRecognition.getClient( ChineseTextRecognizerOptions.Builder().build() ) } }结果高亮显示 在识别结果上显示文本块边界fun drawTextBlocks(canvas: Canvas, blocks: ListText.TextBlock) { blocks.forEach { block - block.boundingBox?.let { rect - val paint Paint().apply { color Color.GREEN style Paint.Style.STROKE strokeWidth 4f } canvas.drawRect(rect, paint) block.lines.forEach { line - line.boundingBox?.let { lineRect - paint.color Color.BLUE canvas.drawRect(lineRect, paint) } } } } }离线模式优化 ML Kit默认支持离线识别但首次使用时需要下载模型。可以通过以下方式检查模型状态val remoteModel TextRecognizerRemoteModel.Builder(TextRecognitionOptions.DEFAULT_OPTIONS) .build() DownloadManager.getInstance(context).isModelDownloaded(remoteModel) .addOnSuccessListener { downloaded - if (!downloaded) { // 显示下载提示 DownloadManager.getInstance(context) .download(remoteModel, DownloadConditions.Builder().build()) } }在实际项目中我发现合理控制分析频率对平衡性能和识别效果至关重要。当处理快速移动的文本时适当降低帧率反而能获得更稳定的结果。另外对于中文识别确保文字区域至少有16像素的高度能显著提高准确率。