Использование набора ML Kit для машинного обучения в Flutter-приложениях
Разработкой функций, связанных с использованием методов машинного обучения, обычно занимается отдельная команда специалистов. Но если вы работаете самостоятельно или в небольшой команде, и при этом хотите интегрировать в свое приложение продвинутые опции машинного обучения, вам поможет набор ML Kit.
В состав пакета ML Kit входит комплект API для работы с различными моделями машинного обучения. Все API при этом запускаются непосредственно на устройстве, подключение к интернету не требуется. В этой статье мы поговорим о том, как использовать ML Kit для распознавания текста в Flutter-приложении.
Мы подробно разберем следующие темы:
- что такое ML Kit;
- предоставление приложению доступа к камере пользовательского устройства;
- использование ML Kit для распознавания текста;
- распознавание email адресов на изображениях;
- применение виджета CustomPaint для выделения найденного текста.
Что такое ML Kit
ML Kit – это набор инструментов для работы с моделями машинного обучения, созданный разработчиками корпорации Google. Этот пакет работает непосредственно на пользовательском устройстве, без подключения к облачным сервисам. Это дает возможность создавать быстрые и надежные приложения, работающие в режиме реального времени и не передающие конфиденциальные данные в интернет.
Примечание: пакет ML Kit для Flutter на данный момент находится в стадии разработки и доступен только для платформы Android.
Существует пакет под названием firebase_ml_vision, обладающий похожей функциональностью. Этот набор использует облачный сервис машинного обучения, который подключается к базе данных Firebase и поддерживает обе платформы, (Android и iOS). К сожалению, разработка этого проекта прекращена, и его набор API отсутствует в последней версии Firebase SDK.
Создание нового проекта Flutter
Для создания нового проекта выполните приведенную ниже команду:
flutter create flutter_mlkit_vision
Откройте созданный проект в любой IDE-среде. Для открытия в редакторе VS Code выполните следующую команду:
code flutter_mlkit_vision
Обзор проекта
Приложение, которое мы создадим в рамках этого руководства, состоит из двух экранов. Первый экран CameraScreenвключает в себя предварительный просмотр области, видимой камере, и кнопку для фотографирования. Второй экран DetailScreen будет показывать результаты анализа изображения, отделяя текст от графики.
Скриншоты приложения
Готовое приложение будет выглядеть следующим образом:
Доступ к камере устройства
Для использования камеры в Flutter- приложении вам понадобится плагин под названием camera. Добавьте плагин в файл pubspec.yaml, выполнив следующую команду:
camera: ^0.8.1
Последнюю версию плагина можно найти на pub.dev.
Вместо демонстрационного кода, расположенного в файле main.dart вставьте в него код, приведенный ниже:
import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'camera_screen.dart'; // Глобальная переменная для хранения списка доступных камер List<CameraDescription> cameras = []; Future<void> main() async { // Создать список доступных камер до инициализации приложения try { WidgetsFlutterBinding.ensureInitialized(); cameras = await availableCameras(); } on CameraException catch (e) { debugPrint('CameraError: ${e.description}'); } runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter MLKit Vision', theme: ThemeData( primarySwatch: Colors.blue, ), home: CameraScreen(), ); } }
В приведенном выше коде я использовал метод availableCameras() для получения списка доступных камер.
Теперь мы создадим код для экрана приложения, который выводит предварительный просмотр видимой области и кнопку фотографирования.
class CameraScreen extends StatefulWidget { @override _CameraScreenState createState() => _CameraScreenState(); } class _CameraScreenState extends State<CameraScreen> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Flutter MLKit Vision'), ), body: Container(), ); } } 1. Создаем объект CameraController: // Внутри _CameraScreenState класса late final CameraController _controller;
- Создаем метод _initializeCamera(), инициализируем _controller контроллер камеры внутри него:
// Инициализация контроллера камеры для вывода предварительного просмотра на экран void _initializeCamera() async { final CameraController cameraController = CameraController( cameras[0], ResolutionPreset.high, ); _controller = cameraController; _controller.initialize().then((_) { if (!mounted) { return; } setState(() {}); }); }
У контроллера CameraController() есть два обязательных параметра.
CameraDescription – тип камеры (фронтальная, задняя). Здесь вы задаете тип камеры, к которой хотите получить доступ:
- 1 – для фронтальной;
- 0 – для задней.
ResolutionPreset – разрешение по умолчанию. Здесь вы задаете качество фотографий, снимаемых камерой.
- Вызываем метод внутри initState():
@override void initState() { _initializeCamera(); super.initState(); }
- Для предотвращения утечки памяти останавливаем _controller:
@override void dispose() { _controller.dispose(); super.dispose(); }
- Определим метод _takePicture() для фотографирования и сохранения изображения в файле. Метод будет возвращать путь к сохраненному файлу.
// Делает снимок выбранной камерой // Возвращает путь к файлу Future<String?> _takePicture() async { if (!_controller.value.isInitialized) { print("Контроллер не инициализирован"); return null; } String? imagePath; if (_controller.value.isTakingPicture) { print("Сохраняем фото..."); return null; } try { // Отключение вспышки камеры _controller.setFlashMode(FlashMode.off); // Сохранение в кроссплатформенном формате final XFile file = await _controller.takePicture(); // Возвращение пути к файлу imagePath = file.path; } on CameraException catch (e) { print("Камера недоступна"); return null; } return imagePath; }
Использование CRUD-операций с базой данных Cloud Firestore в Flutter-приложениях
- Теперь мы готовы приступить к созданию интерфейса для экрана захвата изображения CameraScreen. Интерфейс состоит из предварительного просмотра области, доступной камере, и кнопки для фотографирования. После того, как снимок сделан, экран приложения сменится на следующий, DetailScreen.
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Flutter MLKit Vision'), ), body: _controller.value.isInitialized ? Stack( children: <Widget>[ CameraPreview(_controller), Padding( padding: const EdgeInsets.all(20.0), child: Container( alignment: Alignment.bottomCenter, child: ElevatedButton.icon( icon: Icon(Icons.camera), label: Text("Click"), onPressed: () async { // Если появился путь к файлу // перейти на экран DetailScreen await _takePicture().then((String? path) { if (path != null) { Navigator.push( context, MaterialPageRoute( builder: (context) => DetailScreen( imagePath: path, ), ), ); } else { print('Путь к файлу не найден!'); } }); }, ), ), ) ], ) : Container( color: Colors.black, child: Center( child: CircularProgressIndicator(), ), ), ); }
На этом процесс создания функций по работе с камерой закончен. На следующем этапе мы будем анализировать сделанные снимки, и распознавать текст на них. Результаты анализа будут отображаться на экране DetailScreen.
Интеграция ML Kit в приложение
Импортируйте плагин google_ml_kit в свой файл pubspec.yaml:
google_ml_kit: ^0.3.0
Вам необходимо передать путь к файлу снимка в коде экрана DetailScreen. Основная структура экрана анализа изображения DetailScreen определяется следующим образом:
// Внутри image_detail.dart файла import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:google_ml_kit/google_ml_kit.dart'; class DetailScreen extends StatefulWidget { final String imagePath; const DetailScreen({required this.imagePath}); @override _DetailScreenState createState() => _DetailScreenState(); } class _DetailScreenState extends State<DetailScreen> { late final String _imagePath; late final TextDetector _textDetector; Size? _imageSize; List<TextElement> _elements = []; List<String>? _listEmailStrings; Future<void> _getImageSize(File imageFile) async { // Размер изображения выводится здесь } void _recognizeEmails() async { // Инициализация распознавания текста происходит здесь // анализ текста для поиска email адресов } @override void initState() { _imagePath = widget.imagePath; // Initializing the text detector _textDetector = GoogleMlKit.vision.textDetector(); _recognizeEmails(); super.initState(); } @override void dispose() { // Закрытие детектора текста после использования _textDetector.close(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Image Details"), ), body: Container(), ); } }
Начинаем работать с Flutter
В приведенном выше фрагменте кода мы провели инициализацию детектора текста ML Kit внутри метода initState(), а потом закрыли его в методе dispose(). Теперь вам потребуется определить два метода:
- _getImageSize() – для получения размера сделанного снимка;
- _recognizeEmails() – для распознавания текста и выявления в нем email адресов.
Получение размера снимка
Внутри метода _getImageSize() мы сначала получим снимок с помощью пути к файлу, а затем определим размер фотографии.
// Определение размера фото Future<void> _getImageSize(File imageFile) async { final Completer<Size> completer = Completer<Size>(); final Image image = Image.file(imageFile); image.image.resolve(const ImageConfiguration()).addListener( ImageStreamListener((ImageInfo info, bool _) { completer.complete(Size( info.image.width.toDouble(), info.image.height.toDouble(), )); }), ); final Size imageSize = await completer.future; setState(() { _imageSize = imageSize; }); }
Распознавание email адресов
Внутри метода _recognizeEmails() мы опишем всю процедуру распознавания и извлечения необходимой информации из фотографии. Я пошагово покажу, как получить адреса электронной почты из распознанного текста.
- Получаем снимок с использованием пути к файлу, вызываем метод _getImageSize():
void _recognizeEmails() async { _getImageSize(File(_imagePath)); }
- Создаем объект InputImage, используя путь к файлу, и запускаем распознавание текста на снимке:
// Создание объекта InputImage с использованием пути к файлу снимка final inputImage = InputImage.fromFilePath(_imagePath); // Получение распознанного текста из объекта final text = await _textDetector.processImage(inputImage);
- Теперь нам надо получить текст из объекта RecognisedText, и выделить из текста адреса электронной почты. Текст находится в blocks -> lines -> text (блоки -> строки -> текст).
// Стандартное выражение для поиска email адресов String pattern = r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$"; RegExp regEx = RegExp(pattern); List<String> emailStrings = []; // Обнаружение и сохранение строк for (TextBlock block in text.textBlocks) { for (TextLine line in block.textLines) { if (regEx.hasMatch(line.lineText)) { emailStrings.add(line.lineText); } } }
- Сохраняем извлеченный текст в переменной _listEmailStrings.
setState(() { _listEmailStrings = emailStrings; });
Создаем пользовательский интерфейс
Мы определили все методы и готовы перейти к созданию интерфейса для экрана DetailScreen, показывающего результаты анализа снимка. Интерфейс состоит из двух виджетов – первый отображает снимок, второй – демонстрирует распознанные email адреса.
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Image Details"), ), body: _imageSize != null ? Stack( children: [ Container( width: double.maxFinite, color: Colors.black, child: AspectRatio( aspectRatio: _imageSize!.aspectRatio, child: Image.file( File(_imagePath), ), ), ), Align( alignment: Alignment.bottomCenter, child: Card( elevation: 8, color: Colors.white, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Padding( padding: const EdgeInsets.only(bottom: 8.0), child: Text( "Identified emails", style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), ), Container( height: 60, child: SingleChildScrollView( child: _listEmailStrings != null ? ListView.builder( shrinkWrap: true, physics: BouncingScrollPhysics(), itemCount: _listEmailStrings!.length, itemBuilder: (context, index) => Text(_listEmailStrings![index]), ) : Container(), ), ), ], ), ), ), ), ], ) : Container( color: Colors.black, child: Center( child: CircularProgressIndicator(), ), ), ); }
Организация файлов приложения Flutter
Если переменная _imageSize окажется пустой, на экране появится индикатор загрузки изображения CircularProgressIndicator.
Экран анализа снимка до использования виджета для выделения текста выглядит следующим образом:
Цветное выделение текста
Для выделения распознанных адресов электронной почты цветной рамкой можно использовать виджет CustomPaint.
- Сначала нам потребуется модифицировать метод _recognizeEmails(), чтобы получить элементы текста TextElement из каждой строки.
List<TextElement> _elements = []; void _recognizeEmails() async { // ... // Обнаружение и сохранение текстовых строк и элементов for (TextBlock block in text.textBlocks) { for (TextLine line in block.textLines) { if (regEx.hasMatch(line.lineText)) { emailStrings.add(line.lineText); // Извлечение текстовых элементов и сохранение их в списке for (TextElement element in line.textElements) { _elements.add(element); } } } } // ... }
- Поместите виджет AspectRatio, содержащий изображение, внутрь виджета CustomPaint.
CustomPaint( foregroundPainter: TextDetectorPainter( _imageSize!, _elements, ), child: AspectRatio( aspectRatio: _imageSize!.aspectRatio, child: Image.file( File(_imagePath), ), ), ),
- Теперь нужно определить класс TextDetectorPainter, который будет расширением виджета CustomPainter.
// Помогает нарисовать цветную рамку // окружающую адреса электронной почты на изображении class TextDetectorPainter extends CustomPainter { TextDetectorPainter(this.absoluteImageSize, this.elements); final Size absoluteImageSize; final List<TextElement> elements; @override void paint(Canvas canvas, Size size) { // TODO: Define painter } @override bool shouldRepaint(TextDetectorPainter oldDelegate) { return true; } }
- Внутри метода paint() получаем размер отображаемой области изображения:
final double scaleX = size.width / absoluteImageSize.width; final double scaleY = size.height / absoluteImageSize.height;
- Определяем метод scaleRect(), который поможет окружить обнаруженный текст цветной рамкой.
Rect scaleRect(TextElement container) { return Rect.fromLTRB( container.rect.left * scaleX, container.rect.top * scaleY, container.rect.right * scaleX, container.rect.bottom * scaleY, ); }
- Определяем объект Paint:
final Paint paint = Paint() ..style = PaintingStyle.stroke ..color = Colors.red ..strokeWidth = 2.0;
- Используем TextElement для рисования прямоугольных цветных рамок:
for (TextElement element in elements) { canvas.drawRect(scaleRect(element), paint); }
После добавления цветного выделения экран анализа в приложении выглядит следующим образом:
Запуск приложения
Перед запуском следует убедиться в правильности настроек конфигурации приложения.
Настройки для Android
Перейдите в директорию проекта, откройте android -> app -> build.gradle и установите 26-ю версию minSdkVersion:
minSdkVersion 26
Теперь все готово к запуску.
Настройки для iOS
Плагин пока что не работает на этой платформе. Как только ML Kit начнет поддерживать iOS, информация о настройках появится в репозитории плагина на GitHub.
Заключение
Пакет ML Kit предоставляет набор моделей машинного обучения, которые легко интегрируются в любое мобильное приложение. Среди возможностей пакета – распознавание лиц и поз, идентификация дорожных ориентиров, сканирование штрих-кодов, маркировка изображений и многое другое. Для изучения всех возможностей пакета ML Kit посетите официальный сайт проекта.
Исходный код приложения, разработанного в рамках данного руководства, доступен на странице автора в GitHub.
Источник: www.internet-technologies.ru