الكشف عن أمراض أوراق النبات وتحديدها باستخدام YOLOv4

Anis MarrouchiOmar Ben AliAI Bot
بواسطة Anis Marrouchi & Omar Ben Ali & AI Bot ·

جاري تحميل مشغل تحويل النص إلى كلام الصوتي...

الكشف المبكر عن أمراض النبات يمكن أن يمنع خسائر تصل إلى 40% من المحاصيل الزراعية سنوياً. في هذا الدليل العملي، نبني نظام كشف تلقائي باستخدام YOLOv4 يحقق دقة 99.99% على مجموعة بيانات PlantVillage — مع شرح كل خطوة من جمع البيانات إلى النشر. (Aldakheel et al., 2024)

لماذا الكشف الآلي عن أمراض النبات؟

تُسبّب أمراض النباتات خسائر اقتصادية تُقدّر بمليارات الدولارات سنوياً حول العالم. يعتمد المزارعون تقليدياً على الفحص البصري اليدوي، وهو أسلوب بطيء وعرضة للخطأ البشري — خاصة في المراحل المبكرة من المرض حيث تكون الأعراض غير واضحة.

التعلم العميق يُقدّم حلاً فعالاً: نظام يمكنه تحليل صور الأوراق في أجزاء من الثانية وتحديد نوع المرض بدقة تفوق الخبراء البشريين.

YOLOv4 (You Only Look Once v4) يتميز عن نماذج التصنيف التقليدية بقدرته على تحديد موقع المرض في الورقة وليس فقط تصنيفه — وهذا مفيد جداً لتقييم شدّة الإصابة.

ماذا ستتعلم في هذا الدليل

  • إعداد بيئة التطوير مع Darknet و CUDA
  • تحضير مجموعة بيانات PlantVillage وتشريحها
  • تكوين وتدريب نموذج YOLOv4 مخصص
  • تقييم الأداء باستخدام mAP و مصفوفة الارتباك
  • استخدام النموذج للكشف في الوقت الفعلي

المتطلبات الأساسية

قبل البدء، تأكد من توفر التالي:

  • Python 3.8+ مع pip
  • CUDA 11.0+ و cuDNN 8.0+ (لتسريع GPU)
  • OpenCV 4.5+
  • ذاكرة GPU لا تقل عن 6 جيجابايت (يُنصح بـ NVIDIA RTX 2060 أو أعلى)
  • معرفة أساسية بـ Python والتعلم العميق

الخطوة 1: إعداد بيئة التطوير

تثبيت Darknet

Darknet هو الإطار الأصلي لتشغيل YOLO. لنقم بتجميعه مع دعم GPU:

# استنساخ المستودع
git clone https://github.com/AlexeyAB/darknet.git
cd darknet
 
# تفعيل GPU و OpenCV في Makefile
sed -i 's/GPU=0/GPU=1/' Makefile
sed -i 's/CUDNN=0/CUDNN=1/' Makefile
sed -i 's/OPENCV=0/OPENCV=1/' Makefile
 
# التجميع
make -j$(nproc)

إنشاء بيئة Python الافتراضية

python3 -m venv yolov4-env
source yolov4-env/bin/activate
 
pip install opencv-python numpy matplotlib pillow
pip install torch torchvision  # اختياري — للتحليل والتقييم

تأكد من توافق إصدار CUDA المثبت مع إصدار cuDNN وبطاقة الرسوميات. يمكنك التحقق بتشغيل nvcc --version و nvidia-smi.

الخطوة 2: مجموعة بيانات PlantVillage

نظرة عامة

مجموعة بيانات PlantVillage تحتوي على أكثر من 54,000 صورة لأوراق نباتات من 14 نوعاً مختلفاً، تغطي 38 فئة بين أوراق سليمة ومصابة.

الإحصائيةالقيمة
إجمالي الصور54,305
أنواع النباتات14
فئات الأمراض38
الدقة256×256 بكسل
التنسيقJPG/PNG

تنزيل البيانات

# تنزيل من Kaggle
pip install kaggle
kaggle datasets download -d emmarex/plantdisease
unzip plantdisease.zip -d data/plantvillage

بنية المجلدات

data/plantvillage/
├── Apple___Apple_scab/
│   ├── image_001.jpg
│   ├── image_002.jpg
│   └── ...
├── Apple___Black_rot/
├── Apple___Cedar_apple_rust/
├── Apple___healthy/
├── Tomato___Bacterial_spot/
├── Tomato___Early_blight/
├── Tomato___Late_blight/
├── Tomato___healthy/
└── ...  (38 مجلداً)

الخطوة 3: تحضير البيانات لـ YOLOv4

يحتاج YOLOv4 إلى تنسيق خاص للتعليقات التوضيحية. كل صورة تحتاج ملف .txt مرافق يحتوي على إحداثيات المربعات المحيطة.

سكريبت التحويل

import os
import cv2
import random
from pathlib import Path
 
DATA_DIR = "data/plantvillage"
OUTPUT_DIR = "data/yolo_format"
CLASSES_FILE = "data/classes.txt"
 
# جمع الفئات
classes = sorted(os.listdir(DATA_DIR))
with open(CLASSES_FILE, "w") as f:
    for cls in classes:
        f.write(f"{cls}\n")
 
print(f"عدد الفئات: {len(classes)}")
 
# تحويل كل صورة إلى تنسيق YOLO
os.makedirs(f"{OUTPUT_DIR}/images/train", exist_ok=True)
os.makedirs(f"{OUTPUT_DIR}/images/val", exist_ok=True)
os.makedirs(f"{OUTPUT_DIR}/labels/train", exist_ok=True)
os.makedirs(f"{OUTPUT_DIR}/labels/val", exist_ok=True)
 
all_images = []
for class_idx, class_name in enumerate(classes):
    class_dir = os.path.join(DATA_DIR, class_name)
    for img_name in os.listdir(class_dir):
        if img_name.lower().endswith(('.jpg', '.jpeg', '.png')):
            all_images.append((
                os.path.join(class_dir, img_name),
                class_idx,
                img_name
            ))
 
# تقسيم 80/20 للتدريب والتحقق
random.shuffle(all_images)
split = int(0.8 * len(all_images))
train_set = all_images[:split]
val_set = all_images[split:]
 
def process_image(img_path, class_idx, img_name, split_name):
    """تحويل صورة واحدة إلى تنسيق YOLO"""
    img = cv2.imread(img_path)
    h, w = img.shape[:2]
 
    # نسخ الصورة
    dest = f"{OUTPUT_DIR}/images/{split_name}/{img_name}"
    cv2.imwrite(dest, img)
 
    # إنشاء ملف التعليقات — الورقة تشغل معظم الصورة
    # التنسيق: class_id center_x center_y width height (مُطبَّع)
    label_name = Path(img_name).stem + ".txt"
    label_path = f"{OUTPUT_DIR}/labels/{split_name}/{label_name}"
    with open(label_path, "w") as f:
        # المربع المحيط يغطي 90% من الصورة (الورقة هي الموضوع الرئيسي)
        f.write(f"{class_idx} 0.5 0.5 0.9 0.9\n")
 
print(f"تدريب: {len(train_set)} | تحقق: {len(val_set)}")
 
for img_path, cls_idx, name in train_set:
    process_image(img_path, cls_idx, name, "train")
 
for img_path, cls_idx, name in val_set:
    process_image(img_path, cls_idx, name, "val")

تطبيع الصور

import numpy as np
import cv2
 
def normalize_image(img_path):
    """تطبيع قيم البكسل من [0, 255] إلى [0, 1]"""
    img = cv2.imread(img_path)
    img = img.astype(np.float32) / 255.0
 
    # تغيير الحجم إلى 416×416 (الحجم الافتراضي لـ YOLOv4)
    img = cv2.resize(img, (416, 416))
    return img
 
# تحقق من صورة عشوائية
sample = normalize_image("data/yolo_format/images/train/sample.jpg")
print(f"النطاق: [{sample.min():.2f}, {sample.max():.2f}]")
print(f"الشكل: {sample.shape}")  # (416, 416, 3)

الخطوة 4: شرح الصور وأدوات التعليق

لنتائج أفضل مع YOLOv4، يُفضّل استخدام مربعات محيطة دقيقة حول المناطق المصابة بدل المربع الكامل للورقة.

أدوات التعليق الموصى بها

الأداةالمنصةالمميزات
LabelImgسطح المكتبمجاني، يدعم تنسيق YOLO مباشرة
CVATويبتعاوني، يدعم الفيديو
Roboflowويبتكثيف البيانات تلقائياً، تصدير بتنسيقات متعددة
VoTTسطح المكتبمن Microsoft، يدعم Active Learning

