Реализация AI-распознавания животных по фотографии в мобильном приложении
Задача схожа с распознаванием растений по архитектуре, но имеет принципиальное отличие: животные двигаются. Пользователь фотографирует птицу на ветке — она улетает пока он поднимает телефон. Значит, помимо точности классификации, нужно думать о скорости capture и о том, как работает распознавание на смазанных снимках.
Выбор модели под задачу
Задача сильно зависит от целевого класса животных:
| Категория | Готовое API / модель | Кол-во видов |
|---|---|---|
| Птицы | Merlin Bird ID (Cornell Lab API), iNaturalist | 10 000+ |
| Дикие животные | iNaturalist API, INat Seek SDK | 100 000+ таксонов |
| Рыбы | iNaturalist, FishVerify API | 30 000+ |
| Домашние питомцы (порода) | Google Cloud Vision, кастомная CoreML | 200–400 пород |
| Насекомые | iNaturalist, iNat Seek | 500 000+ видов |
Для большинства проектов со смешанной аудиторией iNaturalist API — оптимальный выбор: широкая таксономическая база, confidence score на уровне вида/рода/семейства (что честно при низком качестве фото), и есть SDK для мобильных — Seek с on-device моделью для iOS и Android.
Интеграция iNaturalist Seek SDK на Android
class AnimalRecognitionManager(private val context: Context) {
// Seek использует TFLite модель ~15MB
private val seekModel by lazy {
SeekClassifier(context, modelPath = "seek_v2.tflite")
}
fun recognizeFromBitmap(bitmap: Bitmap): List<TaxonResult> {
val resized = Bitmap.createScaledBitmap(bitmap, 299, 299, true)
val results = seekModel.classify(resized)
return results
.filter { it.score > 0.15f }
.sortedByDescending { it.score }
.map { result ->
TaxonResult(
taxonId = result.taxonId,
name = result.name,
commonName = result.commonName,
rank = result.rank, // SPECIES, GENUS, FAMILY
confidence = result.score,
photoUrl = result.defaultPhotoUrl
)
}
}
}
Rank важен для UI: если уверенность на уровне вида 30%, честнее показать «Семейство Вьюрковые (85%)», чем конкретный вид с низкой точностью.
Быстрый capture для движущихся объектов
// iOS: буферизация кадров для выбора лучшего
class AnimalCaptureViewController: UIViewController {
private var frameBuffer: [CMSampleBuffer] = []
private let bufferSize = 10 // последние 10 кадров
func captureOutput(_ output: AVCaptureOutput,
didOutput sampleBuffer: CMSampleBuffer,
from connection: AVCaptureConnection) {
frameBuffer.append(sampleBuffer)
if frameBuffer.count > bufferSize {
frameBuffer.removeFirst()
}
}
// При нажатии кнопки — выбрать наименее смазанный кадр из буфера
func captureWithMotionCompensation() -> UIImage? {
return frameBuffer
.compactMap { UIImage(from: $0) }
.max(by: { sharpnessScore($0) < sharpnessScore($1) })
}
}
Такой подход снижает долю нечётких снимков примерно втрое по сравнению с обычным capturePhoto().
Ориентиры по срокам
Интеграция одного API или Seek SDK с базовым UI — 1 день. Добавление буферизации кадров, выбора лучшего снимка, карточки животного с описанием и ссылками (Wikipedia, iNaturalist), история распознаваний, iOS + Android — до 2 дней на базовой задаче, до 1.5 недель при нестандартных требованиях.







