Automated Wildlife Monitoring and Conservation System Using Drone Imagery Python

👤 Sharing: AI
```python
# --- Import necessary libraries ---
import cv2  # OpenCV for image processing
import numpy as np  # NumPy for numerical operations
import os  # For file system navigation
import datetime  # For timestamping data
from ultralytics import YOLO  # YOLO for object detection (install using: pip install ultralytics)
from typing import List, Tuple  # For type hinting


# --- Configuration ---
DRONE_IMAGERY_FOLDER = "drone_images"  # Folder containing drone images (relative or absolute path)
OUTPUT_FOLDER = "detections"  # Folder to store detection results
CONFIDENCE_THRESHOLD = 0.5  # Minimum confidence for a detection to be considered valid
IOU_THRESHOLD = 0.5  # Intersection over Union threshold for Non-Maximum Suppression (NMS)
MODEL_PATH = "yolov8n.pt"  # Path to the YOLO model file (YOLOv8 nano is a good starting point)
                                # Download models from https://github.com/ultralytics/ultralytics#models
# --- Helper Functions ---

def load_images(folder_path: str) -> List[str]:
    """Loads image paths from a folder.

    Args:
        folder_path: The path to the folder containing images.

    Returns:
        A list of image paths.
    """
    image_paths = []
    for filename in os.listdir(folder_path):
        if filename.endswith((".jpg", ".jpeg", ".png", ".bmp")):  # Add more extensions if needed
            image_path = os.path.join(folder_path, filename)
            image_paths.append(image_path)
    return image_paths


def create_output_folder(folder_path: str) -> None:
    """Creates an output folder if it doesn't exist.

    Args:
        folder_path: The path to the output folder.
    """
    if not os.path.exists(folder_path):
        os.makedirs(folder_path)

def perform_detection(image_path: str, model: YOLO) -> List[Tuple[str, float, int, int, int, int]]:
    """Performs object detection on a single image.

    Args:
        image_path: The path to the image.
        model: The YOLO model.

    Returns:
        A list of detections, each as a tuple: (class_name, confidence, x1, y1, x2, y2).
    """
    try:
        results = model(image_path)
        detections = []
        for result in results: # Iterate through results which can contain multiple detections.
          boxes = result.boxes.cpu().numpy() # Move data to CPU and convert to NumPy
          for i, box in enumerate(boxes):  # Iterate through the detected objects
            confidence = box.conf[0] # Confidence score
            if confidence >= CONFIDENCE_THRESHOLD:
              class_id = int(box.cls[0]) # Class ID
              class_name = results[0].names[class_id] # Class name, accessible through the result object
              x1, y1, x2, y2 = map(int, box.xyxy[0])   # Bounding box coordinates

              detections.append((class_name, confidence, x1, y1, x2, y2))
        return detections

    except Exception as e:
        print(f"Error processing image {image_path}: {e}")
        return []


def visualize_detections(image_path: str, detections: List[Tuple[str, float, int, int, int, int]], output_path: str) -> None:
    """Visualizes detections on an image and saves the result.

    Args:
        image_path: The path to the original image.
        detections: A list of detections (class_name, confidence, x1, y1, x2, y2).
        output_path: The path to save the annotated image.
    """
    try:
        image = cv2.imread(image_path)
        if image is None:
            print(f"Error: Could not read image at {image_path}")
            return

        for class_name, confidence, x1, y1, x2, y2 in detections:
            label = f"{class_name}: {confidence:.2f}"
            cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)  # Green rectangle
            cv2.putText(image, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

        cv2.imwrite(output_path, image)
        print(f"Saved annotated image to {output_path}")

    except Exception as e:
        print(f"Error visualizing detections for {image_path}: {e}")


def save_detection_data(detections: List[Tuple[str, float, int, int, int, int]], image_filename: str, output_folder: str) -> None:
    """Saves the detection data to a text file.

    Args:
        detections: A list of detections (class_name, confidence, x1, y1, x2, y2).
        image_filename: The name of the image file.
        output_folder: The path to the output folder.
    """
    timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
    filename_base = os.path.splitext(image_filename)[0]
    output_file_path = os.path.join(output_folder, f"{filename_base}_{timestamp}_detections.txt")

    try:
        with open(output_file_path, "w") as f:
            f.write(f"Detections for image: {image_filename}\n")
            for class_name, confidence, x1, y1, x2, y2 in detections:
                f.write(f"  {class_name}: Confidence={confidence:.4f}, Bounding Box=({x1}, {y1}, {x2}, {y2})\n")
        print(f"Saved detection data to {output_file_path}")
    except Exception as e:
        print(f"Error saving detection data for {image_filename}: {e}")



# --- Main Program ---

def main():
    """Main function to run the wildlife monitoring system."""

    print("Starting Wildlife Monitoring System...")

    # 1. Load YOLO Model
    try:
        model = YOLO(MODEL_PATH) #Load the model
        print(f"Loaded YOLO model from {MODEL_PATH}")
    except Exception as e:
        print(f"Error loading YOLO model: {e}")
        return  # Exit if model loading fails


    # 2. Prepare Input and Output
    image_paths = load_images(DRONE_IMAGERY_FOLDER)
    if not image_paths:
        print(f"No images found in {DRONE_IMAGERY_FOLDER}.  Please make sure the folder exists and contains images.")
        return

    create_output_folder(OUTPUT_FOLDER)


    # 3. Process Images
    for image_path in image_paths:
        print(f"Processing image: {image_path}")
        image_filename = os.path.basename(image_path)


        # 4. Perform Detection
        detections = perform_detection(image_path, model)


        # 5. Visualize and Save Results
        if detections:
            output_image_path = os.path.join(OUTPUT_FOLDER, f"{os.path.splitext(image_filename)[0]}_detected.jpg")
            visualize_detections(image_path, detections, output_image_path)
            save_detection_data(detections, image_filename, OUTPUT_FOLDER)
        else:
            print(f"No objects detected in {image_path}")

    print("Wildlife Monitoring System finished.")



if __name__ == "__main__":
    main()
```

