Распознавание текста из фото при помощи ML Kit в андроид

Автор: | 22.05.2020

В настоящее время широкую популярность набирает машинное обучение. Так как обучение моделей трудозатратно, то к его применению в 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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *