AI Development for Agriculture: Crop Image Analysis
The client comes with raw drone images and a request to "make NDVI". We look at histograms — 80% of values are in the 0.2–0.4 range, but the field looks green. Problem: incorrect calibration of the multispectral camera. Without considering shooting parameters (altitude, sun angle, humidity), NDVI is garbage. Our experience shows: we fine-tune models on your fields and save up to 30% on fertilizers through accurate zonal maps. Over the season on 5000 ha, fertilizer savings amounted to 1.2 million rubles ($16,000). Seasonal savings amount to $16,000. Contact us for evaluation — we'll analyze your data in 2 days.
What Problems We Solve
Incorrect NDVI calculation. Satellite images low resolution (10 m/pixel) average the field state. Drone gives 5 cm/pixel, but without calibration using a reference panel (Labsphere), the value spread reaches 0.2. We use field spectroradiometers to tie NDVI to actual chlorophyll readings.
Disease detection with false positives. RGB model on YOLOv8 confuses sunburn with alternaria. Solution: add multispectral channels (RedEdge, NIR) and use an attention layer that learns to distinguish necrotic spots by spectral curve.
Prescription maps don't match machinery. ISO-XML tasks don't match John Deere format. We generate shapefiles with N_rate_kg_ha attributes directly importable into Operations Center — tested on 20+ farms.
How Neural Networks Detect Plant Diseases
Standard pipeline: take leaf fragments 640×640 px, apply augmentations (rotation, scale, brightness), feed into YOLOv8-small pretrained on ImageNet. For multispectral images, use multi-channel input (6 channels: BGR + NIR + RedEdge + NDVI). Result is a mask of affected areas with confidence >0.85. Below is an example of a fine-tuned model for tomatoes:
from ultralytics import YOLO
import cv2
class CropDiseaseDetector:
def __init__(self, model_path: str):
# YOLOv8 fine-tuned on PlantVillage Dataset + custom data
self.model = YOLO(model_path)
self.disease_classes = [
'healthy', 'early_blight', 'late_blight', 'leaf_mold',
'septoria_leaf_spot', 'spider_mites', 'target_spot',
'yellow_leaf_curl', 'mosaic_virus', 'bacterial_spot'
]
def analyze_leaf(self, leaf_image: np.ndarray) -> dict:
results = self.model(leaf_image, conf=0.4)
detections = []
for box in results[0].boxes:
disease = self.disease_classes[int(box.cls)]
detections.append({
'disease': disease,
'confidence': float(box.conf),
'bbox': box.xyxy[0].tolist(),
'severity': self._estimate_severity(leaf_image, box)
})
# Overall health score
if not detections:
health_score = 1.0
else:
max_disease_conf = max(d['confidence'] for d in detections
if d['disease'] != 'healthy')
health_score = 1.0 - max_disease_conf
return {
'health_score': health_score,
'detections': detections,
'needs_treatment': health_score < 0.6
}
Limitations of NDVI for Early Stress Detection
NDVI saturates at LAI (leaf area index) >3. On crops in tillering stage it shows 0.8–0.9 — all zones "excellent", though nitrogen stress is already present. Alternative — NDRE (Red Edge), which, according to Gitelson et al., responds linearly to chlorophyll content up to LAI=6. We calculate both indices and build a combined map:
import numpy as np
import rasterio
import matplotlib.pyplot as plt
import matplotlib.colors as colors
class CropHealthAnalyzer:
def calculate_ndvi(self, red_band: np.ndarray,
nir_band: np.ndarray) -> np.ndarray:
"""NDVI = (NIR - Red) / (NIR + Red)"""
red = red_band.astype(np.float32)
nir = nir_band.astype(np.float32)
ndvi = np.where(
(nir + red) > 0,
(nir - red) / (nir + red),
0
)
return np.clip(ndvi, -1, 1)
def calculate_ndre(self, red_edge: np.ndarray,
nir: np.ndarray) -> np.ndarray:
"""NDRE — more sensitive than NDVI for early stress"""
return (nir - red_edge) / (nir + red_edge + 1e-8)
def calculate_indices_batch(self, multispectral_path: str) -> dict:
"""Calculate all indices from multispectral GeoTIFF"""
with rasterio.open(multispectral_path) as src:
# Assume order: Blue, Green, Red, RedEdge, NIR
blue = src.read(1).astype(np.float32)
green = src.read(2).astype(np.float32)
red = src.read(3).astype(np.float32)
red_edge = src.read(4).astype(np.float32)
nir = src.read(5).astype(np.float32)
transform = src.transform
crs = src.crs
indices = {
'ndvi': self.calculate_ndvi(red, nir),
'ndre': self.calculate_ndre(red_edge, nir),
'gndvi': (nir - green) / (nir + green + 1e-8), # Green NDVI
'evi': 2.5 * (nir - red) / (nir + 6*red - 7.5*blue + 1 + 1e-8),
}
return indices, transform, crs
def classify_crop_health(self, ndvi: np.ndarray) -> np.ndarray:
"""Classify crop health by NDVI"""
health_map = np.zeros_like(ndvi, dtype=np.uint8)
health_map[ndvi < 0.1] = 0 # Soil/no vegetation
health_map[(ndvi >= 0.1) & (ndvi < 0.3)] = 1 # Stress/sparse
health_map[(ndvi >= 0.3) & (ndvi < 0.5)] = 2 # Moderate
health_map[(ndvi >= 0.5) & (ndvi < 0.7)] = 3 # Good
health_map[ndvi >= 0.7] = 4 # Excellent
return health_map
How We Generate Prescription Maps
Based on raster health zones, we build vector polygons with application rates. Example for wheat: zone 1 (NDVI<0.3) — 120 kg N/ha, zone 2 (0.3–0.5) — 90 kg/ha, zone 3 (0.5–0.7) — 60 kg/ha, zone 4 (>0.7) — 30 kg/ha. The file is exported as Shapefile with projection EPSG:32637 (UTM 37N).
import geopandas as gpd
from shapely.geometry import shape
import json
def create_prescription_map(ndvi_array: np.ndarray,
transform,
field_boundary: gpd.GeoDataFrame) -> dict:
"""
Create prescription map by zones
for variable rate fertilizer application
"""
health_zones = classify_into_zones(ndvi_array, n_zones=4)
# Vectorize raster zones
from rasterio.features import shapes
zone_geometries = list(shapes(health_zones.astype('uint8'), transform=transform))
prescription = []
for geom, zone_value in zone_geometries:
# N rate by zones (kg/ha)
n_rate = {1: 120, 2: 90, 3: 60, 4: 30}[int(zone_value)] if zone_value > 0 else 0
prescription.append({
'geometry': geom,
'zone': int(zone_value),
'n_rate_kg_ha': n_rate,
'action': 'apply_fertilizer' if n_rate > 0 else 'skip'
})
return prescription
Turnkey Work Process
- Data audit — check image quality, calibration coefficients, metadata.
- Prototyping — select architecture (YOLOv8, EfficientNet, U-Net), tune hyperparameters.
- Training — use PyTorch + Hugging Face Datasets, tracking in W&B, distributed training on 4×A100.
- Field validation — compare with manual chlorophyll measurements (SPAD-502) and biomass.
- Deployment — Triton Inference Server, API on FastAPI, MQTT integration for drones.
- Support — fine-tuning on new crops, model drift monitoring.
What’s Included in Deliverables
- Documentation: Full technical report on data preprocessing, model architecture, training hyperparameters, and validation results. Includes data format specifications and API documentation.
- Source Code: Complete pipeline (PyTorch, Docker Compose) with reproducible training scripts, inference endpoints, and sample notebooks.
- Access: Model weights, training data (if permitted), and access to AWS S3 bucket for collaboration.
- Training: 2-day on-site operator training on drone operation, data collection, and software usage.
- Support: 2 months of technical support including bug fixes, model fine-tuning for additional fields, and troubleshooting.
- Deliverables per milestone: Pilot delivers one trained model, prescription map generation software, and basic dashboard. Full farm delivery includes multi-crop models, deployment on customer infrastructure, and real-time drone integration.
Timeline and Cost
| Scope | Timeline | Cost |
|---|---|---|
| Pilot: 1 crop, 1 field | 4–6 weeks | $8,000–$12,000 |
| Farm: 5–10 crops | 8–14 weeks | $25,000–$60,000 |
| Regional platform | 16–24 weeks | $100,000–$250,000 |
Exact cost is determined after analyzing your data — we evaluate the project in 2 working days. Request a consultation: our engineers will contact you and discuss the case.
Comparison: AI vs Manual Field Inspection
Local case: 5000 ha farm, corn. Before implementation — quad bike inspection once a week, 5 points per field. After — drone with multispectral camera (5 cm/pixel), AI analyzes 1 million points per hour. Result: AI is 10 times faster than manual inspection per hectare, fertilizer savings 25–30% (based on zone maps), yield increase of 12% due to timely treatment of disease hotspots. Seasonal savings — over 1.2 million rubles ($16,000).
| Parameter | Manual method | AI analysis | Improvement factor |
|---|---|---|---|
| Time per 100 ha | 4 hours | 20 minutes | 12x faster |
| Stress assessment accuracy | ±0.3 NDVI | ±0.05 NDVI | 6x more accurate |
| Fertilizer savings | 0% | up to 30% | 30% better |
| Early detection | None | 2–3 days before visible | Up to 5 days earlier |
Why AI Outperforms Visual Inspection by 5x
Humans see symptoms when the plant is already affected. AI detects spectral changes 2–3 days before visible signs. This time is critical for fungicide treatment — a 5-day delay reduces efficacy by 40%. Our models predict disease outbreaks with 88% accuracy 7 days in advance. In controlled trials, AI-based treatment timing reduced fungicide use by 35% while maintaining yield.
We are a team with 5+ years of experience in AI for agriculture, delivered 40+ projects for farms and agriholdings. We guarantee quality: every project is backed by SLA on detection accuracy (±0.02 NDVI) and API response time (<200ms per image).







