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:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" />相机预览实现: 创建一个CameraPreview类继承自PreviewView:
class 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:id="@+id/cameraPreview" android:layout_width="match_parent" android:layout_height="match_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: List<Text.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 = ArrayDeque<String>() 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: Array<out 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: List<Text.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像素的高度能显著提高准确率。