AI System for Biodiversity Monitoring
Camera traps, drones with multispectral cameras, hydrophones, and acoustic sensors generate terabytes of data daily. Without AI, processing this volume is impossible. Manual field observations cover limited areas — two or three specialists walk a few square kilometers per season. An AI system covers thousands of hectares in real time.
We design and deploy AI systems for automated biodiversity monitoring. Our team has completed projects for nature reserves, conservation organizations, and ecological researchers. We solve four core tasks: species identification from images and audio, individual counting on aerial photos, re-ID of specific animals for long-term tracking, and detection of habitat changes. Contact us for a consultation — we evaluate your project within two working days.
Biodiversity Monitoring AI: System Architecture
The system is built on a two-stage pipeline: a YOLO detector locates animals in the frame, and an EfficientNet classifier identifies the species. For re-ID of individuals, we use ArcFace embeddings compared against a gallery. Acoustic data is processed by BirdNET or PANN for bird and other animal classification.
All components are deployed in Docker containers. Results are stored in PostGIS for geospatial analysis. A Grafana dashboard displays an activity map and population dynamics.
AI Species Detector for Biodiversity Monitoring
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 if model trained behavior: Optional[str] # resting / moving / feeding camera_id: str timestamp: float class WildlifeMonitor: """ Detection and classification of wild animals from camera trap images. Datasets: - iNaturalist (1.4M images, 5000+ species) — for pretraining - LILA BC (camera trap images) — leopard, snow leopard, etc. - Snapshot Serengeti (22M images, 48 species) - CaltechCameraTraps (20 species, North America) Two-stage pipeline: 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 (optional): megapixel model for fur patterns 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]: """ Process a camera trap image. Camera traps: night (IR) + day, various resolutions. """ # Enhance night image 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 for classification 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: """Enhance IR camera trap images""" # Check for IR (low saturation) hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) saturation = float(np.mean(hsv[:, :, 1])) if saturation < 20: # IR image 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) # Day image with excessive shadow 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: """Simple behavior classification based on 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' # lying horizontally elif aspect < 0.6: return 'alert' # standing upright, head raised return 'moving' @torch.no_grad() def _get_individual_id(self, crop: np.ndarray, species: str) -> Optional[str]: """Re-ID via embedding similarity (for leopards, tigers)""" 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 # Search in gallery best_match = None best_sim = 0.75 # threshold 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 individual new_id = f'{species}_{len(self._individual_gallery)+1:04d}' self._individual_gallery[new_id] = embedding return new_id return best_match class AerialAnimalCounter: """ Animal counting on aerial images (drone/aircraft). Application: monitoring ungulate herds, penguin counting, fur seal rookery inventories. SAHI is essential for 50+ MPix orthophotos. """ 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), # pixels² → km² '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 ] } Accuracy Metrics and Timeline
Below are indicative quality metrics for typical biodiversity monitoring tasks. Metrics depend on dataset quality and shooting conditions.
| Dataset / Task | Method | Metric |
|---|---|---|
| iNaturalist (10k species) | EfficientNet-B5 fine-tune | Top-1 85–91% |
| Snapshot Serengeti (48 species) | YOLOv8 + classifier | mAP 73–79% |
| Aerial penguin count | SAHI + YOLOv8 | MAE < 3% |
| Re-ID (leopards, AmurTiger) | ResNet50 + ArcFace | Rank-1 82–89% |
| Bioacoustics (BirdCLEF 2024) | BirdNET / PANN | cmAP 72–78% |
Acoustic Biodiversity Monitoring
Sound data reveals species that are difficult to detect visually: nocturnal birds, bats, aquatic mammals — all leave an acoustic trace. We deploy hydrophones and acoustic recorders (AudioMoth, SongMeter) and process recordings with BirdNET and PANN.
The acoustic analysis process includes three stages. First, the audio file is segmented into 3–5 second chunks. Then each chunk is converted to a mel-spectrogram and passed through the classifier. Finally, post-processing merges adjacent detections of the same species into events, filtering by a minimum confidence threshold of 0.5.
BirdNET achieves cmAP 72–78% on European and North American birds in BirdCLEF competition. For exotic or tropical species, fine-tuning on regional data is required. We help collect and label a training corpus.
Integration with Geographic Information Systems
Monitoring data is stored in PostGIS. Each observation has coordinates, a timestamp, and a source identifier. QGIS and ArcGIS connect directly via the PostGIS connector. For dashboards we use Grafana with the Geomap plugin — the observation grid updates every hour.
Population trends are calculated using a 30-day sliding window. An anomalous drop in species activity by more than 30% of the baseline automatically generates an alert. This allows ecologists to respond quickly to changes before a quarterly report.
What's Included
- Data audit: inventory of sensors, formats, and data volumes.
- Labeling and training: creating a dataset, fine-tuning detector and classifier.
- Processing pipeline: automated processing of camera trap images, orthophotos, or audio recordings.
- Map dashboard: visualization of species activity on a map with filters.
- Documentation and team training: operation instructions and API specifications.
- Technical support: 3 months of maintenance after launch.
Time Estimates
| Task | Duration |
|---|---|
| Camera trap classifier (one biome, 20–50 species) | 5–8 weeks |
| Full pipeline: detection + classification + re-ID | 12–18 weeks |
| Monitoring platform with map and reports | 18–28 weeks |
Contact us for a consultation — we will evaluate your data and propose the optimal architecture. Reach out for a preliminary audit of your material. We have helped 15+ organizations automate biodiversity monitoring: nature reserves, environmental NGOs, industrial enterprises conducting environmental impact assessments. We guarantee transfer of source code and documentation after project completion. Our clients value that the system runs offline: all models are deployed on your own infrastructure; data never leaves the perimeter.







