diff --git a/docs/en/guides/analytics.md b/docs/en/guides/analytics.md index 69a0467103..c1fad80f5b 100644 --- a/docs/en/guides/analytics.md +++ b/docs/en/guides/analytics.md @@ -71,6 +71,66 @@ This guide provides a comprehensive overview of three fundamental types of data out.release() cv2.destroyAllWindows() ``` + + === "Multiple Lines" + + ```python + import cv2 + from ultralytics import YOLO, solutions + + model = YOLO("yolov8s.pt") + + cap = cv2.VideoCapture("Path/to/video/file.mp4") + assert cap.isOpened(), "Error reading video file" + w, h, fps = (int(cap.get(x)) for x in (cv2.CAP_PROP_FRAME_WIDTH, cv2.CAP_PROP_FRAME_HEIGHT, cv2.CAP_PROP_FPS)) + out = cv2.VideoWriter("multiple_line_plot.avi", cv2.VideoWriter_fourcc(*"MJPG"), fps, (w, h)) + + analytics = solutions.Analytics( + type="line", + writer=out, + im0_shape=(w, h), + view_img=True, + max_points=200, + ) + + frame_count = 0 + data = {} + labels = [] + + while cap.isOpened(): + success, frame = cap.read() + + if success: + frame_count += 1 + + results = model.track(frame, persist=True) + + if results[0].boxes.id is not None: + boxes = results[0].boxes.xyxy.cpu() + track_ids = results[0].boxes.id.int().cpu().tolist() + clss = results[0].boxes.cls.cpu().tolist() + + for box, track_id, cls in zip(boxes, track_ids, clss): + # Store each class label + if model.names[int(cls)] not in labels: + labels.append(model.names[int(cls)]) + + # Store each class count + if model.names[int(cls)] in data: + data[model.names[int(cls)]] += 1 + else: + data[model.names[int(cls)]] = 0 + + # update lines every frame + analytics.update_multiple_lines(data, labels, frame_count) + data = {} # clear the data list for next frame + else: + break + + cap.release() + out.release() + cv2.destroyAllWindows() + ``` === "Pie Chart" @@ -174,21 +234,22 @@ This guide provides a comprehensive overview of three fundamental types of data Here's a table with the `Analytics` arguments: -| Name | Type | Default | Description | -|--------------|-------------------|---------------|-------------------------------------| -| `type` | `str` | `None` | Type of data or object. | -| `im0_shape` | `tuple` | `None` | Shape of the initial image. | -| `writer` | `cv2.VideoWriter` | `None` | Object for writing video files. | -| `title` | `str` | `ultralytics` | Title for the visualization. | -| `x_label` | `str` | `x` | Label for the x-axis. | -| `y_label` | `str` | `y` | Label for the y-axis. | -| `bg_color` | `str` | `white` | Background color. | -| `fg_color` | `str` | `black` | Foreground color. | -| `line_color` | `str` | `yellow` | Color of the lines. | -| `line_width` | `int` | `2` | Width of the lines. | -| `fontsize` | `int` | `13` | Font size for text. | -| `view_img` | `bool` | `False` | Flag to display the image or video. | -| `save_img` | `bool` | `True` | Flag to save the image or video. | +| Name | Type | Default | Description | +|--------------|-------------------|---------------|----------------------------------------------------------------------------------| +| `type` | `str` | `None` | Type of data or object. | +| `im0_shape` | `tuple` | `None` | Shape of the initial image. | +| `writer` | `cv2.VideoWriter` | `None` | Object for writing video files. | +| `title` | `str` | `ultralytics` | Title for the visualization. | +| `x_label` | `str` | `x` | Label for the x-axis. | +| `y_label` | `str` | `y` | Label for the y-axis. | +| `bg_color` | `str` | `white` | Background color. | +| `fg_color` | `str` | `black` | Foreground color. | +| `line_color` | `str` | `yellow` | Color of the lines. | +| `line_width` | `int` | `2` | Width of the lines. | +| `fontsize` | `int` | `13` | Font size for text. | +| `view_img` | `bool` | `False` | Flag to display the image or video. | +| `save_img` | `bool` | `True` | Flag to save the image or video. | +| `max_points` | `int` | `50` | For multiple lines, total points drawn on frame, before deleting initial points. | ### Arguments `model.track` diff --git a/ultralytics/__init__.py b/ultralytics/__init__.py index 3b687dd7ee..182e532768 100644 --- a/ultralytics/__init__.py +++ b/ultralytics/__init__.py @@ -1,6 +1,6 @@ # Ultralytics YOLO 🚀, AGPL-3.0 license -__version__ = "8.2.25" +__version__ = "8.2.26" import os diff --git a/ultralytics/solutions/analytics.py b/ultralytics/solutions/analytics.py index bbae23f105..98d0bb256d 100644 --- a/ultralytics/solutions/analytics.py +++ b/ultralytics/solutions/analytics.py @@ -1,5 +1,6 @@ # Ultralytics YOLO 🚀, AGPL-3.0 license +import warnings from itertools import cycle import cv2 @@ -27,6 +28,7 @@ class Analytics: fontsize=13, view_img=False, save_img=True, + max_points=50, ): """ Initialize the Analytics class with various chart types. @@ -45,6 +47,7 @@ class Analytics: fontsize (int): Font size for chart text. view_img (bool): Whether to display the image. save_img (bool): Whether to save the image. + max_points (int): Specifies when to remove the oldest points in a graph for multiple lines. """ self.bg_color = bg_color @@ -53,12 +56,14 @@ class Analytics: self.save_img = save_img self.title = title self.writer = writer + self.max_points = max_points # Set figure size based on image shape figsize = (im0_shape[0] / 100, im0_shape[1] / 100) if type == "line": # Initialize line plot + self.lines = {} fig = Figure(facecolor=self.bg_color, figsize=figsize) self.canvas = FigureCanvas(fig) self.ax = fig.add_subplot(111, facecolor=self.bg_color) @@ -112,9 +117,53 @@ class Analytics: self.ax.autoscale_view() self.canvas.draw() im0 = np.array(self.canvas.renderer.buffer_rgba()) - im0 = cv2.cvtColor(im0[:, :, :3], cv2.COLOR_RGBA2BGR) + self.write_and_display_line(im0) - # Display and save the updated graph + def update_multiple_lines(self, counts_dict, labels_list, frame_number): + """ + Update the line graph with multiple classes. + + Args: + counts_dict (int): Dictionary include each class counts. + labels_list (int): list include each classes names. + frame_number (int): The current frame number. + """ + warnings.warn("Display is not supported for multiple lines, output will be stored normally!") + for obj in labels_list: + if obj not in self.lines: + (line,) = self.ax.plot([], [], label=obj, marker="o", markersize=15) + self.lines[obj] = line + + x_data = self.lines[obj].get_xdata() + y_data = self.lines[obj].get_ydata() + + # Remove the initial point if the number of points exceeds max_points + if len(x_data) >= self.max_points: + x_data = np.delete(x_data, 0) + y_data = np.delete(y_data, 0) + + x_data = np.append(x_data, float(frame_number)) # Ensure frame_number is converted to float + y_data = np.append(y_data, float(counts_dict.get(obj, 0))) # Ensure total_count is converted to float + self.lines[obj].set_data(x_data, y_data) + + self.ax.relim() + self.ax.autoscale_view() + self.ax.legend() + self.canvas.draw() + + im0 = np.array(self.canvas.renderer.buffer_rgba()) + self.view_img = False # for multiple line view_img not supported yet, coming soon! + self.write_and_display_line(im0) + + def write_and_display_line(self, im0): + """ + Write and display the line graph + Args: + im0 (ndarray): Image for processing + """ + + # convert image to BGR format + im0 = cv2.cvtColor(im0[:, :, :3], cv2.COLOR_RGBA2BGR) cv2.imshow(self.title, im0) if self.view_img else None self.writer.write(im0) if self.save_img else None