AI-система мониторинга биоразнообразия

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1 услугВсе 1566 услуг
AI-система мониторинга биоразнообразия
Средняя
~2-4 недели
Часто задаваемые вопросы
Направления AI-разработки
Этапы разработки AI-решения
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1218
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    853
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1047
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    825

Разработка 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 недель