AI-класифікація фотографій в галереї для мобільних додатків
Класифікація фотографій в галереї — це одне з кількох завдань AI, де обробка на пристрої є стандартом, а не винятком. Apple Photos, Google Photos — обидва використовують on-device ML. Завантажувати особисті фотографії користувачів на сервер для класифікації технічно надлишково та неправильно з точки зору приватності.
On-Device класифікація: що працює з коробки
На iOS використовуйте framework Vision з VNClassifyImageRequest. Не потрібні сторонні моделі — вбудована класифікація охоплює 1000+ категорій:
import Vision
func classifyPhoto(cgImage: CGImage, completion: @escaping ([String]) -> Void) {
let request = VNClassifyImageRequest { request, error in
guard let results = request.results as? [VNClassificationObservation] else { return }
// Беріть категорії з confidence > 0.5
let labels = results
.filter { $0.confidence > 0.5 }
.map { $0.identifier }
completion(labels)
}
try? VNImageRequestHandler(cgImage: cgImage, options: [:]).perform([request])
}
Час інференцу — 5–15 мс на фото залежно від пристрою. На iPhone 13+ Neural Engine обробляє всю галерею з 1000 фото за ~15–20 секунд як фонову задачу.
На Android використовуйте ML Kit ImageLabeler з ImageLabelerOptions:
val labeler = ImageLabeling.getClient(
ImageLabelerOptions.Builder()
.setConfidenceThreshold(0.5f)
.build()
)
labeler.process(InputImage.fromBitmap(bitmap, 0))
.addOnSuccessListener { labels ->
val categories = labels.map { it.text }
// "Dog", "Outdoor", "Sky", "Food", etc.
}
ML Kit підтримує 400+ категорій на пристрої без мережі.
Обробка всієї галереї: PHFetchResult та батчинг
Проблема: галерея може містити 50 000+ фотографій. Повторення всіх одночасно блокує основний потік та розряджає батарею.
Правильний підхід: PHFetchResult + інкрементальна обробка через DispatchQueue.global(qos: .background):
func classifyGallery() {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
let batchSize = 50
let processingQueue = DispatchQueue(label: "photo.classification", qos: .background)
processingQueue.async {
var offset = 0
while offset < allPhotos.count {
let batch = (offset..<min(offset + batchSize, allPhotos.count))
.map { allPhotos.object(at: $0) }
self.processBatch(assets: batch)
offset += batchSize
Thread.sleep(forTimeInterval: 0.1) // Дати системі відпочинок
}
}
}
Thread.sleep(0.1) між батчами критична — без неї CPU дросселює після 2–3 хвилин і швидкість падає на 3–5х.
Зберігання результатів класифікації
Зберігайте результати локально, не на сервері. Використовуйте Core Data з NSPersistentContainer:
// Entity: PhotoClassification
// Attributes: assetLocalIdentifier (String), labels (Transformable: [String]), classifiedAt (Date)
Індекс на assetLocalIdentifier + індекс на labels для швидкого пошуку. Для 50k фото таблиця важить ~5–10 MB.
При відкритті галереї витягуйте класифікації з локальної БД та відображайте. Нові фото (додані після останньої сеансу) класифікуються інкрементально через PHPhotoLibraryChangeObserver.
Користувальницькі категорії через CoreML
Вбудована Vision не завжди охоплює потрібні категорії. Для користувальницьких (наприклад, "рецепт", "скріншот документа", "чек", "віза") — навчіть модель CreateML:
// CreateML: 5–10 прикладів на категорію достатньо для базової точності
let dataSource = MLImageClassifier.DataSource.labeledDirectories(at: trainingDir)
let model = try MLImageClassifier(trainingData: dataSource)
try model.write(to: modelURL)
Точність на користувальницьких категоріях з 20+ прикладами — 85–95%. Модель у форматі CoreML — 5–15 MB. Можна доставляти через Core ML Model Deployment без оновлення додатка.
Поширені помилки
Класифікація на основному потоці — найчастіша. VNImageRequestHandler.perform виконується синхронно. Завжди використовуйте фоновий черговий режим.
Запитування повної роздільної здатності для класифікації — непотрібно. PHAsset requestImage з targetSize: CGSize(width: 224, height: 224) досить — це стандартний вхід для більшості моделей класифікації.
Не оновлюйте класифікації при змінах галереї — PHPhotoLibraryChangeObserver повинен запускати інкрементальну обробку лише для нових/змінених фото.
Часові рамки
Базова класифікація з Vision + зберігання Core Data — 4–6 днів. Повна реалізація з користувальницькими категоріями, інкрементальними оновленнями та швидким пошуком тегів — 2–3 тижні. Вартість розраховується індивідуально.







