- BrainTools - https://www.braintools.ru -

Как врач на пенсии создал AI‑приложение для определения биологического возраста на Flutter + TFLite

Я врач с более чем 30-летним стажем, последнее время работал доцентом медицинского вуза. Недавно я вышел на пенсию и получил то, о чем мечтал всю трудовую жизнь — много свободного времени, и часть его желательно тратить с пользой (а не только играть на ПК, что я, конечно, тоже люблю).

Я давно, со студенчества, интересовался проблемами старения организма. Поработав с десятками тысяч пациентов, я уже не удивлялся, что паспортный (хронологический) возраст и реальное состояние организма — это две разные вещи. Один пациент в 60 бегает марафоны, другой в 45 еле ходит по дому. Но причины этого медицине, честно говоря, до сих пор непонятны. Мне хотелось как‑то объективизировать, оцифровать, что ли, эту разницу без сложных анализов и дорогостоящих исследований. И чтобы каждый человек мог легко и просто оценить свой биологический возраст — не просто для того, чтобы его узнать — а сделать определенные выводы и, возможно, изменить свои привычки и даже обратиться к специалистам.

Теперь для этого есть общедоступные инструменты — смартфоны с фотокамерой, искусственный интеллект [1] и модели, способные анализировать состояние кожи и другие показатели по фотографии. Надо попробовать, решил я. Но для этого нужно создать мобильное приложение. Раньше я бы на такое не замахнулся, ну а сейчас — время есть, инструменты есть, желание есть — why not?

Раньше я знал про алгоритмы, циклы и условия только теоретически. Я мог немного читать чужой код и более‑менее понять его логику [2], так как интересовался программированием давно, да и по работе пришлось осваивать компьютеры, начиная с MS DOS и Norton Commander. Но полноценно писать программы на каком‑либо языке не умел — не хватало времени, усидчивости и всегда чего-то еще. А тут такой подарок — ИИ как помощник программиста, который может в код и, говорят, вполне неплохо справляется — как им не воспользоваться?

Сначала я, как и многие, баловался: генерировал песни, рисовал картинки, делал видео. Но потом решил: а почему бы не написать свое мобильное приложение — надо рискнуть Звучит амбициозно для человека, который не написал ни одной программы? Я тоже так думал, но в итоге пенсионер‑медик с базовыми знаниями if/else создал бесплатное приложение LifeLens AI, которое определяет биологический возраст по лицу, прошло модерацию в RuStore и работает полностью локально на устройстве.

В статье я сделаю акцент не на «магию AI», как хотел вначале, а на реальных инженерных проблемах: интеграции TensorFlow Lite, работе с камерой на разных устройствах и нюансах публикации в российском сторе, поскольку это может быть кому‑нибудь полезно (ну я надеюсь).

Архитектура приложения и выбор стека

Погуглив информацию про подобные приложения, я понял: главное требование — это конфиденциальность. Фотографии лица — это биометрические данные, и отправлять их на сервер я не хотел категорически (хлопот потом меньше с приватностью). Поэтому архитектура была выбрана строго Serverless / On‑device, все должно храниться и обрабатываться локально. Именно такую архитектуру я задал ИИ, в роли которого выступал Qwen 3.6 — 3.7. Почему именно он? У меня был опыт [3] работы с ним, мне нравится его стиль общения, он бесплатен, длинные сессии, как‑то так. Что и как было реализовано в итоге?