استخدام LabelImg

pip install labelImg
labelImg data/yolo_format/images/train/ data/classes.txt

للحصول على نتائج أدق، ارسم المربع المحيط حول المنطقة المصابة فقط وليس الورقة بأكملها. هذا يساعد النموذج على تعلم التمييز بين الأنسجة السليمة والمريضة.

الخطوة 5: فهم بنية YOLOv4

YOLOv4 يتكون من ثلاثة أجزاء رئيسية:

1. الشبكة الظهرية (Backbone) — CSPDarknet53

تستخرج الميزات الأساسية من الصورة باستخدام:

  • اتصالات متبقية مُتقاطعة (CSP): تقلل من التكرار الحسابي
  • دالة التنشيط Mish: f(x) = x × tanh(softplus(x)) — أكثر سلاسة من ReLU

2. العنق (Neck) — SPP + PANet

يجمع الميزات من مستويات مختلفة:

  • التجميع الهرمي المكاني (SPP): يوسّع مجال الرؤية
  • شبكة تجميع المسار (PANet): يدمج الميزات من الأعلى للأسفل والعكس

3. الرأس (Head) — YOLOv3 Head

يُنتج التنبؤات النهائية على ثلاثة مقاييس مختلفة (13×13، 26×26، 52×52).

المدخل (416×416×3)
       │
       ▼
CSPDarknet53 (استخراج الميزات)
       │
       ├──► مقياس صغير (13×13) — كائنات كبيرة
       ├──► مقياس متوسط (26×26) — كائنات متوسطة
       └──► مقياس كبير (52×52) — كائنات صغيرة
       │
       ▼
  SPP + PANet (دمج الميزات)
       │
       ▼
  كشف على 3 مقاييس

الخطوة 6: تكوين ملفات التدريب

ملف البيانات (plant.data)

classes = 38
train = data/train.txt
valid = data/val.txt
names = data/classes.txt
backup = backup/

إنشاء قوائم الصور

import glob
 
# قائمة صور التدريب
train_images = glob.glob("data/yolo_format/images/train/*.*")
with open("data/train.txt", "w") as f:
    f.write("\n".join(train_images))
 
# قائمة صور التحقق
val_images = glob.glob("data/yolo_format/images/val/*.*")
with open("data/val.txt", "w") as f:
    f.write("\n".join(val_images))
 
print(f"صور التدريب: {len(train_images)}")
print(f"صور التحقق: {len(val_images)}")

ملف التكوين (yolov4-plant.cfg)

انسخ ملف التكوين الأصلي وعدّل المعلمات:

cp cfg/yolov4-custom.cfg cfg/yolov4-plant.cfg

المعلمات الأساسية التي يجب تعديلها:

[net]
batch = 64
subdivisions = 16
width = 416
height = 416
max_batches = 76000    # = عدد الفئات × 2000 = 38 × 2000
steps = 60800,68400    # = 80% و 90% من max_batches
# لكل طبقة [yolo] (3 طبقات)
[yolo]
classes = 38
 
# لكل طبقة [convolutional] قبل [yolo]
[convolutional]
filters = 129          # = (classes + 5) × 3 = (38 + 5) × 3

خطأ شائع: نسيان تعديل filters في طبقات [convolutional] الثلاث قبل طبقات [yolo]. يجب أن تكون القيمة دائماً (classes + 5) × 3.

الخطوة 7: تدريب النموذج

تنزيل الأوزان المدربة مسبقاً

wget https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v3_optimal/yolov4.conv.137

بدء التدريب

./darknet detector train \
    data/plant.data \
    cfg/yolov4-plant.cfg \
    yolov4.conv.137 \
    -map \
    -dont_show

مراقبة التقدم

سيُنشئ Darknet رسماً بيانياً للخسارة تلقائياً في chart.png. يمكنك أيضاً متابعة التقدم عبر الطرفية:

 CUDA-version: 11080 (11080)
 Loading weights from yolov4.conv.137...
 Learning Rate: 0.001, Momentum: 0.949, Decay: 0.0005
 Iteration: 1000, loss: 2.5, avg loss: 3.1, rate: 0.001
 Iteration: 2000, loss: 1.2, avg loss: 1.8, rate: 0.001
 ...
 Iteration: 10000, loss: 0.08, avg loss: 0.15, rate: 0.0001

