Saltar al contenido

Referencia para ultralytics/solutions/object_counter.py

Nota

Este archivo está disponible en https://github.com/ultralytics/ ultralytics/blob/main/ ultralytics/solutions/object_counter .py. Si detectas algún problema, por favor, ayuda a solucionarlo contribuyendo con una Pull Request 🛠️. ¡Gracias 🙏!



ultralytics.solutions.object_counter.ObjectCounter

Una clase para gestionar el recuento de objetos en un flujo de vídeo en tiempo real basándose en sus huellas.

Código fuente en ultralytics/solutions/object_counter.py
class ObjectCounter:
    """A class to manage the counting of objects in a real-time video stream based on their tracks."""

    def __init__(self):
        """Initializes the Counter with default values for various tracking and counting parameters."""

        # Mouse events
        self.is_drawing = False
        self.selected_point = None

        # Region & Line Information
        self.reg_pts = [(20, 400), (1260, 400)]
        self.line_dist_thresh = 15
        self.counting_region = None
        self.region_color = (255, 0, 255)
        self.region_thickness = 5

        # Image and annotation Information
        self.im0 = None
        self.tf = None
        self.view_img = False
        self.view_in_counts = True
        self.view_out_counts = True

        self.names = None  # Classes names
        self.annotator = None  # Annotator
        self.window_name = "Ultralytics YOLOv8 Object Counter"

        # Object counting Information
        self.in_counts = 0
        self.out_counts = 0
        self.count_ids = []
        self.class_wise_count = {}
        self.count_txt_thickness = 0
        self.count_txt_color = (255, 255, 255)
        self.count_bg_color = (255, 255, 255)
        self.cls_txtdisplay_gap = 50
        self.fontsize = 0.6

        # Tracks info
        self.track_history = defaultdict(list)
        self.track_thickness = 2
        self.draw_tracks = False
        self.track_color = None

        # Check if environment support imshow
        self.env_check = check_imshow(warn=True)

    def set_args(
        self,
        classes_names,
        reg_pts,
        count_reg_color=(255, 0, 255),
        count_txt_color=(0, 0, 0),
        count_bg_color=(255, 255, 255),
        line_thickness=2,
        track_thickness=2,
        view_img=False,
        view_in_counts=True,
        view_out_counts=True,
        draw_tracks=False,
        track_color=None,
        region_thickness=5,
        line_dist_thresh=15,
        cls_txtdisplay_gap=50,
    ):
        """
        Configures the Counter's image, bounding box line thickness, and counting region points.

        Args:
            line_thickness (int): Line thickness for bounding boxes.
            view_img (bool): Flag to control whether to display the video stream.
            view_in_counts (bool): Flag to control whether to display the incounts on video stream.
            view_out_counts (bool): Flag to control whether to display the outcounts on video stream.
            reg_pts (list): Initial list of points defining the counting region.
            classes_names (dict): Classes names
            track_thickness (int): Track thickness
            draw_tracks (Bool): draw tracks
            count_txt_color (RGB color): count text color value
            count_bg_color (RGB color): count highlighter line color
            count_reg_color (RGB color): Color of object counting region
            track_color (RGB color): color for tracks
            region_thickness (int): Object counting Region thickness
            line_dist_thresh (int): Euclidean Distance threshold for line counter
            cls_txtdisplay_gap (int): Display gap between each class count
        """
        self.tf = line_thickness
        self.view_img = view_img
        self.view_in_counts = view_in_counts
        self.view_out_counts = view_out_counts
        self.track_thickness = track_thickness
        self.draw_tracks = draw_tracks

        # Region and line selection
        if len(reg_pts) == 2:
            print("Line Counter Initiated.")
            self.reg_pts = reg_pts
            self.counting_region = LineString(self.reg_pts)
        elif len(reg_pts) >= 3:
            print("Polygon Counter Initiated.")
            self.reg_pts = reg_pts
            self.counting_region = Polygon(self.reg_pts)
        else:
            print("Invalid Region points provided, region_points must be 2 for lines or >= 3 for polygons.")
            print("Using Line Counter Now")
            self.counting_region = LineString(self.reg_pts)

        self.names = classes_names
        self.track_color = track_color
        self.count_txt_color = count_txt_color
        self.count_bg_color = count_bg_color
        self.region_color = count_reg_color
        self.region_thickness = region_thickness
        self.line_dist_thresh = line_dist_thresh
        self.cls_txtdisplay_gap = cls_txtdisplay_gap

    def mouse_event_for_region(self, event, x, y, flags, params):
        """
        This function is designed to move region with mouse events in a real-time video stream.

        Args:
            event (int): The type of mouse event (e.g., cv2.EVENT_MOUSEMOVE, cv2.EVENT_LBUTTONDOWN, etc.).
            x (int): The x-coordinate of the mouse pointer.
            y (int): The y-coordinate of the mouse pointer.
            flags (int): Any flags associated with the event (e.g., cv2.EVENT_FLAG_CTRLKEY,
                cv2.EVENT_FLAG_SHIFTKEY, etc.).
            params (dict): Additional parameters you may want to pass to the function.
        """
        if event == cv2.EVENT_LBUTTONDOWN:
            for i, point in enumerate(self.reg_pts):
                if (
                    isinstance(point, (tuple, list))
                    and len(point) >= 2
                    and (abs(x - point[0]) < 10 and abs(y - point[1]) < 10)
                ):
                    self.selected_point = i
                    self.is_drawing = True
                    break

        elif event == cv2.EVENT_MOUSEMOVE:
            if self.is_drawing and self.selected_point is not None:
                self.reg_pts[self.selected_point] = (x, y)
                self.counting_region = Polygon(self.reg_pts)

        elif event == cv2.EVENT_LBUTTONUP:
            self.is_drawing = False
            self.selected_point = None

    def extract_and_process_tracks(self, tracks):
        """Extracts and processes tracks for object counting in a video stream."""

        # Annotator Init and region drawing
        self.annotator = Annotator(self.im0, self.tf, self.names)

        # Draw region or line
        self.annotator.draw_region(reg_pts=self.reg_pts, color=self.region_color, thickness=self.region_thickness)

        if tracks[0].boxes.id is not None:
            boxes = tracks[0].boxes.xyxy.cpu()
            clss = tracks[0].boxes.cls.cpu().tolist()
            track_ids = tracks[0].boxes.id.int().cpu().tolist()

            # Extract tracks
            for box, track_id, cls in zip(boxes, track_ids, clss):
                # Draw bounding box
                self.annotator.box_label(box, label=f"{self.names[cls]}#{track_id}", color=colors(int(track_id), True))

                # Store class info
                if self.names[cls] not in self.class_wise_count:
                    self.class_wise_count[self.names[cls]] = {"IN": 0, "OUT": 0}

                # Draw Tracks
                track_line = self.track_history[track_id]
                track_line.append((float((box[0] + box[2]) / 2), float((box[1] + box[3]) / 2)))
                if len(track_line) > 30:
                    track_line.pop(0)

                # Draw track trails
                if self.draw_tracks:
                    self.annotator.draw_centroid_and_tracks(
                        track_line,
                        color=self.track_color if self.track_color else colors(int(track_id), True),
                        track_thickness=self.track_thickness,
                    )

                prev_position = self.track_history[track_id][-2] if len(self.track_history[track_id]) > 1 else None

                # Count objects in any polygon
                if len(self.reg_pts) >= 3:
                    is_inside = self.counting_region.contains(Point(track_line[-1]))

                    if prev_position is not None and is_inside and track_id not in self.count_ids:
                        self.count_ids.append(track_id)

                        if (box[0] - prev_position[0]) * (self.counting_region.centroid.x - prev_position[0]) > 0:
                            self.in_counts += 1
                            self.class_wise_count[self.names[cls]]["IN"] += 1
                        else:
                            self.out_counts += 1
                            self.class_wise_count[self.names[cls]]["OUT"] += 1

                # Count objects using line
                elif len(self.reg_pts) == 2:
                    if prev_position is not None and track_id not in self.count_ids:
                        distance = Point(track_line[-1]).distance(self.counting_region)
                        if distance < self.line_dist_thresh and track_id not in self.count_ids:
                            self.count_ids.append(track_id)

                            if (box[0] - prev_position[0]) * (self.counting_region.centroid.x - prev_position[0]) > 0:
                                self.in_counts += 1
                                self.class_wise_count[self.names[cls]]["IN"] += 1
                            else:
                                self.out_counts += 1
                                self.class_wise_count[self.names[cls]]["OUT"] += 1

        labels_dict = {}

        for key, value in self.class_wise_count.items():
            if value["IN"] != 0 or value["OUT"] != 0:
                if not self.view_in_counts and not self.view_out_counts:
                    continue
                elif not self.view_in_counts:
                    labels_dict[str.capitalize(key)] = f"OUT {value['OUT']}"
                elif not self.view_out_counts:
                    labels_dict[str.capitalize(key)] = f"IN {value['IN']}"
                else:
                    labels_dict[str.capitalize(key)] = f"IN {value['IN']} OUT {value['OUT']}"

        if labels_dict is not None:
            self.annotator.display_analytics(self.im0, labels_dict, self.count_txt_color, self.count_bg_color, 10)

    def display_frames(self):
        """Display frame."""
        if self.env_check:
            cv2.namedWindow(self.window_name)
            if len(self.reg_pts) == 4:  # only add mouse event If user drawn region
                cv2.setMouseCallback(self.window_name, self.mouse_event_for_region, {"region_points": self.reg_pts})
            cv2.imshow(self.window_name, self.im0)
            # Break Window
            if cv2.waitKey(1) & 0xFF == ord("q"):
                return

    def start_counting(self, im0, tracks):
        """
        Main function to start the object counting process.

        Args:
            im0 (ndarray): Current frame from the video stream.
            tracks (list): List of tracks obtained from the object tracking process.
        """
        self.im0 = im0  # store image
        self.extract_and_process_tracks(tracks)  # draw region even if no objects

        if self.view_img:
            self.display_frames()
        return self.im0

