Покажу, как написать мобильное приложение, "понимающее", одел человек маску или нет. Работать будем с живым изображением с камеры телефона и в реальном времени оценивать наличие маски.
Данный пример не требует от приложения активного интернет-соединения и каких-либо дополнительных затрат.
Основная цель примера - показать, как работает ИИ на прикладном уровне, не вдаваясь в подробности.
Для работы нам понадобится готовая "натренированная"
модель. Для тренировки предполагается использование вручную размеченных примеров, в данном случае - понадобится как можно больше фотографий с людьми в масках и без масок. Своих фоток я делать не буду,
датасеты для экспериментов можно скачать вот здесь:
https://www.kaggle.com/datasets?search=maskПоскольку наличие нескольких человек в кадре требует дополнительной разметки для тренировки модели, то для упрощения задачи предлагаю использовать только те фото, где по одному человеку в кадре. Хотя, как показывает практика, программа в итоге будет довольно успешно определять всех людей в кадре, несмотря на то, что тренировали мы ее только на единичных снимках.
На выходе у нас должно быть два файла, добавляемых в папку assets проекта - это экспортированная с сайта выше модель в формате tflite (TensorFlow Lite), и текстовый файл с метками, по одной в каждой строке. Полученный размер модели вряд ли превысит 10 Мб.
Порядок меток зависит от порядка данных в модели, которую вы тренировали. если программа будет путать - просто поменяйте первую и вторую строку местами.
В проект на Flutter добавим два пакета - camera и tflite.
Я немного пожертвовал принципами написания кода для того, чтобы упростить код и вместить весь экран в один файл, тем не менее он полностью готов к работе:
import 'package:camera/camera.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:tflite/tflite.dart';
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
List<CameraDescription>? cameras;
CameraImage? imgCamera;
CameraController? cameraController;
bool isWorking = false;
String result = '';
@override
void initState() {
super.initState();
loadModel();
initCamera();
}
loadModel() async {
//загружаем данные TensorFlow
await Tflite.loadModel(
model: 'assets/model.tflite',
labels: 'assets/labels.txt',
);
}
initCamera() async {
//инициализируем камеру телефона - автоматически
//будет запрос на разрешение, и если все нормально,
//то запустится потоковое обновление картинки с камеры
cameras = await availableCameras();
//cameras[] содержит камеры устройства,
//0 - основная, 1 - сэлфи
cameraController = CameraController(cameras![0], ResolutionPreset.medium);
cameraController! .initialize() .then((value) {
if (!mounted) return; //если камеру не разрешили - завершаем работу
setState(() {
cameraController! .startImageStream((imageFromStream) => {
//обязательно делаем проверку, не занят ли
//телефон обработкой кадра
if (!isWorking)
{
isWorking = true,
imgCamera = imageFromStream,
runModelOnFrame(),
}
});
});
});
}
runModelOnFrame() async {
//сам блок работы над кадром
//распознанных объектов может быть несколько, тогда
//надо будет запустить итератор над recognitions
if (imgCamera != null) {
var recognitions = await Tflite .runModelOnFrame(
bytesList: imgCamera! .planes.map((plane) {
return plane .bytes;
}) .toList(),
imageHeight: imgCamera! .height,
imageWidth: imgCamera! .width,
imageMean: 127.5,
imageStd: 127.5,
numResults: 1, //количество распознаваемых объектов в кадре
asynch: true,
);
result = recognitions! .first["label"];
//список recognitions содержит такие поля, как
//double confidence - степень уверенности
//int index - номер найденного образа в модели
//String label - метка
//TODO...именно здесь в зависимости от результата можно
//добавить свою логику работы.
setState(() {
//после завершения анализа обновляем экран с новым
//результатом и разрешаем отправку еще одного кадра
//на анализ
result;
});
isWorking = false;
}
}
@override
Widget build(BuildContext context) {
// рабочий экран приложения - в заголовке
//результат от TFLite, в теле - вид из камеры
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text(result),
centerTitle: true,
),
body: Container(
child: (!cameraController! .value .isInitialized)
? Container()
: CameraPreview(cameraController!),
),
));
}
}
Естественно, достаточно натренировать другую модель, и ваше приложение будет находить в кадре что угодно, при этом модель может содержать неограниченное количество распознаваемых объектов. Помимо масок и бананов она может распознавать людей, предметы в их руках и т.п.
Качество работы напрямую зависит от того, какими фотками вы ее тренировали. Например, если использовать фотографии преимущественно темнокожих людей, то на моей бледной роже по мнению программы всегда одета маска, это если ноздри не раздувать и смотреть прямо. Но стоит немного повернуть голову - и программа уже начинает предполагать, что я без маски, так что по уму надо не просто слушать, что говорит ИИ, а еще и делать самостоятельную выборку по его "предсказаниям". Да хотя бы собрать 100 срабатываний и посмотреть, каких больше и с какой достоверностью.
Данный код работает только на мобильных устройствах и требует минимальную версию андроид 5.0 (minSdkVersion 21 в build. gradle)
Если хотим, чтобы проект работал через web, то вместо пакета camera надо будет использовать camera_web, там есть ряд своих ограничений, в частности, там нет стриминга видео, а "живая" картинка для анализа будет доступна по сети:
Image .network(capturedImage.path);
Но это уже отдельный разговор )