Как врач на пенсии создал AI‑приложение для определения биологического возраста на Flutter + TFLite. android.. android. rustore.. android. rustore. биологический возраст.. android. rustore. биологический возраст. вайбкодинг.. android. rustore. биологический возраст. вайбкодинг. здоровье.. android. rustore. биологический возраст. вайбкодинг. здоровье. мобильная разработка.. android. rustore. биологический возраст. вайбкодинг. здоровье. мобильная разработка. Разработка мобильных приложений.. android. rustore. биологический возраст. вайбкодинг. здоровье. мобильная разработка. Разработка мобильных приложений. сканер.

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

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

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

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

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

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

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

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

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

  • 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 работает практически вслепую. Проблема в том, что она обучалась на стандартизированных изображениях лиц, а у меня в приложении обрабатываются случайные кадры с камеры разного размера.

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

Как выяснилось, получаемый из камеры после анализа моделью биологический возраст — величина очень нестабильная. Освещение, угол съемки, макияж, усталость или плохой сон могут дать погрешность в 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

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

Автор: Valerii_first

Источник