IA et Agriculture : Détecter les Maladies des Cultures par Image

IA et Agriculture : Détecter les Maladies des Cultures par Image

Saviez-vous que les maladies des plantes détruisent entre 20 et 40 % des récoltes mondiales chaque année, selon la FAO ? En France, les pertes dues aux maladies fongiques et bactériennes représentent plusieurs milliards d’euros annuellement pour les exploitants agricoles. L’identification précoce d’une maladie — quelques jours avant qu’elle ne se propage — peut sauver une récolte entière. La vision par ordinateur et l’IA rendent désormais ce diagnostic accessible depuis un smartphone sur le terrain.

Pourquoi l’IA Révolutionne le Diagnostic Phytosanitaire

Les Limites de l’Expertise Humaine

Un agronome expérimenté peut identifier une cinquantaine de maladies courantes à l’œil. Mais il ne peut pas surveiller 200 hectares chaque jour. Les symptômes précoces — légère décoloration, ponctuations microscopiques — échappent souvent à l’œil non exercé jusqu’à ce que l’infection soit avancée.

Un modèle de vision par ordinateur entraîné sur des milliers d’images annotées identifie ces symptômes dès les premiers stades, de façon cohérente, sur des centaines de photos par heure.

L’Approche Multi-Méthodes

La détection des maladies des plantes mobilise plusieurs techniques complémentaires :

MéthodeAvantagesCas d’usage
Classification CNNRapide, simple à déployer“Cette feuille est-elle malade ?”
YOLO (détection)Localise et compte les lésionsSuivi progression, sévérité
SegmentationQuantifie le pourcentage de surface atteinteDécision traitement chimique
Imagerie drone multispectraleVue parcelle complèteCartographie des zones à risque

Détecter les Maladies par Vision par Ordinateur

Le Dataset PlantVillage — Le Point de Départ

PlantVillage (Penn State University) est le dataset de référence en pathologie végétale : 54 306 images couvrant 26 maladies sur 14 espèces de plantes. Il est disponible librement sur Kaggle et permet d’entraîner un premier modèle en quelques heures.

import torch
import torchvision.transforms as transforms
from torchvision.datasets import ImageFolder
from torchvision.models import efficientnet_b2, EfficientNet_B2_Weights
from torch.utils.data import DataLoader

