AI Biodiversity Monitoring System

We design and deploy artificial intelligence systems: from prototype to production-ready solutions. Our team combines expertise in machine learning, data engineering and MLOps to make AI work not in the lab, but in real business.
Showing 1 of 1 servicesAll 1566 services
AI Biodiversity Monitoring System
Medium
~2-4 weeks
FAQ
AI Development Areas
AI Solution Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1212
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822

Разработка AI для мониторинга биоразнообразия

Мониторинг биоразнообразия традиционно опирался на ручные полевые наблюдения: 2–3 специалиста за сезон обследуют ограниченную территорию. Фотоловушки, дроны с мультиспектральными камерами, гидрофоны и акустические датчики генерируют терабайты данных в сутки — без AI обработать их невозможно. Ключевые задачи: Species identification (определение вида по фото/звуку), population estimation (подсчёт особей по аэрофотосъёмке), individual identification (re-ID конкретной особи для долгосрочного мониторинга), habitat change detection.

Детектор и классификатор диких животных

import numpy as np
import cv2
import torch
from ultralytics import YOLO
from torchvision import models, transforms
from PIL import Image
from dataclasses import dataclass
from typing import Optional
import json

@dataclass
class WildlifeDetection:
    track_id: int
    species: str
    common_name_ru: str
    confidence: float
    bbox: list
    individual_id: Optional[str]   # re-ID если обучена модель
    behavior: Optional[str]        # resting / moving / feeding
    camera_id: str
    timestamp: float

class WildlifeMonitor:
    """
    Детекция и классификация диких животных по снимкам с фотоловушек.
    Датасеты:
    - iNaturalist (1.4M изображений, 5000+ видов) — для предобучения
    - LILA BC (camera trap images) — leopard, snow leopard, пр.
    - Snapshot Serengeti (22M изображений, 48 видов)
    - CaltechCameraTraps (20 видов, Северная Америка)
    Двухступенчатый пайплайн: YOLO detection → EfficientNet species classification.
    """
    def __init__(self, detector_path: str,
                  classifier_path: str,
                  species_vocab_path: str,
                  reid_model_path: Optional[str] = None,
                  device: str = 'cuda'):
        self.detector = YOLO(detector_path)
        self.device = device

        # Classifier: EfficientNet-B3 fine-tuned on iNaturalist
        with open(species_vocab_path) as f:
            vocab_data = json.load(f)
        self.species_list = vocab_data['species']
        self.species_ru = vocab_data.get('species_ru', {})

        self.classifier = models.efficientnet_b3(pretrained=False)
        n_classes = len(self.species_list)
        self.classifier.classifier[-1] = torch.nn.Linear(
            self.classifier.classifier[-1].in_features, n_classes
        )
        state = torch.load(classifier_path, map_location=device)
        self.classifier.load_state_dict(state)
        self.classifier = self.classifier.to(device).eval()

        self.classify_transform = transforms.Compose([
            transforms.Resize((300, 300)),
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406],
                                  [0.229, 0.224, 0.225])
        ])

        # Re-ID (опционально): мегапиксельная модель для паттернов меха
        self.reid_model = None
        if reid_model_path:
            self.reid_model = torch.load(reid_model_path,
                                          map_location=device).eval()
        self._individual_gallery: dict[str, np.ndarray] = {}

    def process_camera_trap_image(self, image: np.ndarray,
                                   camera_id: str,
                                   timestamp: float) -> list[WildlifeDetection]:
        """
        Обработка снимка с фотоловушки.
        Фотоловушки: ночные (ИК) + дневные, разное разрешение.
        """
        # Улучшение ночного снимка
        enhanced = self._enhance_camera_trap(image)

        detections_raw = self.detector(
            enhanced, conf=0.25, verbose=False
        )
        results = []

        for box in detections_raw[0].boxes:
            x1, y1, x2, y2 = map(int, box.xyxy[0])
            track_id = int(box.id.item()) if box.id is not None else -1

            # Вырезать crop для классификации
            crop = enhanced[max(0,y1):y2, max(0,x1):x2]
            if crop.size == 0:
                continue

            species, conf = self._classify_species(crop)
            species_ru = self.species_ru.get(species, species)
            behavior = self._estimate_behavior(crop, box)

            # Re-ID
            individual_id = None
            if self.reid_model:
                individual_id = self._get_individual_id(crop, species)

            results.append(WildlifeDetection(
                track_id=track_id,
                species=species,
                common_name_ru=species_ru,
                confidence=round(conf, 3),
                bbox=[x1, y1, x2, y2],
                individual_id=individual_id,
                behavior=behavior,
                camera_id=camera_id,
                timestamp=timestamp
            ))

        return results

    @torch.no_grad()
    def _classify_species(self, crop: np.ndarray) -> tuple[str, float]:
        pil = Image.fromarray(cv2.cvtColor(crop, cv2.COLOR_BGR2RGB))
        tensor = self.classify_transform(pil).unsqueeze(0).to(self.device)
        logits = self.classifier(tensor)
        probs = torch.softmax(logits, dim=-1).squeeze()
        conf, idx = probs.max(dim=0)
        species = self.species_list[int(idx.item())]
        return species, float(conf.item())

    def _enhance_camera_trap(self, image: np.ndarray) -> np.ndarray:
        """Улучшение ИК-снимков фотоловушек"""
        # Проверка на ИК (низкая цветность)
        hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
        saturation = float(np.mean(hsv[:, :, 1]))

        if saturation < 20:  # ИК снимок
            gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
            clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
            enhanced_gray = clahe.apply(gray)
            return cv2.cvtColor(enhanced_gray, cv2.COLOR_GRAY2BGR)

        # Дневной снимок с избыточной тенью
        lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
        clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
        lab[:, :, 0] = clahe.apply(lab[:, :, 0])
        return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)

    def _estimate_behavior(self, crop: np.ndarray,
                            box) -> str:
        """Простая классификация поведения по bbox aspect ratio"""
        x1, y1, x2, y2 = map(int, box.xyxy[0])
        w, h = x2 - x1, y2 - y1
        aspect = w / max(h, 1)
        if aspect > 2.0:
            return 'resting'  # лежит горизонтально
        elif aspect < 0.6:
            return 'alert'    # стоит вертикально, голова поднята
        return 'moving'

    @torch.no_grad()
    def _get_individual_id(self, crop: np.ndarray,
                            species: str) -> Optional[str]:
        """Re-ID через embedding similarity (для леопардов, тигров)"""
        pil = Image.fromarray(cv2.cvtColor(crop, cv2.COLOR_BGR2RGB))
        transform = transforms.Compose([
            transforms.Resize((224, 224)),
            transforms.ToTensor(),
        ])
        tensor = transform(pil).unsqueeze(0).to(self.device)
        embedding = self.reid_model(tensor).squeeze().cpu().numpy()
        embedding /= np.linalg.norm(embedding) + 1e-8

        # Поиск в галерее
        best_match = None
        best_sim = 0.75  # порог

        gallery_key = f'{species}_'
        for ind_id, gallery_emb in self._individual_gallery.items():
            if not ind_id.startswith(gallery_key):
                continue
            sim = float(np.dot(embedding, gallery_emb))
            if sim > best_sim:
                best_sim = sim
                best_match = ind_id

        if best_match is None:
            # Новая особь
            new_id = f'{species}_{len(self._individual_gallery)+1:04d}'
            self._individual_gallery[new_id] = embedding
            return new_id
        return best_match


