Реалізація AI-пошуку по фотографіях за текстовим описом у мобільному додатку

TRUETECH займається розробкою, підтримкою та обслуговуванням мобільних додатків iOS, Android, PWA. Маємо великий досвід та експертизу для публікації мобільних додатків до популярних маркетів Google Play, App Store, Amazon, AppGallery та інші.

Розробка та підтримка будь-яких видів мобільних додатків:

Інформаційні та розважальні мобільні програми
Новинки, ігри, довідники, онлайн-каталоги, погодні, фітнес та здоров'я, туристичні, освітні, соціальні мережі та месенджери, квіз, блоги та подкасти, форуми, агрегатори
Мобільні програми електронної комерції
Інтернет-магазини, B2B-додатки, маркетплейси, онлайн-обмінники, кешбек-сервіси, біржі, дропшиппінг-платформи, програми лояльності, доставка їжі та товарів, платіжні системи
Мобільні програми для управління бізнес-процесами
CRM-системи, ERP-системи, управління проектами, інструменти для команди продажів, облік фінансів, управління виробництвом, логістика та доставка, управління персоналом, системи моніторингу даних
Мобільні програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, платформи надання електронних послуг, платформи кешбеку, відеохостинги, тематичні портали, платформи онлайн-бронювання та запису, платформи онлайн-торгівлі

Це лише деякі з типів мобільних додатків, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1735 послуг
Реалізація AI-пошуку по фотографіях за текстовим описом у мобільному додатку
Складний
~1-2 тижні
Часті запитання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_mobile-applications_feedme_467_0.webp
    Розробка мобільного додатка для компанії FEEDME
    792
  • image_mobile-applications_xoomer_471_0.webp
    Розробка мобільного додатку для компанії XOOMER
    671
  • image_mobile-applications_rhl_428_0.webp
    Розробка мобільного додатку для компанії RHL
    1097
  • image_mobile-applications_zippy_411_0.webp
    Розробка мобільного додатку для компанії ZIPPY
    969
  • image_mobile-applications_affhome_429_0.webp
    Розробка мобільного додатку для компанії Affhome
    914
  • image_mobile-applications_flavors_409_0.webp
    Розробка мобільного додатку для компанії FLAVORS
    495

AI-поний текстовий пошук фотографій в мобільних додатках

"Покажи фото з собакою на пляжі" — користувач описує текстом, додаток знаходить релевантні фото. Це CLIP (Contrastive Language-Image Pretraining від OpenAI): модель навчена сопоставляти зображення і текстові описи у спільному векторному просторі. Косинусна подібність між вектором тексту і вектором зображення — це "релевантність".

Архітектура: embeddings + vector search

Pipeline складається з двох незалежних етапів:

Індексація (відбувається один раз для всієї галереї, потім інкрементально):

  • Для кожного фото → CLIP Image Embedding (512-мірний вектор)
  • Зберігаємо в локальну векторну БД

Пошук (відбувається при кожному запиті користувача):

  • Запит користувача → CLIP Text Embedding (той же 512-мірний вектор)
  • ANN-пошук найближчих векторів у базі
  • Повертаємо фото за спаданням косинусної подібності

CLIP on-device через CoreML

Apple не включила CLIP в стандартний Vision framework, але Apple ML Research випустила ml-mobileclip — дистильовану версію спеціально для мобільних пристроїв. MobileCLIP-S0: 18 MB, 3–5 мс інференцу зображення на iPhone 14.

import CoreML

class MobileCLIPEmbedder {
    private let imageEncoder: MobileCLIPImageEncoder
    private let textEncoder: MobileCLIPTextEncoder

    func embedImage(_ cgImage: CGImage) throws -> [Float] {
        let resized = resize(cgImage, to: CGSize(width: 256, height: 256))
        let input = MobileCLIPImageInput(image: MLMultiArray(from: resized))
        let output = try imageEncoder.prediction(input: input)
        return l2Normalize(output.embedding.toFloatArray())
    }

    func embedText(_ query: String) throws -> [Float] {
        let tokens = tokenize(query)  // BPE tokenizer
        let input = MobileCLIPTextInput(tokens: MLMultiArray(from: tokens))
        let output = try textEncoder.prediction(input: input)
        return l2Normalize(output.embedding.toFloatArray())
    }
}