# Transformations pour augmentation des données
transform_train = transforms.Compose([
    transforms.RandomResizedCrop(224, scale=(0.7, 1.0)),
    transforms.RandomHorizontalFlip(),
    transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.2),
    transforms.RandomRotation(15),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

transform_val = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

# Charger le dataset PlantVillage
dataset_train = ImageFolder("plantvillage/train", transform=transform_train)
dataset_val = ImageFolder("plantvillage/val", transform=transform_val)

loader_train = DataLoader(dataset_train, batch_size=32, shuffle=True, num_workers=4)
loader_val = DataLoader(dataset_val, batch_size=32, num_workers=4)

print(f"Classes : {dataset_train.classes}")
print(f"Images entraînement : {len(dataset_train)}")

Fine-Tuning d’EfficientNet sur vos Cultures Locales

EfficientNet-B2 offre le meilleur équilibre précision/taille pour un déploiement mobile. Le fine-tuning sur PlantVillage + vos propres images locales converge en général en 10 à 20 epochs :

import torch.nn as nn
from torch.optim import AdamW
from torch.optim.lr_scheduler import CosineAnnealingLR

# Charger EfficientNet pré-entraîné
model = efficientnet_b2(weights=EfficientNet_B2_Weights.IMAGENET1K_V1)

# Geler toutes les couches sauf les dernières
for param in model.parameters():
    param.requires_grad = False

# Remplacer la tête de classification
n_classes = len(dataset_train.classes)  # ex: 38 maladies
model.classifier = nn.Sequential(
    nn.Dropout(p=0.3),
    nn.Linear(model.classifier[1].in_features, n_classes)
)

# Dégeler la dernière couche de features aussi
for param in model.features[-1].parameters():
    param.requires_grad = True

optimizer = AdamW(
    filter(lambda p: p.requires_grad, model.parameters()),
    lr=1e-3,
    weight_decay=1e-4
)
scheduler = CosineAnnealingLR(optimizer, T_max=20)

# Entraînement
device = "cuda" if torch.cuda.is_available() else "cpu"
model = model.to(device)

for epoch in range(20):
    model.train()
    total_loss, correct = 0, 0

    for images, labels in loader_train:
        images, labels = images.to(device), labels.to(device)
        optimizer.zero_grad()
        outputs = model(images)
        loss = nn.CrossEntropyLoss()(outputs, labels)
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
        correct += (outputs.argmax(1) == labels).sum().item()

    scheduler.step()
    accuracy = correct / len(dataset_train)
    print(f"Epoch {epoch+1}/20 — Loss: {total_loss/len(loader_train):.3f} — Acc: {accuracy:.1%}")

Détection YOLO pour la Localisation des Lésions

La classification indique si une feuille est malade. La détection YOLO va plus loin : elle localise et comptabilise les lésions, permettant d’estimer la sévérité de l’infection.

from ultralytics import YOLO

# Fine-tuner YOLOv8 sur vos annotations de lésions
model = YOLO("yolov8m.pt")  # Medium = bon compromis sur Jetson

results = model.train(
    data="maladies_vigne.yaml",  # Votre dataset annoté
    epochs=100,
    imgsz=640,
    batch=16,
    device="0",  # GPU
    augment=True,
    mosaic=1.0,   # Augmentation mosaïque
    mixup=0.1,
    project="detection_maladies",
    name="mildiou_oïdium_v1"
)

# Inférence sur une image de terrain
model_trained = YOLO("detection_maladies/mildiou_oïdium_v1/weights/best.pt")
results = model_trained("photo_vigne_20250905.jpg", conf=0.5)

for r in results:
    print(f"Lésions détectées : {len(r.boxes)}")
    for box in r.boxes:
        maladie = model_trained.names[int(box.cls)]
        surface_px = (box.xyxy[0][2] - box.xyxy[0][0]) * (box.xyxy[0][3] - box.xyxy[0][1])
        print(f"  - {maladie} : confiance {float(box.conf):.0%}, surface {surface_px:.0f} px²")

Surveillance par Drone — Vue d’Ensemble Parcelle

Imagerie Multispectrale

Les drones équipés de capteurs multispectraux (NIR, Red-Edge) captent des longueurs d’onde invisibles à l’œil. L’indice NDVI (Normalized Difference Vegetation Index) révèle le stress végétal avant que les symptômes ne soient visibles :

import numpy as np
import rasterio
from rasterio.plot import show

# Charger l'image multispectrale (drone DJI Mavic 3 Multispectral)
with rasterio.open("parcelle_nord_20250901.tif") as src:
    rouge = src.read(3).astype(float)    # Bande rouge (665 nm)
    nir = src.read(5).astype(float)      # Proche infrarouge (842 nm)
    red_edge = src.read(4).astype(float) # Red-Edge (705 nm)

# Calcul NDVI
epsilon = 1e-10  # Éviter la division par zéro
ndvi = (nir - rouge) / (nir + rouge + epsilon)

# NDRE (plus sensible au stress précoce)
ndre = (nir - red_edge) / (nir + red_edge + epsilon)

# Cartographier les zones à risque (NDVI < 0.4 = stress probable)
zones_stress = ndvi < 0.4
print(f"Surface en stress : {zones_stress.sum() * 0.0001:.2f} ha")  # Résolution 10cm/pixel

# Générer des coordonnées GPS des zones à traiter
# (convertir pixels → coordonnées géographiques via le géoréférencement rasterio)

Pipeline de Traitement Drone Complet

from pathlib import Path
import geopandas as gpd
from shapely.geometry import box

def analyser_vol_drone(dossier_images: str, seuil_ndvi: float = 0.4):
    """
    Analyse complète d'un vol de drone :
    - Calcul NDVI sur l'orthophotographie
    - Détection des zones de stress
    - Export shapefile pour GPS tracteur
    """
    resultats = []

    for fichier in Path(dossier_images).glob("*.tif"):
        with rasterio.open(fichier) as src:
            transform = src.transform
            rouge = src.read(3).astype(float)
            nir = src.read(5).astype(float)
            ndvi = (nir - rouge) / (nir + rouge + 1e-10)

            # Trouver les blobs de pixels en stress
            from skimage import measure
            regions = measure.regionprops(measure.label(ndvi < seuil_ndvi))

            for region in regions:
                if region.area > 100:  # Ignorer les micro-zones (bruit)
                    # Convertir en coordonnées géographiques
                    row, col = region.centroid
                    lon, lat = rasterio.transform.xy(transform, row, col)
                    surface_m2 = region.area * (transform.a ** 2)

                    resultats.append({
                        "latitude": lat,
                        "longitude": lon,
                        "surface_m2": surface_m2,
                        "ndvi_moyen": float(ndvi[ndvi < seuil_ndvi].mean()),
                        "priorite": "HAUTE" if surface_m2 > 500 else "MOYENNE"
                    })

    # Export pour GPS tracteur
    gdf = gpd.GeoDataFrame(resultats,
        geometry=[box(r["longitude"]-0.0001, r["latitude"]-0.0001,
                      r["longitude"]+0.0001, r["latitude"]+0.0001)
                  for r in resultats])
    gdf.to_file("zones_traitement.shp")
    print(f"{len(resultats)} zones identifiées, export GPS prêt.")
    return resultats

Déploiement sur le Terrain — Application Mobile

Créer une App Mobile avec TFLite

Pour une utilisation sans connexion internet sur le terrain, le modèle peut être intégré dans une app Android/iOS :

# Convertir le modèle PyTorch → ONNX → TFLite pour mobile
import torch
import onnx
import tensorflow as tf

# Export ONNX depuis PyTorch
dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(
    model,
    dummy_input,
    "diagnostic_maladies.onnx",
    input_names=["image"],
    output_names=["predictions"],
    dynamic_axes={"image": {0: "batch_size"}}
)

# Conversion vers TFLite INT8 (pour mobile)
converter = tf.lite.TFLiteConverter.from_saved_model("modele_tf/")
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()

with open("diagnostic_maladies_int8.tflite", "wb") as f:
    f.write(tflite_model)

taille_mo = len(tflite_model) / (1024 * 1024)
print(f"Modèle mobile : {taille_mo:.1f} Mo")

ROI et Impact Économique pour les Exploitants

Calcul de Rentabilité

Sur une exploitation viticole de 50 hectares :

PosteSans IAAvec IA
Traitements fongicides (nb/saison)8-12 traitements4-6 traitements ciblés
Coût traitements (€/ha/an)800-1200 €400-600 €
Pertes récolte évitées10-20%< 5%
Temps de surveillance15h/semaine3h/semaine
Gain net estimé (50 ha)30 000-60 000 €/an

L’investissement initial (modèle IA + drone + app) s’amortit généralement en 1 à 2 saisons.

Précision des Modèles sur des Cultures Françaises

Les modèles entraînés sur PlantVillage atteignent 85 à 92 % de précision sur les maladies courantes. Avec du fine-tuning sur des images locales (cépage, conditions climatiques régionales), la précision monte à 94-97 %.

Exemples de maladies bien détectées : mildiou de la vigne, oïdium, botrytis, tavelure du pommier, alternariose de la tomate, fusariose du blé.

Conclusion

L’IA de vision transforme concrètement le diagnostic phytosanitaire : détection précoce, couverture exhaustive de la parcelle, recommandations de traitement ciblées. Ce n’est plus une technologie de laboratoire — des exploitants français l’utilisent aujourd’hui sur leurs smartphones et leurs drones.

La clé de succès : constituer un dataset d’images représentatives de vos cultures et conditions locales. 500 à 1 000 images annotées suffisent pour un premier modèle performant sur les maladies prioritaires.

Votre exploitation agricole fait face à des pertes récurrentes liées aux maladies ? Contactez-nous à contact@brio-novia.eu — nous concevons des outils de diagnostic IA adaptés à vos cultures et à vos conditions terrain.