Использование набора ML Kit для машинного обучения в Flutter-приложениях

Использование набора ML Kit для машинного обучения в Flutter-приложениях

 

Разработкой функций, связанных с использованием методов машинного обучения, обычно занимается отдельная команда специалистов. Но если вы работаете самостоятельно или в небольшой команде, и при этом хотите интегрировать в свое приложение продвинутые опции машинного обучения, вам поможет набор ML Kit.

В состав пакета ML Kit входит комплект API для работы с различными моделями машинного обучения. Все API при этом запускаются непосредственно на устройстве, подключение к интернету не требуется. В этой статье мы поговорим о том, как использовать ML Kit для распознавания текста в Flutter-приложении.

Мы подробно разберем следующие темы:

  • что такое ML Kit;
  • предоставление приложению доступа к камере пользовательского устройства;
  • использование ML Kit для распознавания текста;
  • распознавание email адресов на изображениях;
  • применение виджета CustomPaint для выделения найденного текста.
  • Заключение
  • Что такое ML Kit

    Использование набора ML Kit для машинного обучения в Flutter-приложениях

    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 будет показывать результаты анализа изображения, отделяя текст от графики.

    Скриншоты приложения

    Готовое приложение будет выглядеть следующим образом:

    Использование набора ML Kit для машинного обучения в Flutter-приложениях

    Доступ к камере устройства

    Для использования камеры в 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.

    Экран анализа снимка до использования виджета для выделения текста выглядит следующим образом:

    Использование набора ML Kit для машинного обучения в Flutter-приложениях

    Цветное выделение текста

    Для выделения распознанных адресов электронной почты цветной рамкой можно использовать виджет 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);
        }
    

    После добавления цветного выделения экран анализа в приложении выглядит следующим образом:

    Использование набора ML Kit для машинного обучения в Flutter-приложениях

    Запуск приложения

    Перед запуском следует убедиться в правильности настроек конфигурации приложения.

    Настройки для Android

    Перейдите в директорию проекта, откройте android -> app -> build.gradle и установите 26-ю версию minSdkVersion:

    minSdkVersion 26

    Теперь все готово к запуску.

    Настройки для iOS

    Плагин пока что не работает на этой платформе. Как только ML Kit начнет поддерживать iOS, информация о настройках появится в репозитории плагина на GitHub.

    Заключение

    Пакет ML Kit предоставляет набор моделей машинного обучения, которые легко интегрируются в любое мобильное приложение. Среди возможностей пакета – распознавание лиц и поз, идентификация дорожных ориентиров, сканирование штрих-кодов, маркировка изображений и многое другое. Для изучения всех возможностей пакета ML Kit посетите официальный сайт проекта.

    Исходный код приложения, разработанного в рамках данного руководства, доступен на странице автора в GitHub.

    Источник: www.internet-technologies.ru