Найти в Дзене

Переводчик с изнанки, или разработка из первых рук

На прошлой неделе мы писали про мобильный переводчик с изображения: На этой же мы приоткроем занавес и расскажем подробнее о процессе создания этого прототипа. А именно Как сделать overlay-переводчик для Android на Flutter. Мы рассмотрим в этой статье стадии решения поставленной задачи, применяемые технологии и осветим ключевые аспекты их использования. В данной статье мы не даём полное представление кодовой базы приложения, в ней будут приведены лишь наиболее релевантные фрагменты кода, непосредственно связанные с описываемыми технологиями и подходами. Требовалось разработать мобильное приложение для Android на Flutter, способное в реальном времени: Для создания нашего приложения мы использовали плагин для создания overlay (оверлея). Оверлей — приложение или части приложения (например, панель или виджеты), которые располагается поверх остальных. Мы выделили в разработке решения следующие этапы: Разбиваем этап на подзадачи: Для создания оверлея мы использовали плагин flutter_overlay_wi
Оглавление

На прошлой неделе мы писали про мобильный переводчик с изображения:

Мобильный переводчик - вместо 1000 слов!
MIR - Студия разработки умных устройств (Embedded NN Lab)3 апреля

На этой же мы приоткроем занавес и расскажем подробнее о процессе создания этого прототипа. А именно Как сделать overlay-переводчик для Android на Flutter.

Мы рассмотрим в этой статье стадии решения поставленной задачи, применяемые технологии и осветим ключевые аспекты их использования.

В данной статье мы не даём полное представление кодовой базы приложения, в ней будут приведены лишь наиболее релевантные фрагменты кода, непосредственно связанные с описываемыми технологиями и подходами.

Задача

Требовалось разработать мобильное приложение для Android на Flutter, способное в реальном времени:

  1. Распознать текст на экране устройства.
  2. Перевести обнаруженный текст на целевой язык.
  3. Наложить переведённые текстовые блоки поверх исходных, сохраняя их расположение и структуру.

Этапы

Для создания нашего приложения мы использовали плагин для создания overlay (оверлея). Оверлей — приложение или части приложения (например, панель или виджеты), которые располагается поверх остальных.

Мы выделили в разработке решения следующие этапы:

  1. Создание оверлея, который будет отображаться поверх любых приложений, и научить его делать скриншоты экранов этих приложений.
  2. Распознавание скриншота и получение текста и метаданных, необходимых для отрисовки виджета с переводом.
  3. Перевод текста
  4. Создание виджета с расположением переведенного текста поверх исходного текста

Первый этап

Разбиваем этап на подзадачи:

  • создать оверлей,
  • реализовать обмен данными между оверлеем и приложением,
  • реализовать захват экрана из оверлея.

Создание оверлея

Для создания оверлея мы использовали плагин flutter_overlay_window. Его функциональности хватает для создания оверлея, который будет показывать панель с кнопками управления или виджет с переводом в зависимости от состояния приложения. В оверлей передаётся виджет. В нашем случае один из двух виджетов: панель с кнопками, которая управляет переводчиком («Свернуть»/«Развернуть», «Закрыть», «Начать перевод») и виджет с переведённым текстом, который располагается в тех же областях экрана, что и исходный текст.

Панель с кнопками
Панель с кнопками

Наложение переведённого текста поверх оригинального
Наложение переведённого текста поверх оригинального

Обмен данными между оверлеем и приложением

Захват экрана должен выполняться при нажатии кнопки в оверлее. Но захват и обработка изображения экрана должны запускаться в основном приложении, а не в оверлее. То есть оверлей должен отправлять команду для запуска всей цепочки действий, в результате которых будут получены данные для отрисовки виджета с переводом. Было принято решение запускать оверлей в отдельном изоляте (Isolate) и управлять им с помощью команд.

Реализация захвата экрана
Реализация захвата экрана

В функцию sendMessage передаётся источник сообщения (приложение или оверлей) и команда. В соответствии с источником сообщения выбирается порт для отправки сообщения.

Реализация захвата экрана для перевода

Когда оверлей инициирует процесс перевода, первым шагом необходимо получить изображение экрана. Однако здесь мы сталкиваемся с существенными платформенными ограничениями из-за безопасности:

1. Ограничения стандартных решений. Существующие Flutter-плагины для скриншотов работают исключительно в рамках собственного приложения и не могут захватывать содержимое других приложений.

2. Решение через MediaProjection API. Для системного захвата экрана требуется использование Android MediaProjection API - специального системного интерфейса, который позволяет:

  • захватывать контент всего экрана;
  • получать скриншоты сторонних приложений;
  • записывать видео (в нашем случае не используется).

3. Особенности реализации. В использовании MediaProjection есть важные нюансы:

  • требует явного подтверждения пользователя через системный диалог;
  • отображает постоянную иконку записи в строке состояния;
  • ограничено политиками безопасности Android (начиная с версии 10+).

Так как MediaProjection API является частью Android SDK и не поддерживается напрямую из Flutter, то для того чтобы воспользоваться возможностями MediaProjection API из Flutter, нужно сделать несколько шагов.