__init__()

Inicializa el Contador con valores por defecto para varios parámetros de seguimiento y recuento.

Código fuente en ultralytics/solutions/object_counter.py
def __init__(self):
    """Initializes the Counter with default values for various tracking and counting parameters."""

    # Mouse events
    self.is_drawing = False
    self.selected_point = None

    # Region & Line Information
    self.reg_pts = [(20, 400), (1260, 400)]
    self.line_dist_thresh = 15
    self.counting_region = None
    self.region_color = (255, 0, 255)
    self.region_thickness = 5

    # Image and annotation Information
    self.im0 = None
    self.tf = None
    self.view_img = False
    self.view_in_counts = True
    self.view_out_counts = True

    self.names = None  # Classes names
    self.annotator = None  # Annotator
    self.window_name = "Ultralytics YOLOv8 Object Counter"

    # Object counting Information
    self.in_counts = 0
    self.out_counts = 0
    self.count_ids = []
    self.class_wise_count = {}
    self.count_txt_thickness = 0
    self.count_txt_color = (255, 255, 255)
    self.count_bg_color = (255, 255, 255)
    self.cls_txtdisplay_gap = 50
    self.fontsize = 0.6

    # Tracks info
    self.track_history = defaultdict(list)
    self.track_thickness = 2
    self.draw_tracks = False
    self.track_color = None

    # Check if environment support imshow
    self.env_check = check_imshow(warn=True)

display_frames()

Marco de la pantalla.

Código fuente en ultralytics/solutions/object_counter.py
def display_frames(self):
    """Display frame."""
    if self.env_check:
        cv2.namedWindow(self.window_name)
        if len(self.reg_pts) == 4:  # only add mouse event If user drawn region
            cv2.setMouseCallback(self.window_name, self.mouse_event_for_region, {"region_points": self.reg_pts})
        cv2.imshow(self.window_name, self.im0)
        # Break Window
        if cv2.waitKey(1) & 0xFF == ord("q"):
            return

extract_and_process_tracks(tracks)

Extrae y procesa pistas para el recuento de objetos en un flujo de vídeo.

Código fuente en ultralytics/solutions/object_counter.py
def extract_and_process_tracks(self, tracks):
    """Extracts and processes tracks for object counting in a video stream."""

    # Annotator Init and region drawing
    self.annotator = Annotator(self.im0, self.tf, self.names)

    # Draw region or line
    self.annotator.draw_region(reg_pts=self.reg_pts, color=self.region_color, thickness=self.region_thickness)

    if tracks[0].boxes.id is not None:
        boxes = tracks[0].boxes.xyxy.cpu()
        clss = tracks[0].boxes.cls.cpu().tolist()
        track_ids = tracks[0].boxes.id.int().cpu().tolist()

        # Extract tracks
        for box, track_id, cls in zip(boxes, track_ids, clss):
            # Draw bounding box
            self.annotator.box_label(box, label=f"{self.names[cls]}#{track_id}", color=colors(int(track_id), True))

            # Store class info
            if self.names[cls] not in self.class_wise_count:
                self.class_wise_count[self.names[cls]] = {"IN": 0, "OUT": 0}

            # Draw Tracks
            track_line = self.track_history[track_id]
            track_line.append((float((box[0] + box[2]) / 2), float((box[1] + box[3]) / 2)))
            if len(track_line) > 30:
                track_line.pop(0)

            # Draw track trails
            if self.draw_tracks:
                self.annotator.draw_centroid_and_tracks(
                    track_line,
                    color=self.track_color if self.track_color else colors(int(track_id), True),
                    track_thickness=self.track_thickness,
                )

            prev_position = self.track_history[track_id][-2] if len(self.track_history[track_id]) > 1 else None

            # Count objects in any polygon
            if len(self.reg_pts) >= 3:
                is_inside = self.counting_region.contains(Point(track_line[-1]))

                if prev_position is not None and is_inside and track_id not in self.count_ids:
                    self.count_ids.append(track_id)

                    if (box[0] - prev_position[0]) * (self.counting_region.centroid.x - prev_position[0]) > 0:
                        self.in_counts += 1
                        self.class_wise_count[self.names[cls]]["IN"] += 1
                    else:
                        self.out_counts += 1
                        self.class_wise_count[self.names[cls]]["OUT"] += 1

            # Count objects using line
            elif len(self.reg_pts) == 2:
                if prev_position is not None and track_id not in self.count_ids:
                    distance = Point(track_line[-1]).distance(self.counting_region)
                    if distance < self.line_dist_thresh and track_id not in self.count_ids:
                        self.count_ids.append(track_id)

                        if (box[0] - prev_position[0]) * (self.counting_region.centroid.x - prev_position[0]) > 0:
                            self.in_counts += 1
                            self.class_wise_count[self.names[cls]]["IN"] += 1
                        else:
                            self.out_counts += 1
                            self.class_wise_count[self.names[cls]]["OUT"] += 1

    labels_dict = {}

    for key, value in self.class_wise_count.items():
        if value["IN"] != 0 or value["OUT"] != 0:
            if not self.view_in_counts and not self.view_out_counts:
                continue
            elif not self.view_in_counts:
                labels_dict[str.capitalize(key)] = f"OUT {value['OUT']}"
            elif not self.view_out_counts:
                labels_dict[str.capitalize(key)] = f"IN {value['IN']}"
            else:
                labels_dict[str.capitalize(key)] = f"IN {value['IN']} OUT {value['OUT']}"

    if labels_dict is not None:
        self.annotator.display_analytics(self.im0, labels_dict, self.count_txt_color, self.count_bg_color, 10)

