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.







