Реализация AI-генерации аватаров по фотографии в мобильном приложении
Задача звучит просто: пользователь загружает селфи — приложение возвращает набор стилизованных аватаров. На деле это несколько связанных проблем: выбор модели (Stable Diffusion + LoRA против специализированных API), управление очередями генерации, приемлемое время ожидания и работа с фото-разрешениями.
Архитектура: почему не стоит генерировать on-device
Stable Diffusion 1.5 в FLOAT16 весит ~2.5 GB. Apple ML Stable Diffusion Swift package позволяет запустить его на iPhone 14 Pro (Neural Engine + 6 GB RAM). 20 шагов DDIM на 512×512 — около 8 секунд. Это на топовом устройстве. На iPhone 12 или любом среднебюджетном Android — нереально.
Для генерации аватаров в продакшен-приложении разумный выбор — серверная генерация через специализированный сервис. Варианты:
| Сервис | Подход | Время | Качество |
|---|---|---|---|
| Replicate (SDXL + IP-Adapter) | REST API | 15–40 сек | Высокое |
| Fal.ai | REST + WebSocket | 5–15 сек | Высокое |
| Leonardo.ai | REST API | 10–30 сек | Очень высокое |
| Astria.ai | Fine-tune + генерация | 10–30 мин (fine-tune) + 15 сек | Максимальное |
Для аватаров «похожих на пользователя» лучший результат даёт IP-Adapter или InstantID — они сохраняют черты лица без полноценного fine-tune LoRA. Если нужна максимальная точность (как в Lensa App) — Dreambooth LoRA с 10–20 фото пользователя, но это займёт 10–20 минут обработки.
Мобильный клиент: флоу загрузки и ожидания
Генерация занимает время — пользователю нужен понятный feedback. Архитектура асинхронного флоу:
// iOS: запуск генерации и polling статуса
class AvatarGenerationService {
private let apiClient: APIClient
func generateAvatar(photo: UIImage, style: AvatarStyle) async throws -> [UIImage] {
// 1. Compress + upload photo
let photoData = photo.jpegData(compressionQuality: 0.85)!
let uploadURL = try await apiClient.uploadPhoto(data: photoData)
// 2. Start generation job
let jobId = try await apiClient.startGeneration(
photoURL: uploadURL,
style: style.rawValue,
count: 6
)
// 3. Poll with exponential backoff
return try await pollJobResult(jobId: jobId)
}
private func pollJobResult(jobId: String) async throws -> [UIImage] {
var delay: TimeInterval = 2.0
for _ in 0..<30 {
try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
let status = try await apiClient.checkJob(id: jobId)
switch status.state {
case .completed: return try await downloadResults(urls: status.resultURLs)
case .failed: throw AvatarError.generationFailed(status.error)
case .pending, .processing: delay = min(delay * 1.5, 8.0)
}
}
throw AvatarError.timeout
}
}
На Android аналогично через Kotlin Coroutines + kotlinx.coroutines.delay.
Подготовка фото на клиенте
Качество аватара напрямую зависит от входного фото. Нужно валидировать перед отправкой:
- Лицо детектировано (iOS:
VNDetectFaceRectanglesRequest, Android: ML KitFaceDetector) - Освещение приемлемое — проверяем среднее значение яркости через
CIAreaAverage - Разрешение минимум 512×512
- Одно лицо в кадре (если несколько — показываем предупреждение)
Фото компрессируем до 1024×1024 JPEG 85% перед отправкой — избыточное разрешение не улучшает результат, но увеличивает время загрузки и стоимость.
Кеширование и галерея результатов
Сгенерированные аватары нужно хранить. Не надо генерировать повторно при каждом открытии — это дорого. На iOS: сохраняем в FileManager с метаданными в Core Data (стиль, дата, jobId для traceability). На Android — Room + FileProvider.
Важный момент: если приложение уходит в бэкграунд во время генерации — polling прерывается. Решение: сохранить jobId в UserDefaults / SharedPreferences, при следующем открытии приложения проверить статус незавершённых задач.
Push-уведомление о готовности
Ждать 20–40 секунд с открытым приложением — неплохо. Но если пользователь свернул приложение — нужен push. Сервер отправляет FCM/APNs-уведомление после завершения генерации. На клиенте — UNNotificationAction с deep link в галерею аватаров.
Права и конфиденциальность
Privacy Nutrition Labels в App Store требуют декларировать сбор фотографий. Если фото уходит на сервер — это Photos data type, usage: App Functionality. Обязательно: явное согласие пользователя, политика хранения (удалять исходное фото после генерации), не передавать третьим сторонам для обучения без согласия.
На Android с targetSdk 33+ запрашиваем READ_MEDIA_IMAGES вместо устаревшего READ_EXTERNAL_STORAGE.
Сроки
Базовый флоу (загрузка фото, API вызов, polling, показ результатов) — 3–5 дней. С валидацией лица, галереей, push-уведомлениями и поддержкой нескольких стилей — 2–3 недели. Стоимость зависит от платформы (iOS/Android/оба) и выбранного AI-провайдера.