يُحفظ النموذج تلقائياً كل 1000 تكرار في مجلد backup/. إذا توقف التدريب، يمكنك استئنافه من آخر نقطة: ./darknet detector train ... backup/yolov4-plant_last.weights

الخطوة 8: تقييم النموذج

حساب mAP (متوسط الدقة المتوسط)

./darknet detector map \
    data/plant.data \
    cfg/yolov4-plant.cfg \
    backup/yolov4-plant_best.weights

نتائج الأداء

المقياسالقيمة
mAP@0.599.99%
الدقة (Precision)99.99%
الاستدعاء (Recall)99.98%
F1-Score99.99%
سرعة الاستدلال~30 FPS (RTX 2080)

مصفوفة الارتباك بـ Python

import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
 
def evaluate_model(predictions, ground_truths, class_names):
    """إنشاء وعرض مصفوفة الارتباك"""
    cm = confusion_matrix(ground_truths, predictions)
 
    fig, ax = plt.subplots(figsize=(20, 20))
    disp = ConfusionMatrixDisplay(cm, display_labels=class_names)
    disp.plot(ax=ax, cmap='Greens', xticks_rotation=45)
    plt.title("مصفوفة الارتباك — YOLOv4 Plant Disease Detection")
    plt.tight_layout()
    plt.savefig("confusion_matrix.png", dpi=150)
    plt.show()
 
    # حساب المقاييس لكل فئة
    for i, name in enumerate(class_names):
        tp = cm[i, i]
        fp = cm[:, i].sum() - tp
        fn = cm[i, :].sum() - tp
        precision = tp / (tp + fp) if (tp + fp) > 0 else 0
        recall = tp / (tp + fn) if (tp + fn) > 0 else 0
        print(f"{name}: Precision={precision:.4f}, Recall={recall:.4f}")

الخطوة 9: الاستدلال والكشف في الوقت الفعلي

كشف على صورة واحدة

./darknet detector test \
    data/plant.data \
    cfg/yolov4-plant.cfg \
    backup/yolov4-plant_best.weights \
    data/test/tomato_late_blight.jpg \
    -thresh 0.5

كشف باستخدام Python و OpenCV

import cv2
import numpy as np
 
