SahilCodevally's picture
Init commit
e1c010e
Raw History Blame Contribute Delete
4.45 kB
"""
Vision Module — Object detection using YOLOv8.
Wraps the Ultralytics YOLO model to detect objects in images and return
structured results. The model is loaded **once** in the constructor and
reused for all subsequent detections.
"""
import logging
from typing import Any
import numpy as np
logger = logging.getLogger(__name__)
class ObjectDetector:
"""
Detects objects in images using a pretrained YOLOv8 model.
Attributes:
model: The loaded YOLO model instance (cached after first load).
"""
def __init__(self, model_name: str = "yolov8n.pt", confidence: float = 0.25):
"""
Initialize the detector and load the YOLO model.
Args:
model_name: Name or path of the YOLO model weights.
confidence: Minimum confidence threshold for detections.
"""
self.model_name = model_name
self.confidence = confidence
self.model = None
self._load_model()
def _load_model(self) -> None:
"""Load the YOLO model. Called once during initialization."""
try:
from ultralytics import YOLO
logger.info("Loading YOLO model: %s", self.model_name)
self.model = YOLO(self.model_name)
logger.info("YOLO model loaded successfully.")
except Exception as exc:
logger.error("Failed to load YOLO model '%s': %s", self.model_name, exc)
raise RuntimeError(
f"Could not load YOLO model '{self.model_name}'. "
f"Ensure ultralytics is installed and the model file is accessible."
) from exc
def detect(self, image: np.ndarray) -> list[dict[str, Any]]:
"""
Run object detection on an image.
Args:
image: Input image as a NumPy array (H, W, C) in BGR or RGB format.
Returns:
List of detected objects, each containing:
- ``label`` (str): Class name.
- ``confidence`` (float): Detection confidence (0-1).
- ``bbox`` (list[int]): Bounding box as [x1, y1, x2, y2].
Raises:
ValueError: If the input image is invalid.
RuntimeError: If the model is not loaded.
"""
if self.model is None:
raise RuntimeError("YOLO model is not loaded.")
if image is None or not isinstance(image, np.ndarray):
raise ValueError("Invalid image: expected a NumPy ndarray.")
if image.ndim < 2:
raise ValueError(
f"Invalid image dimensions: expected 2D or 3D array, "
f"got {image.ndim}D."
)
logger.info(
"Running detection on image of shape %s with confidence=%.2f",
image.shape,
self.confidence,
)
try:
results = self.model(image, conf=self.confidence, verbose=False)
except Exception as exc:
logger.error("YOLO inference failed: %s", exc)
return []
detections = self._parse_results(results)
logger.info("Detected %d objects.", len(detections))
return detections
def _parse_results(self, results: Any) -> list[dict[str, Any]]:
"""
Parse YOLO results into a structured list of detections.
Args:
results: Raw YOLO results object.
Returns:
Structured list of detection dictionaries.
"""
detections: list[dict[str, Any]] = []
for result in results:
boxes = result.boxes
if boxes is None:
continue
for i in range(len(boxes)):
try:
bbox = boxes.xyxy[i].cpu().numpy().tolist()
confidence = float(boxes.conf[i].cpu().numpy())
class_id = int(boxes.cls[i].cpu().numpy())
label = result.names.get(class_id, f"class_{class_id}")
detections.append(
{
"label": label,
"confidence": round(confidence, 4),
"bbox": [int(coord) for coord in bbox],
}
)
except (IndexError, KeyError, AttributeError) as exc:
logger.warning("Failed to parse detection %d: %s", i, exc)
continue
return detections