1. Добавить разрешения в android/app/src/main/AndroidManifest.xml.

-5

2. Создать сервис захвата: android/app/src/main/kotlin/com/example/multilingual_app/ScreenshotService.kt.

3. Реализовать вызов MediaProjection в Kotlin.

-6

4. Связь с Flutter (MethodChannel).

-7

5. Сделать обработку полученного изображения.

-8

Второй этап

Распознавание текста и извлечение метаданных из скриншота

После получения скриншота мы приступили к распознаванию текста и его структуры. Для этого мы использовали пакет google_mlkit_text_recognition, предоставляющий мощные инструменты для обработки изображений с помощью ML-моделей Google.

Преобразование входных данных

На вход алгоритма поступает массив байтов (Uint8List), однако метод распознавания текста требует объект типа InputImage.

При попытке использовать InputImage.fromBytes() мы столкнулись с проблемой:

  • Метод требует обязательные метаданные (формат, ориентации др.),
  • При ручном задании параметров возникали ошибки, приводящие к некорректному распознаванию.

Решение:
Был применен альтернативный подход – сохранение массива байтов во временный файл и создание
InputImage через InputImage.fromFile().

-9

Результат распознавания

На выходе получаем:

  • распознанный текст (строка),
  • список TextBlock — структурные блоки текста.

TextBlock содержит:

  • текст и его координаты на изображении,
  • cornerPoints — массив точек описывающих границы блока (полигон, в котором расположен текст).

Эти данные критически важны для четвёртого этапа – наложения переведённого текста поверх исходного сохранением позиции и размера.

Координаты cornerPoints будут использоваться для:

  • позиционирования виджета с переводом,
  • маскировки исходного текста (если требуется),
  • адаптации под изменения масштаба/ориентации экрана.

Таким образом, на этом этапе не только извлекается текст, но и подготавливаются данные для точного визуального сопоставления с оригиналом.

-10

Третий этап

Перевод текста и обновление текстовых блоков

После успешного распознавания текста переходим к переводу с сохранением структуры исходных блоков. Для этого мы использовали пакет google_mlkit_translation, обеспечивающий машинный перевод на устройстве.

Примечание: качество перевода оставляет желать лучшего, но целью нашего приложения была попробовать сам подход к распознаванию с экрана, этот момент мы ещё проработаем и приручим какую-нибудь хорошую нейрушку для перевода.

Инициализация переводчика

1. Определение языков:

  • sourceLanguage – язык исходного текста (автоопределение или заданный);
  • targetLanguage – язык перевода (например, русский).

Коды языков должны быть преобразованы из формата BCP-47 (например,"en","ru") в тип TranslateLanguage:

Создание транслятора

-11

Процесс перевода

Для каждого TextBlock из распознанного текста:

1. извлекается исходный текст,

2. выполняется перевод,

3. сохраняется результат с сохранением структуры блока.

Формирование списка переведенных блоков

Результат — список обновленных TextBlock, где:

  • исходный текст заменён на переведенный,
  • все метаданные (координаты, размеры и т.д.) остаются неизменными.

Реализация:

-12

Итоговые данные

На выходе получаем:

  • translatedTextList – список переведенных строк (для логирования или проверки),
  • translatedBlocks – модифицированные TextBlock с переведенным текстом, но исходными координатами и структурой.

Зачем это нужно?

1. Точное наложение перевода – координаты из cornerPoints позволяют разместить переведенный текст поверх оригинального с пиксельной точностью.

2. Сохранение контекста – структурные элементы (разбивка на строки, блоки) остаются неизменными, что важно для читаемости.

3. Гибкость – можно дополнительно обрабатывать отдельные блоки (например, игнорировать знаки или числа).

Этот этап завершает подготовку данных для финального рендеринга — отрисовки перевода поверх интерфейса приложения.

Четвёртый этап

Реализация CustomPainter для отрисовки переведённого текста

Для визуализации переведённого текста с сохранением исходного позиционирования был создан специализированный CustomPainter – класс TranslatedTextPainter. Его задача — корректно отобразить текст поверх оригинального контента с учётом масштабирования экрана.

Ключевые особенности

1. Масштабирование координат.
Параметр ratio преобразует физические пиксели (из cornerPoints) в логические:

2. Метод paint().
Отрисовывает каждый TextBlock как:

  • фоновую подложку текста,
  • выделение границ блока,
  • переведённый текст.

3. Оптимизация перерисовки.
shouldRepaint перерисовывает виджет только при изменении данных или масштаба.

Интеграция с оверлеем

Для встраивания в интерфейс:

  1. Создаём CustomPaint с нашим TranslatedTextPainter:
  2. Разворачиваем на весь экран через _resizeOverlay:
-13

Визуальный результат

  • Текст отображается поверх оригинального контента,
  • Блоки повторяют форму и положение исходных элементов,
  • Поддержка динамического масштабирования (например, при повороте экрана).

Таким образом, система обеспечивает бесшовную интеграцию перевода в реальном времени.

Проект полностью можно посмотреть на гитхабе: https://github.com/Grovety/Android_translater

Возможно, вам также будет интересно: