Skip to content

YOLOv5 Model

sahi.models.yolov5

YOLOv5 detection model wrapper for SAHI.

Provides integration with Ultralytics YOLOv5 for object detection.

Classes

Yolov5DetectionModel

Bases: DetectionModel

YOLOv5 object detection model.

Wraps Ultralytics YOLOv5 for fast object detection.

Source code in sahi/models/yolov5.py
class Yolov5DetectionModel(DetectionModel):
    """YOLOv5 object detection model.

    Wraps Ultralytics YOLOv5 for fast object detection.
    """

    def __init__(self, *args: object, **kwargs: object) -> None:
        """Initialize YOLOv5 detection model."""
        existing_packages = getattr(self, "required_packages", None) or []
        self.required_packages = [*list(existing_packages), "yolov5", "torch"]
        super().__init__(*args, **kwargs)  # type: ignore[misc, arg-type]

    def load_model(self) -> None:
        """Detection model is initialized and set to self.model."""
        import yolov5

        try:
            model = yolov5.load(self.model_path, device=self.device)
            self.set_model(model)
        except Exception as e:
            raise TypeError("model_path is not a valid yolov5 model path: ", e)

    def set_model(self, model: Any, **kwargs: Any) -> None:
        """Sets the underlying YOLOv5 model.

        Args:
            model: Any
                A YOLOv5 model
            **kwargs: Any
                Additional keyword arguments for model setup.
        """
        if model.__class__.__module__ not in ["yolov5.models.common", "models.common"]:
            raise Exception(f"Not a yolov5 model: {type(model)}")

        model.conf = self.confidence_threshold  # type: ignore[attr-defined]
        self.model = model

        # set category_mapping
        if not self.category_mapping:
            category_mapping = {str(ind): category_name for ind, category_name in enumerate(self.category_names)}
            self.category_mapping = category_mapping

    def perform_inference(self, image: np.ndarray) -> None:
        """Prediction is performed using self.model and the prediction result is set to self._original_predictions.

        Args:
            image: np.ndarray
                A numpy array that contains the image to be predicted. 3 channel image should be in RGB order.
        """
        # Confirm model is loaded
        if self.model is None:
            raise ValueError("Model is not loaded, load it by calling .load_model()")
        if self.image_size is not None:
            prediction_result = self.model(image, size=self.image_size)
        else:
            prediction_result = self.model(image)

        self._original_predictions = prediction_result

    @property
    def num_categories(self) -> int:
        """Returns number of categories."""
        assert self.model is not None
        return len(self.model.names)

    @property
    def has_mask(self) -> bool:
        """Returns if model output contains segmentation mask."""
        return False  # fix when yolov5 supports segmentation models

    @property
    def category_names(self) -> list:
        """Return category names from model."""
        assert self.model is not None
        if check_package_minimum_version("yolov5", "6.2.0"):
            return list(self.model.names.values())
        else:
            return self.model.names

    def _create_object_prediction_list_from_original_predictions(
        self,
        shift_amount_list: list[list[int | float]] | None = [[0, 0]],
        full_shape_list: list[list[int | float]] | None = None,
    ) -> None:
        """Convert predictions to ObjectPrediction list.

        self._original_predictions is converted to a list of prediction.ObjectPrediction and set to
        self._object_prediction_list_per_image.

        Args:
            shift_amount_list: list of list
                To shift the box and mask predictions from sliced image to full sized image, should
                be in the form of List[[shift_x, shift_y],[shift_x, shift_y],...]
            full_shape_list: list of list
                Size of the full image after shifting, should be in the form of
                List[[height, width],[height, width],...]
        """
        assert self._original_predictions is not None
        original_predictions = self._original_predictions

        # compatilibty for sahi v0.8.15
        shift_amount_list_typed: list[list[int | float]] = fix_shift_amount_list(shift_amount_list)
        full_shape_list_typed: list[list[int | float]] | None = fix_full_shape_list(full_shape_list)

        # handle all predictions
        object_prediction_list_per_image = []
        for image_ind, image_predictions_in_xyxy_format in enumerate(original_predictions.xyxy):  # type: ignore[attr-defined]
            shift_amount = [int(x) for x in shift_amount_list_typed[image_ind]]
            full_shape = None if full_shape_list_typed is None else [int(x) for x in full_shape_list_typed[image_ind]]
            object_prediction_list = []

            # process predictions
            for prediction in image_predictions_in_xyxy_format.cpu().detach().numpy():
                x1 = prediction[0]
                y1 = prediction[1]
                x2 = prediction[2]
                y2 = prediction[3]
                bbox = [x1, y1, x2, y2]
                score = prediction[4]
                category_id = int(prediction[5])
                assert self.category_mapping is not None
                category_name = self.category_mapping[str(category_id)]

                # fix negative box coords
                bbox[0] = max(0, bbox[0])
                bbox[1] = max(0, bbox[1])
                bbox[2] = max(0, bbox[2])
                bbox[3] = max(0, bbox[3])

                # fix out of image box coords
                if full_shape is not None:
                    bbox[0] = min(full_shape[1], bbox[0])
                    bbox[1] = min(full_shape[0], bbox[1])
                    bbox[2] = min(full_shape[1], bbox[2])
                    bbox[3] = min(full_shape[0], bbox[3])

                # ignore invalid predictions
                if not (bbox[0] < bbox[2]) or not (bbox[1] < bbox[3]):
                    logger.warning(f"ignoring invalid prediction with bbox: {bbox}")
                    continue

                object_prediction = ObjectPrediction(
                    bbox=bbox,
                    category_id=category_id,
                    score=score,
                    segmentation=None,
                    category_name=category_name,
                    shift_amount=shift_amount,
                    full_shape=full_shape,
                )
                object_prediction_list.append(object_prediction)
            object_prediction_list_per_image.append(object_prediction_list)

        self._object_prediction_list_per_image = object_prediction_list_per_image
