Розробка поіску по зображенню в мобільному додатку
Користувач фотографує товар у магазині конкурента або робить скриншот з Instagram — та хоче знайти те ж саме в вашому каталозі. Visual search закриває цей сценарій. Технічно завдання складається з двох частин: отримати векторне представлення зображення (embedding) та знайти по ньому найближчих сусідів у базі.
Два підходи до реалізації
Embedded модель на пристрої. На iOS — Vision фреймворк з VNGenerateImageFeaturePrintRequest, на Android — ML Kit Image Labeling або кастомна TFLite-модель через TensorFlow Lite Task Library. Преімущество: працює offline, немає мережевої затримки. Обмеження: feature print від Apple працює тільки всередині екосистеми iOS та не сумісна з серверним індексом.
Серверний embedding. Зображення відправляється на сервер, там прогоняється через модель (CLIP, EfficientNet, ResNet), повертається вектор, який шукається по індексу. Це точніше та гнучче — один та той же індекс працює з iOS, Android, вебом.
На практиці частіше вибираємо серверний варіант з локальною pre-processing: зображення стискається та нормалізується на пристрої до відправлення.
Захват та підготовка зображення
На iOS для вибору з галереї — PHPickerViewController (не UIImagePickerController, він deprecated з iOS 14). Для камери — AVCaptureSession з AVCapturePhotoOutput. Зображення перед відправкою:
func prepareForSearch(image: UIImage) -> Data? {
// Масштабуємо до 512px по довгій стороні
let maxDimension: CGFloat = 512
let scale = maxDimension / max(image.size.width, image.size.height)
let newSize = CGSize(width: image.size.width * scale,
height: image.size.height * scale)
UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
image.draw(in: CGRect(origin: .zero, size: newSize))
let resized = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return resized?.jpegData(compressionQuality: 0.85)
}
На Android — ActivityResultContracts.TakePicture() для камери та PickVisualMedia() для галереї (Photo Picker API, доступен з Android 13 та через Jetpack).
Серверний поіск: векторний індекс
Для поіску найближчих сусідів по embedding використовуємо Qdrant, Weaviate або pgvector (якщо вже PostgreSQL у стеку). CLIP-модель від OpenAI дає гарні результати для товарного поіску — вона навчена на парах зображення-текст, тому працює в обидві сторони: по фото знайти текст та навпаки.
Запрос до сервера з індикатором прогреса:
// Android, Retrofit + OkHttp
suspend fun searchByImage(imageBytes: ByteArray): List<SearchResult> {
val requestBody = imageBytes.toRequestBody("image/jpeg".toMediaType())
val part = MultipartBody.Part.createFormData("image", "search.jpg", requestBody)
return searchApi.visualSearch(part)
}
Важливо: обробляємо випадок, коли сервер не знайшов близьких збігів (cosine distance > порога). Показуємо «нічого не знайдено» честно, а не повертаємо нерелевантні результати з далекими векторами.
Предобробка з CoreML / TFLite на пристрої
Якщо потрібен offline або потрібно прискорити відклик — вбудовуємо легку модель. MobileNetV3 або EfficientNet-Lite дають розумний компроміс між точністю та розміром. На iOS конвертуємо в .mlmodel через coremltools, на Android — в .tflite. Локальний індекс зберігається в SQLite з розширенням для косинусної відстані або використовуємо Faiss через JNI/FFI.
Процес роботи
Аудит каталогу: розмір, тип товарів, вимоги до точності поіску.
Вибір архітектури: серверний embedding або гібридний (локальна предобробка + серверний індекс).
Підготовка еталонних embeddings для каталогу, налаштування векторного індексу.
Розробка UI: вибір фото, крoп-інструмент (опційно), відображення результатів з similarity score.
Тестування з реальними користувальницькими фотографіями — погане освітлення, кути, часткові збіги.
Орієнтири за строками
Інтеграція з готовим серверним API поіску — 3–5 днів. Повна реалізація включаючи серверну частину (embedding-сервіс, векторний індекс, завантаження каталогу) — 3–6 тижнів залежно від обсягу каталогу та потрібної точності.