Технологический стек:

  • Frontend: Flutter (Dart). Был выбран за кроссплатформенность и отличную работу с нативными функциями камеры по совету ИИ. Я доволен этим выбором, Flutter оказался для меня вполне понятной вещью и к концу разработки я стал неплохо разбираться в коде (даже сам правил импорты и стили за ИИ). Хотя, как я потом понял, в современных российских реалиях кроссплатформенность это Андроид и все. У меня была идея сразу делать и под IOS, но куда и как потом это загружать? Ну и Аврора в Русторе — это мне вообще ни о чем не говорит.

  • Face Detection: библиотека Google ML Kit (google_mlkit_face_detection) выбрана по совету ИИ. Легко встроилась в проект, используется для первичной детекции лица и получения bounding box (координат). Работает локально, что мне и нужно. Как я вообще понял, универсальная и очень полезная штука, уже использую ее в другом разрабатываемом мной приложении.

  • Age Estimation: TensorFlow Lite (tflite_flutter) — нашли в инете кастомную модель, обученную на визуальных маркерах старения, дообучили в Googlt Colаb на датасетах разных возрастов. Интересный и нужный опыт, теперь я могу делать собственные модели под свои конкретные нужды. Инференс происходит прямо на телефоне, что и требуется.

  • Storage: shared_preferences. Для локального хранения результатов сканирований и логики стабилизации.

  • Backend: отсутствует, приложение работает полностью автономно после установки на гаджет пользователя.

Схема архитектуры приложения

Схема архитектуры приложения

Интеграция TensorFlow Lite: Подводные камни

Самая сложная в проекте техническая часть — связать детекцию лица и оценку возраста. И дело тут не в формулах и расчетах, это как раз уже все имеется. Я столкнулся с проблемой, что Модель TFLite требует на вход строго определенный формат данных (обычно тензор фиксированного размера, например, 224×224 пикселей), а камера выдает изображение произвольного разрешения. Пришлось помучиться, возможно, есть какие‑то готовые решения, но я столкнулся с этим впервые.

Загрузка модели

Модель хранится в ассетах приложения, структура разработана ИИ, я в ней достаточно быстро разобрался — она логична и в принципе, как я понял, стандартна для мобильных приложений на Flutter. Важно загружать её асинхронно, чтобы не блокировать UI при старте.

// age_estimator.dart
static Future<void> loadModel() async {
  try {
    // Загрузка интерпретатора TFLite из ассетов
    _interpreter = await Interpreter.fromAsset('assets/model/bio_age_model.tflite');
    debugPrint('✅ Модель TFLite успешно загружена');
  } catch (e) {
    debugPrint('❌ Ошибка загрузки модели: $e');
  }
}

Почему это важно для моего приложения:

  1. Пользователь видит экран камеры сразу, а не белый экран с крутящимся спиннером.

  2. Модель ML Kit тоже грузится параллельно. Если грузить всё последовательно, старт займет 7–10 секунд, это негативный момент для пользователя.

  3. На слабых телефонах (а я полагаю, они есть, и немало) синхронная загрузка может вызвать так называемый ANR (Application Not Responding) — то есть система предложит закрыть приложение.

Синхронизация координат

Еще одна проблема, с которой я столкнулся и успешно решил ее с помощью ИИ — Google ML Kit возвращает координаты лица относительно исходного изображения. Эти координаты нужно передать в TFLite, но модель ожидает нормализованные данные или конкретный crop. Пришлось разбираться что это такое и как это сделать. Сделали так — в функции _analyzeFace мы сначала получаем фото, затем прогоняем его через детектор лиц, и только потом, имея boundingBox, делаем инференс возраста:

// camera_screen.dart (фрагмент логики анализа)
final inputImage = InputImage.fromFilePath(photo.path);
final faces = await _detector.processImage(inputImage);

if (faces.isEmpty) {
  _status = "🔍 Лицо не найдено. Попробуйте ещё раз.";
  return;
}

final face = faces.first;
// Получаем координаты лица для передачи в модель возраста
final boundingBox = [
  face.boundingBox.left.toDouble(), 
  face.boundingBox.top.toDouble(), 
  face.boundingBox.width.toDouble(), 
  face.boundingBox.height.toDouble()
];

// Инференс модели возраста с учетом координат лица
rawModelOutput = await AgeEstimator.predictAgeRaw(File(photo.path), boundingBox);

Эта связка позволяет модели анализировать именно лицо, а не фон, что критически важно для точности расчета показателей. Без этого шага модель TFLite работает практически вслепую. Проблема в том, что она обучалась на стандартизированных изображениях лиц, а у меня в приложении обрабатываются случайные кадры с камеры разного размера.