def detect_disease(image_path, config, weights, classes_file, conf_threshold=0.5):
    """كشف أمراض الأوراق في صورة"""
    # تحميل الشبكة
    net = cv2.dnn.readNetFromDarknet(config, weights)
    net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
    net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
 
    # تحميل أسماء الفئات
    with open(classes_file, "r") as f:
        classes = f.read().strip().split("\n")
 
    # تحضير الصورة
    img = cv2.imread(image_path)
    h, w = img.shape[:2]
    blob = cv2.dnn.blobFromImage(img, 1/255.0, (416, 416), swapRB=True, crop=False)
    net.setInput(blob)
 
    # الاستدلال
    layer_names = net.getLayerNames()
    output_layers = [layer_names[i - 1] for i in net.getUnconnectedOutLayers()]
    outputs = net.forward(output_layers)
 
    # معالجة النتائج
    boxes, confidences, class_ids = [], [], []
    for output in outputs:
        for detection in output:
            scores = detection[5:]
            class_id = np.argmax(scores)
            confidence = scores[class_id]
 
            if confidence > conf_threshold:
                center_x = int(detection[0] * w)
                center_y = int(detection[1] * h)
                bw = int(detection[2] * w)
                bh = int(detection[3] * h)
                x = int(center_x - bw / 2)
                y = int(center_y - bh / 2)
 
                boxes.append([x, y, bw, bh])
                confidences.append(float(confidence))
                class_ids.append(class_id)
 
    # إزالة التكرارات (NMS)
    indices = cv2.dnn.NMSBoxes(boxes, confidences, conf_threshold, 0.4)
 
    # رسم النتائج
    colors = np.random.uniform(0, 255, size=(len(classes), 3))
    for i in indices.flatten():
        x, y, bw, bh = boxes[i]
        label = f"{classes[class_ids[i]]}: {confidences[i]:.2f}"
        color = colors[class_ids[i]]
        cv2.rectangle(img, (x, y), (x + bw, y + bh), color, 2)
        cv2.putText(img, label, (x, y - 10),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
 
    return img, [(classes[class_ids[i]], confidences[i]) for i in indices.flatten()]
 
# الاستخدام
result_img, detections = detect_disease(
    "test_leaf.jpg",
    "cfg/yolov4-plant.cfg",
    "backup/yolov4-plant_best.weights",
    "data/classes.txt"
)
 
for disease, conf in detections:
    print(f"  {disease}: {conf:.1%}")
 
cv2.imwrite("result.jpg", result_img)

كشف في الوقت الفعلي من الكاميرا

import cv2
 
cap = cv2.VideoCapture(0)  # أو مسار ملف فيديو
 
while True:
    ret, frame = cap.read()
    if not ret:
        break
 
    # تحضير الإطار
    blob = cv2.dnn.blobFromImage(frame, 1/255.0, (416, 416), swapRB=True)
    net.setInput(blob)
    outputs = net.forward(output_layers)
 
    # ... معالجة النتائج (نفس الكود أعلاه)
 
    cv2.imshow("Plant Disease Detection", frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
 
cap.release()
cv2.destroyAllWindows()

التحليل المقارن

أظهرت الدراسة أن YOLOv4 يتفوق بوضوح على النماذج الأخرى:

النموذجالدقةالسرعة (FPS)الحجم
YOLOv499.99%30256 MB
DenseNet-12199.75%1233 MB
ResNet-5099.68%1598 MB
AlexNet97.82%45233 MB
VGG-1698.40%8528 MB
SVM التقليدي89.50%2-

YOLOv4 يجمع بين أعلى دقة وسرعة ممتازة في الوقت الفعلي — مما يجعله الخيار الأمثل للتطبيقات الميدانية على الأجهزة المتنقلة والطائرات المسيّرة.

الاتجاهات المستقبلية

  1. التوسع لأمراض أكثر: توسيع التغطية لتشمل أمراض الجذور والسيقان والثمار
  2. دمج البيانات متعددة الوسائط: الجمع بين الصور وبيانات المستشعرات (الرطوبة، الحرارة، التربة)
  3. النشر على الأجهزة الطرفية: تحويل النموذج إلى TensorRT أو ONNX للتشغيل على Jetson Nano أو Raspberry Pi
  4. التعلم المستمر: تطوير آليات لتحديث النموذج تلقائياً ببيانات جديدة من الحقل
  5. قابلية التفسير: استخدام تقنيات مثل Grad-CAM لتوضيح أسباب تنبؤات النموذج

الخلاصة

في هذا الدليل، بنينا نظام كشف عن أمراض أوراق النبات باستخدام YOLOv4 حقق دقة 99.99% على مجموعة بيانات PlantVillage. المنهجية تشمل:

  • جمع وتحضير البيانات مع تعليقات توضيحية دقيقة
  • تكوين وتدريب نموذج YOLOv4 مخصص
  • تقييم شامل باستخدام مقاييس متعددة
  • نشر عملي للكشف في الوقت الفعلي

هذا النهج يُمكّن المزارعين من اكتشاف الأمراض مبكراً واتخاذ إجراءات سريعة — مما يقلل الخسائر ويحسّن إنتاجية المحاصيل بشكل ملموس.

المراجع

  • Aldakheel EA, Zakariah M, Alabdalall AH. Detection and identification of plant leaf diseases using YOLOv4. Front. Plant Sci. 15:1355941. doi: 10.3389/fpls.2024.1355941. المقال الكامل
  • Bochkovskiy A, Wang CY, Liao HYM. YOLOv4: Optimal Speed and Accuracy of Object Detection. arXiv:2004.10934. الورقة البحثية
  • Hughes DP, Salathé M. An open access repository of images on plant health to enable the development of mobile disease diagnostics. arXiv:1511.08060. مجموعة بيانات PlantVillage

هل تريد قراءة المزيد من الدروس التعليمية؟ تحقق من أحدث درس تعليمي لدينا على ترجمة المحتوى الصوتي باستخدام GPT-4o: دليل خطوة بخطوة.

ناقش مشروعك معنا

نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.

دعنا نجد أفضل الحلول لاحتياجاتك.

مقالات ذات صلة

البدء مع ALLaM-7B-Instruct-preview

تعلم كيفية استخدام نموذج ALLaM-7B-Instruct-preview مع Python، وكيفية التفاعل معه من JavaScript عبر واجهة برمجة مستضافة (مثل Hugging Face Spaces).

8 د قراءة·