Как стать автором
Обновить

Камера и Flutter

Уровень сложностиПростой
Время на прочтение6 мин
Количество просмотров4.1K

Hola, Amigos! На связи Александр Чаплыгин, Flutter-dev в Amiga. Недавно я выступал на конференции для разработчиков DevFest в Омске с докладом «Камера и Flutter». Решил поделиться с вами своим первым опытом выступления. Возможно, кому-то будет полезно понять, как это устроено изнутри. И расскажу про проект, в котором использовалась библиотека Google ML Kit Barcode Scanning.

Мы с командой Flutter-разработчиков Amiga ведем свой телеграм-канал Flutter.Много, где регулярно пишем интересные посты про кроссплатформенную разработку, делимся своим опытом, переводим статьи иностранных СМИ и анонсируем конфы, в которых будем участвовать. Подписывайтесь, нас там уже больше 1600!  

Немного о DevFest

Начну с того, что Омск – приятный город. Теплота Сибири, ровные дороги и отзывчивые люди. Город с очень богатой историей, которая не оставит вас равнодушным, если вы захотите в нее погрузиться. Забегая вперед, скажу, что приятные впечатления для меня оставил не только город, но и конференция, организованная компанией Effective. 

Для размещения нам предоставили только что построенную современную гостиницу, что с самого начала дало понять, что путешествие будет на должном уровне. DevFest был рассчитан на 3 дня и 10 треков, среди которых: 

  • Flutter;

  • iOS;

  • Android;

  • Аврора;

  • Golang;

  • Python;

  • Web;

  • и др.

Для меня, как для Flutter-разработчика, самым значимым был первый день Flutter-трека, где принимали участие такие компании как: Amiga, Effective, Яндекс.Про, Яндекс.Go. Это было мое первое выступление в качестве спикера и, надеюсь, не последнее ?

В целом, не могу сказать, что людям технических специальностей легко выступать. Волновался ли я? Да, конечно. Вспомнил ли я времена своего диплома? Да, конечно. Но в этот раз, в отличие от диплома, я был уверен, что никто не утонет.

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

Для себя я бы выделил темы Тимура Моисеева (ML и Flutter, Amiga), Геннадия Евстратова (Flutter на Авроре, Яндекс.Go), Сергея Кольцова (Как одной командой писать полсотни приложений в 2 раза быстрее, Яндекс.Про).

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

У Геннадия была озвучена проблематика разработки на Flutter под Аврору (спойлер – трудно). Приобретение смартфона на Авроре и трудности с установкой такого эмулятора приводит к высокому порогу входа в эту нишу. А Сергей рассказал про преимущества Flutter перед нативом и про то, как легко билдить приложения под разные целевые аудитории. 

В своем выступлении я затронул тему камеры и Flutter. Работал над проектом под NDA, в котором использовалась библиотека Google ML Kit. Эта библиотека предназначается для распознавания различных объектов: текста, лиц, баркодов и т.д. Также я рассказал про то, как мы интегрировали КриптоПро SDK и подключили рутокен (nfc и носитель). 

Камера и Flutter 

Главный акцент моего выступления – библиотека Google ML Kit Barcode Scanning. Ее мы использовали для распознавания баркодов разных форматов и видов. Баркод – графическое средство представления цифровых, либо (и) буквенных данных. Библиотека распознает линейные и 2Д форматы баркодов. Из популярных форматов – это Datamatrix, QR-code, EAN-13, EAN-39. Вот так они выглядят:

То есть наша библиотека по сути считывает данный «штрихкод» и переводит его в численно-буквенное представление. Google ML Kit Barcode Scanning имеет высокую скорость обработки баркода, меньше 0,5 секунды, но она не без изъянов. Дело в том, что в численно-буквенном представлении Datamatrix код выглядит как то так:

{FNC1}010460843993429621JgXJ5.T<GS>93Mlcr

Жирным выделены разделители баркода. То есть все, что между ними – смысловые блоки. Серийный номер, крипточасть и другая информация о товаре (вес, рост, ширина, вышина, глубина и т.п.).

И проблема в том, что этот пакет подменяет разделитель FNC1 на GS, что создает неудобства для наших пользователей.

Решили мы данный изъян путем дополнительной отправки изображения на сервер, где ML сервера распознавала баркод в его настоящем виде. На картинке изображена схема обработки баркода в нашем приложении:

Тут, конечно, мы потеряли в производительности: по метрикам процесс обработки одного баркода с участием двух ML занимал от 6 до 10 секунд. Согласитесь, что долгое ожидание в приложении нервирует современного пользователя :)

Но после упрощения правил формирования баркодов пользователями, мы убрали этот запрос на сервер и получили прирост производительности. С участием только Google ML Kit это 4-5 секунд. При этом, заказчик хочет внедрить ML на сервере в наше приложение, что даст гарантию абсолютно правильного распознавания баркода и позволит нам уйти от использования сервиса Google в большом и серьезном приложении.

Расскажу про процесс интеграции этой самой библиотеки и распознавания баркода. Для начала мы получаем все нужные разрешения на доступ к камере. 

<uses-feature
   android:name="android.hardware.camera"
   android:required="true" />
<key>NSCameraUsageDescription</key>
<string>Need access to scan barcodes</string>

Затем подключаем сами библиотеки.

#camera
camera: ^0.10.5+4
google_mlkit_barcode_scanning: ^0.9.0

Дальше мы использовали mixin ввиду того, что у нас имеется множество страниц с камерами под разные нужды.

