В настоящее время широкую популярность набирает машинное обучение. Так как обучение моделей трудозатратно, то к его применению в Android относятся с неохотой. Однако Google упростил нам эту задачу с появлением ML Kit. В этой статье кратко посмотрим на возможности ML Kit и напишем приложение, распознающее текст в изображении с дальнейшим копированием в буфер обмена.
Кратко о возможностях ML Kit
Это мобильное SDK от Google, позволяющая использовать машинное обучение под Android и IOS.
Для совсем новичков в машинном обучении Google предоставляет готовые модели, а для экспертов создавать кастомные при помощи Tensorflow Lite.
ML Kit позволяет нам работать с текстом(Vision Api):
1. Распознавать текст
2. Обнаружение лиц
3. Сканировать штрих-коды
4. Разметка изображений
5. Определять и отслеживать объекты
6. Распознать ландшафты
Также можно работать с NLP(Native Language Processing):
1. Определение языка
2. Перевод текста
3. Генерировать простые ответы на сообщения
Кроме того можно создавать свои кастомные модели, самостоятельно обучать и использовать в своем приложении.
Распознавание текста(Реализация)
Дана задача: определить текст по фото(фото будем брать из галереи), а потом этот текст скопировать в буфер обмена.
Перед тем как начать подключите Firebase к проекту(если не делали этого ранее).
Накидаем разметку в activity_main
<ImageView android:id="@id/image_holder" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:fitsSystemWindows="true" app:layout_collapseMode="parallax" android:contentDescription="@string/image_for_recognition" /> //... <TextView android:id="@+id/detected_text_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center|top" android:textAlignment="center" /> //... <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/choose_image_from_gallery_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/activity_horizontal_margin" android:src="@drawable/ic_gallery_24dp" app:layout_anchor="@id/main.appbar" app:layout_anchorGravity="bottom|right|end" app:rippleColor="@color/colorSecondary" />
image_holder – туда мы будем помещать изображение из галереи
detected_text_view – текст, распознанный с картинки
Накидаем разметку в bottom_sheet
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="0.5" android:text="@string/text_button" android:onClick="recognizeText" android:textAllCaps="false" style="@style/RoundedCornerButton" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="0.5" android:text="@string/copy_button" android:onClick="copyText" android:textAllCaps="false" style="@style/RoundedCornerButton" />
Добавляем зависимость в проект для распознавания текста
implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
Укажем метаданные в манифесте в <application>, чтобы модели загружались в маркете при нажатии кнопки “Install” в Google Play, иначе придется потом в фоне загружать.
<meta-data android:name="com.google.firebase.ml.vision.DEPENDENCIES" android:value="text" />
Откроем MainActivity.kt и добавим слушатель событий для choose_image_from_gallery_btn и вставим картинку в наш image_holder
choose_image_from_gallery_btn.setOnClickListener { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){ if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED){ val permissions = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE) requestPermissions(permissions, PERMISSION_CODE) } else { pickImageFromGallery() } } } private fun pickImageFromGallery() { val intent = Intent(Intent.ACTION_PICK) intent.type = "image/*" startActivityForResult(intent, IMAGE_PICK_CODE) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (resultCode == RESULT_OK && requestCode == IMAGE_PICK_CODE){ image_holder.setImageURI(data?.data) } }
Картинку мы получили, теперь нам нужно распознать текст и вывести в наш detected_text_view.
fun recognizeText(view: View) { if (image_holder.drawable == null){ Toast.makeText(this, R.string.image_null, Toast.LENGTH_LONG).show() return } recognizeTextFromDevice() }
private fun recognizeTextFromDevice() { val detector = FirebaseVision.getInstance().onDeviceTextRecognizer // Получаем состояние FirebaseVisionTextRecognizer val textImage = FirebaseVisionImage.fromBitmap((image_holder.drawable as BitmapDrawable).bitmap) detector.processImage(textImage) .addOnSuccessListener { firebaseVisionText -> detected_text_view.text = TextProcessor.getInstance().process(firebaseVisionText) //Обрабатываем полученный текст } .addOnFailureListener { // Handling error } }
Как вы могли заметить, мы распознаем изображение на устройсте, потому что на облаке нам пришлось бы обрабатывать подключение к сети, наличие интернета на устройсте да и к тому же пришлось бы в Firebase Console создавать платежный аккаунт(год бесплатно), но при этом на устройтве мы не можем распознавать русский язык(по крайней мере мне не удалось).
Создадим синглтонный класс TextProcessor, но для начала разберемся в теории.
Каждый текст(FirebaseVisionText) состоит из блоков(TextBlock), они в свои очередь из линий(Line), а линии из элементов(Element), то бишь из символов.
На примере ниже красным показаны блоки, синим – линии, фиолетовым – элементы
Создаем метод process(). Этот класс я решил написать на Java
public String process(FirebaseVisionText firebaseVisionText) { StringBuilder resultText = new StringBuilder(); for (FirebaseVisionText.TextBlock block: firebaseVisionText.getTextBlocks()) { for (FirebaseVisionText.Line line: block.getLines()) { resultText.append(line.getText()).append("\n"); } resultText.append("\n"); } return resultText.toString().trim(); }
Здесь мы полученный текст после каждой линии переводился на следующую строку, а после каждого блока еще раз, чтобы был виден конец каждого “абзаца”.
Осталось добавить копирование полученного текста в буфер обмена
private fun copyTextToClipBoard() { val clipboardService = getSystemService(Context.CLIPBOARD_SERVICE) val clipboardManager: ClipboardManager = clipboardService as ClipboardManager val srcText: String = detected_text_view.text.toString() val clipData = ClipData.newPlainText("Source Text", srcText) clipboardManager.setPrimaryClip(clipData) }
Можно использовать этот метод при клике на detected_text_view или же при нажатии отдельной кнопки.
Проверим приложение
Небольшие выводы
Мы создали простенькое приложение для распознавания текста из фото, добавили возможность копирования в буфер обмена при помощи небольшого количества кода. Можно улучшить приложение, добавив в него сохранение результатов предыдущих распознанных текстов или же обрезание фото.
Документация библиотеки: ML Kit
Приложение в Google Play: Text Recognizer Line
Гитхаб проекта: Hawoline