Tokenizer для CLIP — BPE (Byte Pair Encoding). Swift-реалізація доступна у репозиторії apple/ml-mobileclip.

На Android: ONNX Runtime з MobileCLIP — менш зручно, але працює. OrtEnvironment + OrtSession, батчинг по 8 зображень.

Векторна БД на пристрої

Для пошуку серед 50 000 векторів потрібен ANN-індекс. Варіанти:

SQLite з розширенням sqlite-vss — додає віртуальні таблиці для векторного пошуку. Компактна, працює в embedded режимі:

CREATE VIRTUAL TABLE photo_embeddings USING vss0(embedding(512));
INSERT INTO photo_embeddings(rowid, embedding) VALUES (42, json('[0.1, -0.3, ...]'));
SELECT rowid, distance FROM photo_embeddings WHERE vss_search(embedding, json('[0.2, -0.1, ...]')) LIMIT 20;

Простий FAISS (C++) через JNI/Swift bridging — швидше на великих об'ємах, але складніше у інтеграції.

Простий flat L2/cosine через Accelerate — для галерей до 10k фото цілком достатньо без спеціалізованого індексу:

func cosineSimilarity(_ a: [Float], _ b: [Float]) -> Float {
    var dotProduct: Float = 0
    vDSP_dotpr(a, 1, b, 1, &dotProduct, vDSP_Length(a.count))
    return dotProduct  // Після L2-нормалізації = косинусна подібність
}

Перебір 10000 512-мірних векторів на iPhone 14 через vDSP_dotpr — ~15 мс. Для галерей до 20k приймаємо.

Індексація у фоні

Первинна індексація галереї 10k фото при 4 мс/фото = 40 секунд. Запускаємо через BGProcessingTask:

// Зберігаємо прогрес — щоб при наступному запуску продовжити з місця зупинки
class GalleryIndexer {
    private var lastIndexedDate: Date {
        get { UserDefaults.standard.object(forKey: "lastIndexedDate") as? Date ?? .distantPast }
        set { UserDefaults.standard.set(newValue, forKey: "lastIndexedDate") }
    }

    func indexNewPhotos() async {
        let fetchOptions = PHFetchOptions()
        fetchOptions.predicate = NSPredicate(format: "creationDate > %@", lastIndexedDate as CVarArg)
        let newPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)

        newPhotos.enumerateObjects { [weak self] asset, _, _ in
            guard let self else { return }
            if let embedding = self.computeEmbedding(for: asset) {
                self.vectorDB.insert(assetId: asset.localIdentifier, embedding: embedding)
            }
        }
        lastIndexedDate = Date()
    }
}

Пошук: обробка запиту

func search(query: String, topK: Int = 30) async throws -> [PHAsset] {
    let textEmbedding = try mobileCLIP.embedText(query)
    let results = vectorDB.search(vector: textEmbedding, limit: topK)

    let fetchOptions = PHFetchOptions()
    fetchOptions.predicate = NSPredicate(
        format: "localIdentifier IN %@",
        results.map { $0.assetId }
    )
    let assets = PHAsset.fetchAssets(with: fetchOptions)

    // Сортуємо за релевантністю (за порядком з vectorDB)
    let idToScore = Dictionary(uniqueKeysWithValues: results.map { ($0.assetId, $0.score) })
    return assets.objects(at: IndexSet(0..<assets.count))
        .sorted { idToScore[$0.localIdentifier, default: 0] > idToScore[$1.localIdentifier, default: 0] }
}

Затримка пошуку — text embedding (~5 мс) + ANN search (~15 мс) = ~20 мс. Результати миттєві з точки зору користувача.

Багатомовний пошук

CLIP навчена переважно на англійській мові. Для російського запиту "собака на пляжі" — якість гірша, ніж для "dog on beach". Рішення: переведіть запит через простий словник частих слів або Google Translate API перед embeddings. На практиці достатньо перекласти 100–200 частих запитів без API.

Часові рамки

Базовий CLIP-пошук з flat index для галерей до 10k — 1–1,5 тижні. Масштабуюча реалізація з ANN-індексом, інкрементальним оновленням, багатомовністю та візуальним пошуком за референс-фото — 3–4 тижні. Вартість розраховується індивідуально.