// Note: request() will check permission without request popup if already granted
 isPermissionGranted = await Permission.camera.request().isGranted;


 if (isPermissionGranted == true) {
   Future.delayed(const Duration(milliseconds: 200), () async {
     cameraController = await CameraUtils.getCameraController(
         ResolutionPreset.high, CameraLensDirection.back);
     await cameraController?.initialize(); // will throw CameraException if already disposed
     if (cameraController?.value.isInitialized == true) {
       if (mounted) {
         await cameraController?.lockCaptureOrientation(DeviceOrientation.portraitUp);
       }
       await _startImageStream();
       _stopped = false;
       if (mounted) {
         setState(() {});
       }
     }
   });
 } else {
   if (mounted) {
     setState(() {});
   }
   _stoppedByLifeCycle = true;
 }
});

По порядку:

  1. Проверяем разрешение на доступ к камере.

  2. Создаем контроллер. Инициализируем его. Запускаем Stream.

Future<void> _startImageStream() async {
 await guarded(() async {
   if (cameraController?.value.isStreamingImages == false) {
     await cameraController?.startImageStream((image) {
  if (!_isDetecting && DateTime.now().difference(_lastScan).inMilliseconds >= 200) {
         _toggleDetectionLock();
         handleCameraImage(image);
       }
     });
   }
 });
}
  1. Делаем проверку на то, распознается ли сейчас баркод и прошло ли больше 200 мс с последнего сканирования.

final barcodes = await ScannerUtils().detect(
 image: image,
 detectInImage: _barcodeDetector.processImage,
 imageRotation: controller.description.sensorOrientation,
);
// Удалим из результатов штрихкоды за прицелом сканера (с периферии экрана)
if (barcodes.isNotEmpty) {
 if (controller.description.sensorOrientation == 90 ||
     controller.description.sensorOrientation == 270) {
   _removePeripherialObjects(barcodes, image.height.toDouble(), image.width.toDouble());
 } else {
   _removePeripherialObjects(barcodes, image.width.toDouble(), image.height.toDouble());
 }
  1. Закидываем байты изображения с контроллера в нашу библиотеку. _barcodeDetector — экземпляр класса BarcodeScanner.

Future<List<Barcode>> detect({
 required CameraImage image,
 required Future<List<Barcode>> Function(InputImage image) detectInImage,
 required int imageRotation,
}) async {
 final results = <Barcode>[];
 final bytes = _concatenatePlanes(image.planes);


 results.addAll(await detectInImage(
   InputImage.fromBytes(
     bytes: bytes,
     metadata: buildMetaData(image, rotationIntToImageRotation(imageRotation)),
   ),
 ));


 if (results.isNotEmpty) {
   return results;
 }


 final img_lib.Image? img = convertCameraImageToImageColor(image, true, pngFormat: false);
 final List<Barcode> resultsWhite = await detectInImage(
   InputImage.fromBytes(
     bytes: img?.getBytes() ?? Uint8List.fromList([]),
     metadata: buildMetaData(image, rotationIntToImageRotation(imageRotation)),
   ),
 );
 results.addAll(resultsWhite);


 return results;
}
  1. Две проверки. Если ML сразу ничего не нашла, то инвертируем изображение и снова отправляем в ML.

void _removePeripherialObjects(List<Barcode> barcodes, double width, double height) {
 final currentWidth = width * 0.76;
 final squareRect = Rect.fromCenter(
     center: Offset(width / 2, height / 2), width: currentWidth, height: currentWidth);
 for (int i = 0; i < barcodes.length; i++) {
   final barcode = barcodes[i];
   final boundingBox = barcode.boundingBox;
   final intersect = squareRect.intersect(boundingBox);
   if (boundingBox == null ||
       intersect.width < boundingBox.width * 0.5 ||
       intersect.height < boundingBox.height * 0.5) {
     barcodes.removeAt(i);
     i--;
   }
 }
}
  1. Удаляем коды за «прицелом» — квадратом на экране. Если больше половины баркода заходит в «прицел» — сохраняем результат, если нет — удаляем.

Теперь мы можем обрабатывать любые баркоды, получать их численно-буквенное представление.

Пора подвести итоги. Мы внедрили библиотеку Google ML Kit Barcode Scanning и, знаете, я бы лучше написал 1000 строк кода, чем один раз выступил? Но, как говорится, выходить из зоны комфорта необходимо, если ты хочешь расти. Я рад, что мне удалось посетить такое мероприятие, выступить в качестве спикера, и я очень благодарен организаторам. Надеюсь, что в будущем коммьюнити Flutter будет расти, и такие конференции станут чаще и доступнее для любого желающего их посетить.

Присоединяйтесь к нашему телеграм-каналу Flutter.Много, будем с вами чаще на связи!

Теги:
Хабы:
Всего голосов 9: ↑9 и ↓0+9
Комментарии9

Публикации

Истории

Работа

Swift разработчик
42 вакансии
iOS разработчик
24 вакансии

Ближайшие события

27 августа – 7 октября
Премия digital-кейсов «Проксима»
МоскваОнлайн
28 сентября – 5 октября
О! Хакатон
Онлайн
3 – 18 октября
Kokoc Hackathon 2024
Онлайн
10 – 11 октября
HR IT & Team Lead конференция «Битва за IT-таланты»
МоскваОнлайн
25 октября
Конференция по росту продуктов EGC’24
МоскваОнлайн
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн