Розпізнавання тварин за фотографією у мобільних застосунках
Схожа архітектура до розпізнавання рослин, але з ключовою відмінністю: тварини рухаються. Користувач фотографує птаха на гілці—вона злітає поки він піднімає телефон. Окрім точності класифікації, врахуйте швидкість захоплення й розпізнавання розмитих снимків.
Вибір моделі під завдання
Завдання сильно залежить від цільового класу тварин:
| Категорія | Готове 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%)», а не конкретний вид з низькою точністю.
Швидкий захопленнення для рухомих об'єктів
// 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 тижня при нестандартних вимогах.