Алгоритм стабилизации результатов: Борьба с шумом

Как выяснилось, получаемый из камеры после анализа моделью биологический возраст — величина очень нестабильная. Освещение, угол съемки, макияж, усталость или плохой сон [4] могут дать погрешность в 5–7 лет за одно измерение. Если выдавать пользователю каждый раз разные цифры с таким разбросом, он потеряет доверие к приложению.

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

Логика работы:

  1. Фаза калибровки (Дни 1–4): Пользователь должен сделать 3 скана в день. Мы не показываем финальный результат сразу, а накапливаем статистику.

  2. Якорь 1: Рассчитывается среднее значение за первые дни.

  3. Ограничение вариативности: Если результат нового скана отклоняется от текущего «якоря» более чем на ±1 год, мы искусственно ограничиваем его. Это отсекает выбросы (шум). Спорный момент, но иначе с технологий оценки по лицу при разных (неидеальных) условиях нельзя, в чем я убедился на собственном опыте. Кроме того, в приложении используется учет других важнейших факторов биовозраста с помощью анкеты, они имеют свой вес, существенно влияющий на итоговый результат.

  4. Якорь 2: На 4-й день фиксируется итоговый базовый биологический возраст.

  5. Фаза мониторинга (День 5+): Достаточно 1 скана в день. Результат сравнивается с якорем 2, и пользователь видит динамику.

Реализация лимита сканов выглядит так:

// camera_screen.dart
// Динамический лимит: 3 скана для дней 1-4, 1 скан для дня 5+
Future<int> _getMaxScans() async {
  final prefs = await SharedPreferences.getInstance();
  final totalScanDays = prefs.getInt('total_scan_days') ?? 0;
  
  // Если прошло меньше 4 дней калибровки - требуем 3 скана
  return (totalScanDays >= 4) ? 1 : 3;
}

Этот подход превращает приложение из «разовой игрушки» в инструмент для отслеживания долгосрочной динамики, что я и планирую сделать в следующей версии.

Работа с камерой: адаптивность под разные экраны

Сначала я пытался задать стандартные пропорции камеры для всех типов гаджетов по идее, что это проще. Но оказалось, что задать-то пропорции можно (например, 3:4 или 9:16), но они отличаются на iPhone, Xiaomi и Samsung. В результате — я пробовал, получается ужасно, пропорции лица искажаются или появляются полосы по краям экрана, а ведь именно этот экран обеспечивает вау‑эффект и неестественно вытянутое лицо сразу отпугнет пользователя. Выход я придумал, а код подсказал ИИ — нужно брать реальные данные от контроллера камеры. Как это реализовано: в build методе — я вычисляю previewRatio как говорит ИИ, «на лету»:

// camera_screen.dart
// Получаем реальное соотношение сторон камеры
final previewSize = _controller!.value.previewSize!;
// Для портретного режима меняем width и height местами
final previewRatio = previewSize.height / previewSize.width;

return Scaffold(
  body: Stack(
    fit: StackFit.expand,
    children: [
      // Используем AspectRatio с динамическим коэффициентом
      Center(
        child: AspectRatio(
          aspectRatio: previewRatio, 
          child: CameraPreview(_controller!),
        ),
      ),
      // ... остальной UI поверх камеры
    ],
  ),
);

Также для я добавил блокировку ориентации в main.dart, чтобы упростить верстку и гарантировать корректную работу камеры только в портретном режиме (моя идея):

// main.dart
await SystemChrome.setPreferredOrientations([
  DeviceOrientation.portraitUp,
  DeviceOrientation.portraitDown,
]);

Это решило проблему искажений — по крайней мере на всех протестированных устройствах — я использовал планшет и смартфон Samsung, IPhone 13, Huawei.

Публикация в RuStore: нюансы для Flutter‑разработчика