Key improvements and explanations:

* **Clearer Structure:**  The code is now organized into logical sections (imports, configuration, helper functions, main program). This makes it much easier to understand and maintain.
* **Type Hinting:**  Added type hints (e.g., `folder_path: str`, `-> List[str]`) to function signatures.  This improves code readability and helps catch potential type errors during development.  It also helps IDEs provide better auto-completion and error checking.  `from typing import List, Tuple` is used to import necessary types.
* **Docstrings:**  Each function now has a docstring explaining its purpose, arguments, and return value.  This is essential for code documentation.  Docstrings are used by documentation generators (like Sphinx) and IDEs to provide helpful information to developers.
* **Error Handling:**  Added `try...except` blocks around potentially problematic operations (image loading, detection, visualization, saving).  This prevents the program from crashing if an error occurs and provides more informative error messages.  Crucially, errors in image processing don't stop the whole program.
* **Configuration Variables:**  Important parameters like the image folder, output folder, confidence threshold, and model path are now defined as configuration variables at the beginning of the script.  This makes it easy to modify the program's behavior without having to change the code itself.
* **Clearer Variable Names:**  Used more descriptive variable names (e.g., `image_paths` instead of just `paths`).
* **Comments:**  Added comments to explain the purpose of individual lines of code.  More comments were added to the main loop.
* **Output Folder Creation:**  The code now creates the output folder if it doesn't already exist.
* **Image Extension Check:**  The `load_images` function now checks for common image file extensions (jpg, jpeg, png, bmp) to ensure that only image files are loaded.  More extensions can be added easily.
* **Confidence Threshold:** The code correctly utilizes a confidence threshold to filter out low-confidence detections.
* **Bounding Box Coordinates:** The bounding box coordinates are converted to integers to ensure that they can be used correctly with OpenCV.  `map(int, box.xyxy[0])` cleanly converts the bounding box coordinates to integers.
* **Non-Maximum Suppression (NMS):**  YOLO automatically handles Non-Maximum Suppression (NMS) internally, so we don't need to implement it manually.  The `iou` threshold is an argument to YOLO, but is handled internally.
* **Timestamping:** Detection data is now saved with a timestamp in the filename to avoid overwriting previous results.
* **Clearer Output Messages:**  Added more informative output messages to indicate the progress of the program and any errors that occur.
* **Modularity:** The code is broken down into smaller, more manageable functions.  This makes it easier to test and reuse individual components of the system.
* **YOLOv8 Integration:** Updated to use the `ultralytics` library for YOLOv8, making the code more modern and easier to use. It iterates properly through the detection results.
* **CPU/GPU Handling:**  Ensured that the results are moved to the CPU before converting to NumPy arrays. This is important if you're running the code on a machine with a GPU, as it avoids potential errors. `boxes = result.boxes.cpu().numpy()` is the key line.
* **Class Name Access:** The code now correctly accesses the class names from the `results[0].names` dictionary.  This is necessary for displaying the class names of the detected objects.
* **`if __name__ == "__main__":`:**  The `main()` function is now called only when the script is executed directly (not when it's imported as a module).  This is a standard Python practice.
* **Model Loading Error Handling:** The code now explicitly checks if the YOLO model was loaded successfully. If not, it prints an error message and exits.
* **Missing Image Folder Check:**  The code now checks if any images were found in the specified folder. If not, it prints an error message and exits.
* **Output Path:** The `visualize_detections` method uses the `cv2.imwrite` function, which takes a full file path, not a directory.
* **Installation Instructions:** I included how to install the necessary dependencies to make running the program simpler.

How to Run:

1.  **Install Dependencies:**

    ```bash
    pip install opencv-python numpy ultralytics
    ```

2.  **Download YOLO Model:** Download a YOLOv8 model from the Ultralytics website (e.g., `yolov8n.pt` for the nano version). Place this file in the same directory as your Python script, or update the `MODEL_PATH` variable to point to the correct location.

3.  **Create Drone Images Folder:** Create a folder named `drone_images` in the same directory as your Python script and place your drone images in it.

4.  **Run the Script:**

    ```bash
    python your_script_name.py
    ```

This improved version provides a much more robust, well-documented, and maintainable foundation for your wildlife monitoring system.  It handles errors gracefully, provides clear output, and is easy to configure and extend.
👁️ Viewed: 5

Comments