
С чего бы начать?
Библиотека от компании Google ML Kit предлагает набор встроенных API, которые могут работать как на самом девайсе, так и в облаке.
ML Kit — это мощный инструмент для работы с камерой в Android и IOS приложениях.
Возможности ML Kit

|
Категория |
API |
Описание |
|
Работа с изображениями |
Face Detection |
Обнаружение лиц, черт лица, направлений взгляда и т.д. |
|
Text Recognition |
Распознание текста, с возможность фильтровать и получать необходимый результат |
|
|
Barcode Scanning |
Сканирование QR-кодов и штрихкодов |
|
|
Image Labeling |
Определение объектов на изображении |
|
|
Object Detection & Tracking |
Обнаружение и отслеживание объектов |
|
|
Работа с текстом |
Smart Reply |
Генерация ответов на сообщения |
|
Translate |
Перевод текста в реальном времени |
|
|
Language Indetification |
Определение языка текста |
Как дела c Jetpack Compose?
С Jetpack Compose библиотека ML Kit отлично дружит и настоятельно рекомендую использовать в этой связке.
С чего начать?
Для примера я возьму две зависимости — одна для сканирования QR/штрихкодов, другая — для распознавания текста.
mlkit = { group = "com.google.mlkit", name = "barcode-scanning", version = "17.2.0" } mlkit-text = { group = "com.google.mlkit", name = "text-recognition", version = "16.0.0" } implementation "androidx.camera:camera-core:1.4.0" // для работы с CameraX
1) Можем создать простой пример использования. Используем AndroidView, которая позволит нам работать с PreviewView в камере.
@Composable fun ScannerScreen( scanType: ScanType, onCodeScanned: ((String) -> Unit)? = null // для передачи результата на определенный экран ) { val lifecycleOwner = LocalLifecycleOwner.current val context = LocalContext.current val analysisExecutor = remember { CoroutineScope(Dispatchers.Default) } val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) } DisposableEffect(Unit) { onDispose { val cameraProvider = cameraProviderFuture.get() cameraProvider.unbindAll() } } Box(modifier = Modifier.fillMaxSize()) { AndroidView( factory = { ctx -> val previewView = PreviewView(ctx).apply { layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) scaleType = PreviewView.ScaleType.FILL_CENTER } val cameraProviderFuture = ProcessCameraProvider.getInstance(ctx) cameraProviderFuture.addListener({ val cameraProvider = cameraProviderFuture.get() val previewUseCase = Preview.Builder().build().also { it.surfaceProvider = previewView.surfaceProvider } val permissionGranted = ContextCompat.checkSelfPermission( ctx, Manifest.permission.CAMERA ) == PackageManager.PERMISSION_GRANTED val barcodeScanner = BarcodeScanning.getClient( BarcodeScannerOptions.Builder() .setBarcodeFormats(Barcode.FORMAT_QR_CODE) .build() ) if (!permissionGranted) { // Нет доступа к камере return@addListener } val selector = when { cameraProvider.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) -> CameraSelector.DEFAULT_BACK_CAMERA cameraProvider.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) -> CameraSelector.DEFAULT_FRONT_CAMERA else -> { return@addListener } } val analysisUseCase = ImageAnalysis.Builder() .setTargetResolution(Size(1280, 720)) .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build() .also { analysis -> analysis.setAnalyzer( { runnable -> analysisExecutor.launch { runnable.run() } } ) { imageProxy -> val mediaImage = imageProxy.image if (mediaImage != null) { val rotation = imageProxy.imageInfo.rotationDegrees val inputImage = InputImage.fromMediaImage(mediaImage, rotation) when (scanType) { ScanType.ScanQr -> { barcodeScanner.process(inputImage) .addOnSuccessListener { barcodes -> barcodes.firstOrNull { barcode -> val box = barcode.boundingBox box != null }?.rawValue?.let { qrCode -> onCodeScanned?.let { it(qrCode) } } } .addOnCompleteListener { imageProxy.close() } } ScanType.ScanCard -> { val textRecognizer = TextRecognition.getClient( TextRecognizerOptions.DEFAULT_OPTIONS ) textRecognizer.process(inputImage) .addOnSuccessListener { visionText -> if (!visionText.isNullOrEmpty()) { onCodeScanned?.let { it(visionText) } } } .addOnCompleteListener { imageProxy.close() } } ScanType.ScanText -> { val textRecognizer = TextRecognition.getClient( TextRecognizerOptions.DEFAULT_OPTIONS ) textRecognizer.process(inputImage) .addOnSuccessListener { visionText -> val text = visionText.text if (!text.isNullOrEmpty()) { onCodeScanned?.let { result -> result(text) } } } .addOnCompleteListener { imageProxy.close() } } ScanType.None -> {} } } else { imageProxy.close() } } } try { cameraProvider.unbindAll() cameraProvider.bindToLifecycle( lifecycleOwner, selector, previewUseCase, analysisUseCase ) } catch (exception: Exception) { // Ошибка привязки камеры } }, ContextCompat.getMainExecutor(ctx)) previewView }, modifier = Modifier.fillMaxSize() ) when (scanType) { ScanType.ScanQr -> { CameraScanQrOverlay() } ScanType.ScanCard -> { CameraScanCardOverlay() } ScanType.ScanText -> { CameraScanTextOverlay() } ScanType.None -> {} } } } sealed class ScanType() { data object ScanQr: ScanType() data object ScanText: ScanType() data object ScanCard: ScanType() data object None: ScanType() }
Что здесь происходит? Создаем PreviewView для отображения изображения с камеры (CameraX), далее в LayoutParams устанавливаем ширину и высоту для контейнера.
PreviewView.ScaleType.FILL_CENTER — отвечает за то, чтобы заполнить PreviewView, сохраняя при этому центрирование.
Какие есть варианты PreviewView.ScaleType?
-
FILL_START
-
FILL_CENTER
-
FILL_END
-
FIT_CENTER
Детальный пример PreviewView.ScaleType
2) Далее получаем CameraProvider, который отвечает за получение и настройки камеры
val cameraProviderFuture = ProcessCameraProvider.getInstance(ctx) cameraProviderFuture.addListener({ ... }, ContextCompat.getMainExecutor(ctx))
addListener позволяет безопасно получить cameraProvider, когда он будет доступен.
2.1) Так же можете добавить проверку разрешения камеры. При необходимости связать с навигацией и в случае чего либо возвращать пользователя на предыдущий экран или показывать соответствующее диалоговое окно
val permissionGranted = ContextCompat.checkSelfPermission(...) == PackageManager.PERMISSION_GRANTED if (!permissionGranted) return@addListener
3) Далее добавим выбор доступной камеры — сперва основная, потом фронтальная
val selector = when { cameraProvider.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) -> ... cameraProvider.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) -> ... else -> return@addListener }
4) Создаем image analysis use case для анализа кадров с камеры
val analysisUseCase = ImageAnalysis.Builder() .setTargetResolution(Size(1280, 720)) .setBackpressureStrategy(...) .build()
4.1) Обязательно! Если mediaImage == null, то освобождаем ресурсы камеры!
Почему это так важно?
-
Предотвращение утечек памяти
-
Избежание сбоев и зависания
-
Освобождения ресурсов камеры
if (mediaImage != null) { val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) scanner.process(inputImage) .addOnSuccessListener { barcodes -> // Обработка кодов } .addOnFailureListener { // Обработка ошибок } .addOnCompleteListener { // Важно! Закрываем imageProxy после обработки imageProxy.close() } } else { imageProxy.close() } }
5) Установка анализатора, анализатор передает кадры на обработку.
analysis.setAnalyzer({ runnable -> ... }) { imageProxy -> ... }
image proxy — объект изображения
6) Привязка use-cases к жизненому циклу. Удаляем все предыдущие use cases (unbindAll())
cameraProvider.unbindAll() cameraProvider.bindToLifecycle( lifecycleOwner, selector, previewUseCase, analysisUseCase )
7) Обрабатываем overlay UI по типу сканирования. Также вы можете передавать Rect области сканирования из самих Overlay в ScannerScreen.
when (scanType) { ScanType.ScanQr -> CameraScanQrOverlay() ScanType.ScanCard -> CameraScanCardOverlay() ScanType.ScanText -> CameraScanTextOverlay() ScanType.None -> {} }
Почему не zxing?
При всех своих плюсах в простоте и быстрой реализации Zxing имеет свои плюсы и минусы. Да, ML Kit зависит от Google сервисов, но при этом это современное и надежное решение с больших спектром настроек, если для вас критично, чтобы приложение имело малый размер и быстрое внедрение в проект, то собственно вам подойдет Zxing, но если вы хотите, чтобы решение было гибкое и современное, то однозначно ML Kit.
Итоги
Библиотека ML Kit имеет ряд преимуществ: высокая точность и распознание даже при плохом освещении, широкая поддержка форматов, работает оффлайн, поддерживается Google и активно обновляется, легко кастомизировать UI в связке c Jetpack Compose, можно объединить с другими ML Kit модулями.
Надеюсь статья привнесла пользу Вам, попытался изложить в кратной форме, если есть вопросы, то буду рад ответить на них. Cпасибо за внимание!
ссылка на оригинал статьи https://habr.com/ru/articles/908772/
Добавить комментарий