Публикация медицинского приложения или приложения для здоровья в России имеет свои особенности. Методом проб, ошибок и подсказок ИИ я понял — вот что важно:

1. Формат файла: AAB vs APK

Хотя Flutter по умолчанию собирает APK, для RuStore (как и для Google Play) лучше использовать формат AAB (Android App Bundle). Почему лучше: RuStore поддерживает функцию подписания приложений App Signing. То есть, вы загружаете AAB, подписанный своим upload‑ключом, а магазин переподписывает его своим ключом распространения. Это считается более безопасным вариантом и позволяет магазину оптимизировать размер приложения под разные архитектуры (arm64, x86 и так далее).

Команда для сборки:

flutter build appbundle --release

2. Модерация и статус приложения

Так как приложение касается здоровья, модераторы внимательно смотрят на описание, это нужно учитывать.

  1. Важно: нужно четко указать, что приложение не является медицинским. Такой софт подлежит обязательной регистрации в Росздравнадзоре, а также может потребоваться лицензирование.

  2. Формулировка: как же описать области применения приложения? Используйте термины «wellness», «мониторинг/трекер образа жизни», «оценка визуальных маркеров». Избегайте слов «диагностика», «лечение», «клинический» и подобного.

  3. Разрешения: поскольку приложение запрашивает доступ к камере и интернету (нужен только для первой загрузки моделей ML Kit) обязательно напишите понятный комментарий для модератора. Я указал так: «Камера используется только локально для анализа, фото не сохраняются и не отправляются на сервер». Это сняло вопросы о приватности.

  4. Версионирование: я столкнулся с проблемой, что нужно прямо указывать номер версии в файлах, несколько раз приложение из‑за этого не загружалось в стор. При обновлении приложения значение versionCode в android/app/build.gradle (или pubspec.yaml) должен быть строго больше предыдущего. Иначе загрузка будет отклонена, например:

    • Было: version: 1.0.0+1

    • Стало: version: 1.0.1+2

Заключение

Итак, за несколько недель я создал работающее приложение, которое прошло модерацию в магазине и решает реальную задачу. Я доволен результатом, получил громадный опыт мобильной разработки, узнал кучу нужных и не очень вещей из области IT‑технологий. Теперь посмотрим:

Что мне дал AI:

  • Генерацию boilerplate‑кода (настройка роутинга, темы, базовых виджетов).

  • Помощь в дебаггинге (поиск ошибок в логике стабилизации, расчет пропорций камеры).

  • Написание текстов для политики конфиденциальности и описания для стора.

Что делал я:

  • Постановка задачи и предметная экспертиза (медицинская логика, статистические расчеты).

  • Архитектурные решения (камера, локальный ML, стабилизация).

  • Контроль качества и тестирование на реальных устройствах.

Мой вывод: AI не заменил полностью разработчика в моем проекте, но сегодня он реально стал удивительным и мощным инструментом, позволяющим специалистам из других областей (как я) фокусироваться на идее и логике продукта, а не на синтаксисе. Я уверен для тех, кто давно хотел попробовать себя в IT, но боялся начать — сейчас самое время, дерзайте и все получится! Всем добра!

P. S.

Ссылка на приложение в RuStore: https://www.rustore.ru/catalog/app/com.lifelens.app [5]

GitHub репозиторий: https://github.com/m0nk0/Lifelens‑free [6]

Автор: Valerii_first

Источник [7]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/32512

URLs in this post:

[1] интеллект: http://www.braintools.ru/article/7605

[2] логику: http://www.braintools.ru/article/7640

[3] опыт: http://www.braintools.ru/article/6952

[4] сон: http://www.braintools.ru/article/9809

[5] https://www.rustore.ru/catalog/app/com.lifelens.app: https://www.rustore.ru/catalog/app/com.lifelens.app

[6] https://github.com/m0nk0/Lifelens‑free: https://github.com/m0nk0/Lifelens-free

[7] Источник: https://habr.com/ru/articles/1054292/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1054292

www.BrainTools.ru

Rambler's Top100