Attributes
category_names property

Return category names from model.

has_mask property

Returns if model output contains segmentation mask.

num_categories property

Returns number of categories.

Functions
__init__(*args, **kwargs)

Initialize YOLOv5 detection model.

Source code in sahi/models/yolov5.py
def __init__(self, *args: object, **kwargs: object) -> None:
    """Initialize YOLOv5 detection model."""
    existing_packages = getattr(self, "required_packages", None) or []
    self.required_packages = [*list(existing_packages), "yolov5", "torch"]
    super().__init__(*args, **kwargs)  # type: ignore[misc, arg-type]
load_model()

Detection model is initialized and set to self.model.

Source code in sahi/models/yolov5.py
def load_model(self) -> None:
    """Detection model is initialized and set to self.model."""
    import yolov5

    try:
        model = yolov5.load(self.model_path, device=self.device)
        self.set_model(model)
    except Exception as e:
        raise TypeError("model_path is not a valid yolov5 model path: ", e)
perform_inference(image)

Prediction is performed using self.model and the prediction result is set to self._original_predictions.

Parameters:

Name Type Description Default
image ndarray

np.ndarray A numpy array that contains the image to be predicted. 3 channel image should be in RGB order.

required
Source code in sahi/models/yolov5.py
def perform_inference(self, image: np.ndarray) -> None:
    """Prediction is performed using self.model and the prediction result is set to self._original_predictions.

    Args:
        image: np.ndarray
            A numpy array that contains the image to be predicted. 3 channel image should be in RGB order.
    """
    # Confirm model is loaded
    if self.model is None:
        raise ValueError("Model is not loaded, load it by calling .load_model()")
    if self.image_size is not None:
        prediction_result = self.model(image, size=self.image_size)
    else:
        prediction_result = self.model(image)

    self._original_predictions = prediction_result
set_model(model, **kwargs)

Sets the underlying YOLOv5 model.

Parameters:

Name Type Description Default
model Any

Any A YOLOv5 model

required
**kwargs Any

Any Additional keyword arguments for model setup.

{}
Source code in sahi/models/yolov5.py
def set_model(self, model: Any, **kwargs: Any) -> None:
    """Sets the underlying YOLOv5 model.

    Args:
        model: Any
            A YOLOv5 model
        **kwargs: Any
            Additional keyword arguments for model setup.
    """
    if model.__class__.__module__ not in ["yolov5.models.common", "models.common"]:
        raise Exception(f"Not a yolov5 model: {type(model)}")

    model.conf = self.confidence_threshold  # type: ignore[attr-defined]
    self.model = model

    # set category_mapping
    if not self.category_mapping:
        category_mapping = {str(ind): category_name for ind, category_name in enumerate(self.category_names)}
        self.category_mapping = category_mapping

Functions