mouse_event_for_region(event, x, y, flags, params)

Esta función está diseñada para mover la región con eventos del ratón en un flujo de vídeo en tiempo real.

Parámetros:

Nombre Tipo Descripción Por defecto
event int

El tipo de evento del ratón (por ejemplo, cv2.EVENT_MOUSEMOVE, cv2.EVENT_LBUTTONDOWN, etc.).

necesario
x int

La coordenada x del puntero del ratón.

necesario
y int

La coordenada y del puntero del ratón.

necesario
flags int

Cualquier indicador asociado al evento (por ejemplo, cv2.EVENT_FLAG_CTRLKEY, cv2.EVENT_FLAG_SHIFTKEY, etc.).

necesario
params dict

Parámetros adicionales que quieras pasar a la función.

necesario
Código fuente en ultralytics/solutions/object_counter.py
def mouse_event_for_region(self, event, x, y, flags, params):
    """
    This function is designed to move region with mouse events in a real-time video stream.

    Args:
        event (int): The type of mouse event (e.g., cv2.EVENT_MOUSEMOVE, cv2.EVENT_LBUTTONDOWN, etc.).
        x (int): The x-coordinate of the mouse pointer.
        y (int): The y-coordinate of the mouse pointer.
        flags (int): Any flags associated with the event (e.g., cv2.EVENT_FLAG_CTRLKEY,
            cv2.EVENT_FLAG_SHIFTKEY, etc.).
        params (dict): Additional parameters you may want to pass to the function.
    """
    if event == cv2.EVENT_LBUTTONDOWN:
        for i, point in enumerate(self.reg_pts):
            if (
                isinstance(point, (tuple, list))
                and len(point) >= 2
                and (abs(x - point[0]) < 10 and abs(y - point[1]) < 10)
            ):
                self.selected_point = i
                self.is_drawing = True
                break

    elif event == cv2.EVENT_MOUSEMOVE:
        if self.is_drawing and self.selected_point is not None:
            self.reg_pts[self.selected_point] = (x, y)
            self.counting_region = Polygon(self.reg_pts)

    elif event == cv2.EVENT_LBUTTONUP:
        self.is_drawing = False
        self.selected_point = None

set_args(classes_names, reg_pts, count_reg_color=(255, 0, 255), count_txt_color=(0, 0, 0), count_bg_color=(255, 255, 255), line_thickness=2, track_thickness=2, view_img=False, view_in_counts=True, view_out_counts=True, draw_tracks=False, track_color=None, region_thickness=5, line_dist_thresh=15, cls_txtdisplay_gap=50)

Configura la imagen del Contador, el grosor de la línea del cuadro delimitador y los puntos de la región de recuento.

Parámetros:

