如何在Android Jetpack CameraX中实现感兴趣区域(ROI)的条码与文字识别功能
Android Jetpack CameraX 实现ROI区域扫描识别方案
核心思路:不需要修改相机预览流本身的输出,仅需将UI层定义的扫描框坐标转换为相机图像帧的对应坐标,要么裁剪对应区域传识别SDK,要么过滤全图识别结果,即可实现仅指定区域识别的需求,性能损耗极低。
1. 坐标映射逻辑实现
相机输出帧的尺寸、方向和UI层预览View的参数不匹配,必须先做坐标转换,否则裁剪区域和预览扫描框完全错位,通用转换代码如下:
fun transformUiRectToImageRect(uiScanRect: RectF, previewView: PreviewView, imageProxy: ImageProxy): Rect { val previewWidth = previewView.width.toFloat() val previewHeight = previewView.height.toFloat() // 判断屏幕方向,竖屏状态下相机帧宽高会互换 val isPortrait = previewView.display.rotation in listOf(Surface.ROTATION_0, Surface.ROTATION_180) val imageWidth = if (isPortrait) imageProxy.height else imageProxy.width val imageHeight = if (isPortrait) imageProxy.width else imageProxy.height // 计算相机帧到预览View的缩放比例,取最大值保证等比例填充 val scaleX = imageWidth / previewWidth val scaleY = imageHeight / previewHeight val scale = max(scaleX, scaleY) // 计算等比例缩放后的偏移量,处理预览View边距裁剪的问题 val offsetX = (imageWidth - previewWidth * scale) / 2 val offsetY = (imageHeight - previewHeight * scale) / 2 // 转换UI坐标到相机帧坐标,做边界兼容防止越界 val left = (uiScanRect.left * scale + offsetX).toInt().coerceAtLeast(0) val top = (uiScanRect.top * scale + offsetY).toInt().coerceAtLeast(0) val right = (uiScanRect.right * scale + offsetX).toInt().coerceAtMost(imageWidth) val bottom = (uiScanRect.bottom * scale + offsetY).toInt().coerceAtMost(imageHeight) return Rect(left, top, right, bottom) }
2. 在图像分析器中实现ROI识别
初始化CameraX的ImageAnalysis用例,在帧回调中处理ROI逻辑,两种实现方式可选:
方案1:裁剪ROI区域后传识别SDK
适合所有支持Bitmap输入的识别SDK,逻辑最通用:
val analysisUseCase = ImageAnalysis.Builder() .setTargetResolution(Size(1280, 720)) .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build() .also { analysis -> analysis.setAnalyzer(Executors.newSingleThreadExecutor()) { imageProxy -> // 转换UI扫描框到相机帧坐标 val roiRect = transformUiRectToImageRect(scanRectF, previewView, imageProxy) // 将YUV帧转成Bitmap val fullBitmap = Bitmap.createBitmap(imageProxy.width, imageProxy.height, Bitmap.Config.ARGB_8888) YuvToRgbConverter(context).yuvToRgb(imageProxy.image!!, fullBitmap) // 裁剪ROI区域Bitmap val roiBitmap = Bitmap.createBitmap(fullBitmap, roiRect.left, roiRect.top, roiRect.width(), roiRect.height()) // 传入条码/文字识别SDK处理 processBarcode(roiBitmap) processTextRecognition(roiBitmap) // 回收资源 roiBitmap.recycle() fullBitmap.recycle() imageProxy.close() } } // 最后将analysisUseCase和Preview用例一起绑定到生命周期
方案2:全图识别后过滤ROI范围内的结果
适合ML Kit等自带识别结果坐标返回的SDK,不需要裁剪Bitmap,性能更好:
analysis.setAnalyzer(Executors.newSingleThreadExecutor()) { imageProxy -> val roiRect = transformUiRectToImageRect(scanRectF, previewView, imageProxy) val inputImage = InputImage.fromMediaImage(imageProxy.image!!, imageProxy.imageInfo.rotationDegrees) // 条码识别示例 BarcodeScanning.getClient().process(inputImage) .addOnSuccessListener { barcodes -> // 仅保留在ROI范围内的识别结果 val validResults = barcodes.filter { barcode -> barcode.boundingBox?.intersect(roiRect) == true } // 处理有效结果 } .addOnCompleteListener { imageProxy.close() } }
3. 可选优化
- 若需要相机优先对焦ROI区域,可添加对焦逻辑:
val meteringPointFactory = SurfaceOrientedMeteringPointFactory(previewView.width.toFloat(), previewView.height.toFloat()) val meteringPoint = meteringPointFactory.createPoint( scanRectF.centerX(), scanRectF.centerY(), scanRectF.width() / previewView.width, scanRectF.height() / previewView.height ) val focusAction = FocusMeteringAction.Builder(meteringPoint, FocusMeteringAction.FLAG_AF) .setAutoCancelDuration(2, TimeUnit.SECONDS) .build() camera?.cameraControl?.startFocusAndMetering(focusAction)
- 扫描框UI可自定义半透明遮罩,无需和CameraX逻辑绑定,只要拿到其在PreviewView中的相对坐标即可。
内容的提问来源于stack exchange,提问作者vignesh prakash
相关产品推荐
相关产品推荐