class AerialAnimalCounter:
    """
    Подсчёт животных на аэрофотоснимках (дрон/самолёт).
    Применение: мониторинг стад копытных, подсчёт пингвинов,
    инвентаризация морских котиков на лежбищах.
    SAHI обязателен для ортофото 50+ МПикс.
    """
    from sahi import AutoDetectionModel
    from sahi.predict import get_sliced_prediction

    def __init__(self, model_path: str, device: str = 'cuda'):
        from sahi import AutoDetectionModel
        self.sahi_model = AutoDetectionModel.from_pretrained(
            model_type='ultralytics',
            model_path=model_path,
            confidence_threshold=0.35,
            device=device
        )

    def count_herd(self, aerial_image: np.ndarray,
                    species_hint: str = 'ungulate') -> dict:
        from sahi.predict import get_sliced_prediction
        result = get_sliced_prediction(
            aerial_image, self.sahi_model,
            slice_height=640, slice_width=640,
            overlap_height_ratio=0.2, overlap_width_ratio=0.2
        )
        count = len(result.object_prediction_list)
        density = count / (aerial_image.shape[0] * aerial_image.shape[1] / 1e6)

        return {
            'species_hint': species_hint,
            'count': count,
            'density_per_km2': round(density * 1e6, 1),  # пикс² → км²
            'detections': [
                {'bbox': [p.bbox.minx, p.bbox.miny, p.bbox.maxx, p.bbox.maxy],
                 'conf': p.score.value}
                for p in result.object_prediction_list
            ]
        }
Датасет / Задача Метод Метрика
iNaturalist (10k видов) EfficientNet-B5 fine-tune Top-1 85–91%
Snapshot Serengeti (48 видов) YOLOv8 + classifier mAP 73–79%
Aerial penguin count SAHI + YOLOv8 MAE < 3%
Re-ID (леопарды, AmurTiger) ResNet50 + ArcFace Rank-1 82–89%
Bioacoustics (BirdCLEF 2024) BirdNET / PANN cmAP 72–78%
Задача Срок
Camera trap classifier (один биом, 20–50 видов) 5–8 недель
Полный pipeline: detection + classification + re-ID 12–18 недель
Мониторинговая платформа с картой и отчётами 18–28 недель