Nombre Tipo Descripción Por defecto
line_thickness int

Grosor de línea de los cuadros delimitadores.

2
view_img bool

Bandera para controlar si se muestra el flujo de vídeo.

False
view_in_counts bool

Bandera para controlar si se muestran los recuentos en el flujo de vídeo.

True
view_out_counts bool

Bandera para controlar si se muestran los recuentos en el flujo de vídeo.

True
reg_pts list

Lista inicial de puntos que definen la región de recuento.

necesario
classes_names dict

Nombres de las clases

necesario
track_thickness int

Grosor de la vía

2
draw_tracks Bool

dibujar pistas

False
count_txt_color RGB color

contar valor de color del texto

(0, 0, 0)
count_bg_color RGB color

cuenta el color de la línea de subrayado

(255, 255, 255)
count_reg_color RGB color

Color de la región de recuento de objetos

(255, 0, 255)
track_color RGB color

color para las pistas

None
region_thickness int

Recuento de objetos Grosor de la región

5
line_dist_thresh int

Umbral de distancia euclidiana para el contador de líneas

15
cls_txtdisplay_gap int

Mostrar el espacio entre cada recuento de clases

50
Código fuente en ultralytics/solutions/object_counter.py
def set_args(
    self,
    classes_names,
    reg_pts,
    count_reg_color=(255, 0, 255),
    count_txt_color=(0, 0, 0),
    count_bg_color=(255, 255, 255),
    line_thickness=2,
    track_thickness=2,
    view_img=False,
    view_in_counts=True,
    view_out_counts=True,
    draw_tracks=False,
    track_color=None,
    region_thickness=5,
    line_dist_thresh=15,
    cls_txtdisplay_gap=50,
):
    """
    Configures the Counter's image, bounding box line thickness, and counting region points.

    Args:
        line_thickness (int): Line thickness for bounding boxes.
        view_img (bool): Flag to control whether to display the video stream.
        view_in_counts (bool): Flag to control whether to display the incounts on video stream.
        view_out_counts (bool): Flag to control whether to display the outcounts on video stream.
        reg_pts (list): Initial list of points defining the counting region.
        classes_names (dict): Classes names
        track_thickness (int): Track thickness
        draw_tracks (Bool): draw tracks
        count_txt_color (RGB color): count text color value
        count_bg_color (RGB color): count highlighter line color
        count_reg_color (RGB color): Color of object counting region
        track_color (RGB color): color for tracks
        region_thickness (int): Object counting Region thickness
        line_dist_thresh (int): Euclidean Distance threshold for line counter
        cls_txtdisplay_gap (int): Display gap between each class count
    """
    self.tf = line_thickness
    self.view_img = view_img
    self.view_in_counts = view_in_counts
    self.view_out_counts = view_out_counts
    self.track_thickness = track_thickness
    self.draw_tracks = draw_tracks

    # Region and line selection
    if len(reg_pts) == 2:
        print("Line Counter Initiated.")
        self.reg_pts = reg_pts
        self.counting_region = LineString(self.reg_pts)
    elif len(reg_pts) >= 3:
        print("Polygon Counter Initiated.")
        self.reg_pts = reg_pts
        self.counting_region = Polygon(self.reg_pts)
    else:
        print("Invalid Region points provided, region_points must be 2 for lines or >= 3 for polygons.")
        print("Using Line Counter Now")
        self.counting_region = LineString(self.reg_pts)

    self.names = classes_names
    self.track_color = track_color
    self.count_txt_color = count_txt_color
    self.count_bg_color = count_bg_color
    self.region_color = count_reg_color
    self.region_thickness = region_thickness
    self.line_dist_thresh = line_dist_thresh
    self.cls_txtdisplay_gap = cls_txtdisplay_gap

start_counting(im0, tracks)

Función principal para iniciar el proceso de recuento de objetos.

Parámetros:

Nombre Tipo Descripción Por defecto
im0 ndarray

Fotograma actual del flujo de vídeo.

necesario
tracks list

Lista de pistas obtenidas en el proceso de seguimiento de objetos.

necesario
Código fuente en ultralytics/solutions/object_counter.py
def start_counting(self, im0, tracks):
    """
    Main function to start the object counting process.

    Args:
        im0 (ndarray): Current frame from the video stream.
        tracks (list): List of tracks obtained from the object tracking process.
    """
    self.im0 = im0  # store image
    self.extract_and_process_tracks(tracks)  # draw region even if no objects

    if self.view_img:
        self.display_frames()
    return self.im0





Creado 2023-12-02, Actualizado 2024-05-08
Autores: Burhan-Q (1), RizwanMunawar (1)