Реализация 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

Пайплайн состоит из двух независимых этапов:

Индексация (происходит один раз для всей галереи, потом инкрементально):

  • Для каждого фото → 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 ms на инференс изображения на 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 ms. Для галерей до 20k приемлемо.

Индексация в фоне

Первичная индексация галереи в 10k фото при 4 ms/фото = 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 ms) + ANN search (~15 ms) = ~20 ms. Результаты мгновенные с точки зрения пользователя.

Мультиязычный поиск

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

Сроки

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