Hola, Amigos! На связи Александр Чаплыгин, Flutter-dev в Amiga. В статье рассказываю о проекте, в котором использовалась библиотека Google ML Kit Barcode Scanning. И о своём первом опыте выступления на конференции для разработчиков DevFest.
Hola, Amigos! На связи Александр Чаплыгин, Flutter-dev в Amiga. В статье рассказываю о проекте, в котором использовалась библиотека Google ML Kit Barcode Scanning. И о своём первом опыте выступления на конференции для разработчиков DevFest.
Мы с командой Flutter-разработчиков Amiga ведем свой телеграм-канал Flutter.Много, где регулярно пишем интересные посты про кроссплатформенную разработку, делимся своим опытом, переводим статьи иностранных СМИ и анонсируем конфы, в которых будем участвовать. Подписывайтесь, нас там уже больше 1700!
Главный акцент моего выступления – библиотека 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;
}
});
По порядку:
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);
}
});
}
});
}
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());
}
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;
}
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--;
}
}
}
Теперь мы можем обрабатывать любые баркоды, получать их численно-буквенное представление.
Пора подвести итоги. Мы внедрили библиотеку Google ML Kit Barcode Scanning и, знаете, я бы лучше написал 1000 строк кода, чем один раз выступил😂 Но, как говорится, выходить из зоны комфорта необходимо, если ты хочешь расти. Я рад, что мне удалось посетить такое мероприятие, выступить в качестве спикера, и я очень благодарен организаторам. Надеюсь, что в будущем коммьюнити Flutter будет расти, и такие конференции станут чаще и доступнее для любого желающего их посетить.
Присоединяйтесь к нашему телеграм-каналу Flutter.Много, будем с вами чаще на связи!