feat: initial HSAP platform
Huaxu Sentinel Active Safety Platform with embedded algorithm code, Docker Compose setup, and vendored dataset scaffolds for clone-and-run. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
# Regions Counting Using Ultralytics YOLO11 (Inference on Video)
|
||||
|
||||
> **Note:** Region Counter is now part of **[Ultralytics Solutions](https://docs.ultralytics.com/solutions/)**, offering enhanced features and ongoing updates.
|
||||
>
|
||||
> 🔗 **Explore the official [Region Counting Guide](https://docs.ultralytics.com/guides/region-counting/) for the latest implementation.**
|
||||
|
||||
> 🔔 **Notice:**
|
||||
>
|
||||
> This GitHub example (`ultralytics/examples/YOLOv8-Region-Counter/`) will remain available but **is no longer actively maintained**. For the most current features, updates, and support, please refer to the official [Region Counting guide](https://docs.ultralytics.com/guides/region-counting/) within the Ultralytics documentation. Thank you!
|
||||
|
||||
Region counting is a technique used to count objects within predefined areas or zones in a video feed. This allows for more detailed analysis, especially when monitoring multiple distinct areas simultaneously. Users can interactively adjust these regions by clicking and dragging with the left mouse button, enabling real-time counting tailored to specific needs and layouts. This method is valuable in various [computer vision](https://www.ultralytics.com/glossary/computer-vision-cv) applications, from traffic analysis to retail analytics.
|
||||
|
||||
<div>
|
||||
<p align="center">
|
||||
<img src="https://github.com/RizwanMunawar/ultralytics/assets/62513924/5ab3bbd7-fd12-4849-928e-5f294d6c3fcf" width="45%" alt="YOLOv8 region counting visual 1">
|
||||
<img src="https://github.com/RizwanMunawar/ultralytics/assets/62513924/e7c1aea7-474d-4d78-8d48-b50854ffe1ca" width="45%" alt="YOLOv8 region counting visual 2">
|
||||
</p>
|
||||
</div>
|
||||
|
||||
## 📚 Table of Contents
|
||||
|
||||
- [Step 1: Install the Required Libraries](#-step-1-install-the-required-libraries)
|
||||
- [Step 2: Run Region Counting with Ultralytics YOLO11](#-step-2-run-region-counting-with-ultralytics-yolo11)
|
||||
- [Usage Options](#-usage-options)
|
||||
- [Frequently Asked Questions (FAQ)](#-frequently-asked-questions-faq)
|
||||
- [Contributing](#-contributing)
|
||||
|
||||
## ⚙️ Step 1: Install the Required Libraries
|
||||
|
||||
First, clone the Ultralytics repository and navigate to the example directory. Ensure you have Python installed along with necessary dependencies like [PyTorch](https://pytorch.org/) and [OpenCV](https://opencv.org/).
|
||||
|
||||
```bash
|
||||
# Clone the ultralytics repository
|
||||
git clone https://github.com/ultralytics/ultralytics
|
||||
|
||||
# Navigate to the example directory
|
||||
cd ultralytics/examples/YOLOv8-Region-Counter
|
||||
|
||||
# Install required packages (if not already installed)
|
||||
pip install ultralytics shapely
|
||||
```
|
||||
|
||||
## ▶️ Step 2: Run Region Counting with Ultralytics YOLO11
|
||||
|
||||
Execute the script using the following commands. You can customize the source, model, device, and other parameters.
|
||||
|
||||
### Note
|
||||
|
||||
Once the video starts playing, you can dynamically reposition the counting regions within the video frame by clicking and dragging them with your left mouse button.
|
||||
|
||||
```bash
|
||||
# Run inference on a video source, saving results and viewing output
|
||||
python yolov8_region_counter.py --source "path/to/video.mp4" --save-img --view-img
|
||||
|
||||
# Run inference using the CPU
|
||||
python yolov8_region_counter.py --source "path/to/video.mp4" --save-img --view-img --device cpu
|
||||
|
||||
# Use a specific Ultralytics YOLO11 model file
|
||||
python yolov8_region_counter.py --source "path/to/video.mp4" --save-img --weights "path/to/yolo11n.pt"
|
||||
|
||||
# Detect only specific classes (e.g., class 0 and class 2)
|
||||
python yolov8_region_counter.py --source "path/to/video.mp4" --classes 0 2 --weights "path/to/yolo11m.pt"
|
||||
|
||||
# Run inference without saving the output video/images
|
||||
python yolov8_region_counter.py --source "path/to/video.mp4" --view-img
|
||||
```
|
||||
|
||||
Learn more about inference arguments in the Ultralytics [Predict Mode documentation](https://docs.ultralytics.com/modes/predict/).
|
||||
|
||||
## 🛠️ Usage Options
|
||||
|
||||
The script accepts several command-line arguments for customization:
|
||||
|
||||
- `--source`: Path to the input video file.
|
||||
- `--device`: Computation device (`cpu` or GPU ID like `0`).
|
||||
- `--save-img`: Boolean flag to save output frames with detections.
|
||||
- `--view-img`: Boolean flag to display the output video stream in real-time.
|
||||
- `--weights`: Path to the [Ultralytics YOLO11](https://docs.ultralytics.com/models/yolo11/) model file (`.pt`). Defaults typically use a standard model like `yolo11n.pt`.
|
||||
- `--classes`: Filter detections by specific class IDs (e.g., `--classes 0 2 3` to detect classes 0, 2, and 3).
|
||||
- `--line-thickness`: Thickness of the [bounding box](https://www.ultralytics.com/glossary/bounding-box) lines.
|
||||
- `--region-thickness`: Thickness of the lines defining the counting regions.
|
||||
- `--track-thickness`: Thickness of the object tracking lines.
|
||||
|
||||
Explore different models and training options in the [Ultralytics documentation](https://docs.ultralytics.com/).
|
||||
|
||||
## ❓ Frequently Asked Questions (FAQ)
|
||||
|
||||
### What is Region Counting?
|
||||
|
||||
Region counting is a process in [computer vision](https://www.ultralytics.com/glossary/computer-vision-cv) used to determine the number of objects within predefined areas of an image or video frame. It's commonly applied in fields like [image processing](https://en.wikipedia.org/wiki/Image_processing) and [pattern recognition](https://en.wikipedia.org/wiki/Pattern_recognition) for analyzing spatial distributions and segmenting objects based on location.
|
||||
|
||||
### Does the Region Counter Support Custom Region Shapes?
|
||||
|
||||
Yes, the Region Counter allows defining regions using polygons (including rectangles). You can customize region properties like coordinates, names, and colors directly in the script. The `shapely` library is used for polygon definitions. See the [Shapely User Manual](https://shapely.readthedocs.io/en/stable/manual.html#polygons) for more details on polygon creation.
|
||||
|
||||
```python
|
||||
from shapely.geometry import Polygon
|
||||
|
||||
# Example definition of counting regions
|
||||
counting_regions = [
|
||||
{
|
||||
"name": "Region 1 (Pentagon)",
|
||||
"polygon": Polygon([(50, 80), (250, 20), (450, 80), (400, 350), (100, 350)]), # 5-point polygon
|
||||
"counts": 0,
|
||||
"dragging": False,
|
||||
"region_color": (255, 42, 4), # BGR color for region
|
||||
"text_color": (255, 255, 255), # BGR color for text
|
||||
},
|
||||
{
|
||||
"name": "Region 2 (Rectangle)",
|
||||
"polygon": Polygon([(200, 250), (440, 250), (440, 550), (200, 550)]), # 4-point polygon (rectangle)
|
||||
"counts": 0,
|
||||
"dragging": False,
|
||||
"region_color": (37, 255, 225), # BGR color for region
|
||||
"text_color": (0, 0, 0), # BGR color for text
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Why Combine Region Counting with YOLO11?
|
||||
|
||||
[Ultralytics YOLO11](https://docs.ultralytics.com/models/yolo11/) excels at [object detection](https://www.ultralytics.com/glossary/object-detection) and [tracking](https://www.ultralytics.com/glossary/object-tracking) in video streams. Integrating region counting enhances its capabilities by enabling object quantification within specific zones, making it useful for applications like crowd monitoring, traffic flow analysis, and retail footfall counting. Check out our blog post on [Object Detection and Tracking with Ultralytics YOLOv8](https://www.ultralytics.com/blog/object-detection-and-tracking-with-ultralytics-yolov8).
|
||||
|
||||
### How Can I Troubleshoot Issues?
|
||||
|
||||
For debugging, you can enable more verbose output. While this specific script doesn't have a dedicated `--debug` flag, you can add print statements within the code to inspect variables or use standard Python debugging tools. Ensure your video path and model weights path are correct. For common issues, refer to the [Ultralytics FAQ](https://docs.ultralytics.com/help/FAQ/).
|
||||
|
||||
### Can I Use Other YOLO Versions or Custom Models?
|
||||
|
||||
Yes, you can use different Ultralytics YOLO model versions (like YOLOv5, YOLOv9, YOLOv10, YOLO11) or your own custom-trained models by specifying the path to the `.pt` file using the `--weights` argument. Ensure the model is compatible with the Ultralytics framework. Find more about training custom models in the [Model Training guide](https://docs.ultralytics.com/modes/train/).
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions to improve this example or add new features are welcome! Please feel free to submit Pull Requests or open Issues on the main [Ultralytics repository](https://github.com/ultralytics/ultralytics). Remember to check the official [Region Counting guide](https://docs.ultralytics.com/guides/region-counting/) for the latest maintained version.
|
||||
@@ -0,0 +1,253 @@
|
||||
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from shapely.geometry import Polygon
|
||||
from shapely.geometry.point import Point
|
||||
|
||||
from ultralytics import YOLO
|
||||
from ultralytics.utils.files import increment_path
|
||||
from ultralytics.utils.plotting import Annotator, colors
|
||||
|
||||
track_history = defaultdict(list)
|
||||
|
||||
current_region = None
|
||||
counting_regions = [
|
||||
{
|
||||
"name": "Ultralytics YOLO Polygon Region",
|
||||
"polygon": Polygon([(50, 80), (250, 20), (450, 80), (400, 350), (100, 350)]), # Polygon points
|
||||
"counts": 0,
|
||||
"dragging": False,
|
||||
"region_color": (255, 42, 4), # BGR Value
|
||||
"text_color": (255, 255, 255), # Region Text Color
|
||||
},
|
||||
{
|
||||
"name": "Ultralytics YOLO Rectangle Region",
|
||||
"polygon": Polygon([(200, 250), (440, 250), (440, 550), (200, 550)]), # Polygon points
|
||||
"counts": 0,
|
||||
"dragging": False,
|
||||
"region_color": (37, 255, 225), # BGR Value
|
||||
"text_color": (0, 0, 0), # Region Text Color
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def mouse_callback(event: int, x: int, y: int, flags: int, param: Any) -> None:
|
||||
"""Handle mouse events for region manipulation in the video frame.
|
||||
|
||||
This function enables interactive region selection and dragging functionality for counting regions. It responds to
|
||||
mouse button down, move, and up events to allow users to select and reposition counting regions in real-time.
|
||||
|
||||
Args:
|
||||
event (int): The mouse event type (e.g., cv2.EVENT_LBUTTONDOWN, cv2.EVENT_MOUSEMOVE).
|
||||
x (int): The x-coordinate of the mouse pointer.
|
||||
y (int): The y-coordinate of the mouse pointer.
|
||||
flags (int): Additional flags passed by OpenCV.
|
||||
param (Any): Additional parameters passed to the callback.
|
||||
|
||||
Examples:
|
||||
Set up mouse callback for interactive region manipulation
|
||||
>>> cv2.setMouseCallback("window_name", mouse_callback)
|
||||
"""
|
||||
global current_region
|
||||
|
||||
# Mouse left button down event
|
||||
if event == cv2.EVENT_LBUTTONDOWN:
|
||||
for region in counting_regions:
|
||||
if region["polygon"].contains(Point((x, y))):
|
||||
current_region = region
|
||||
current_region["dragging"] = True
|
||||
current_region["offset_x"] = x
|
||||
current_region["offset_y"] = y
|
||||
|
||||
# Mouse move event
|
||||
elif event == cv2.EVENT_MOUSEMOVE:
|
||||
if current_region is not None and current_region["dragging"]:
|
||||
dx = x - current_region["offset_x"]
|
||||
dy = y - current_region["offset_y"]
|
||||
current_region["polygon"] = Polygon(
|
||||
[(p[0] + dx, p[1] + dy) for p in current_region["polygon"].exterior.coords]
|
||||
)
|
||||
current_region["offset_x"] = x
|
||||
current_region["offset_y"] = y
|
||||
|
||||
# Mouse left button up event
|
||||
elif event == cv2.EVENT_LBUTTONUP:
|
||||
if current_region is not None and current_region["dragging"]:
|
||||
current_region["dragging"] = False
|
||||
|
||||
|
||||
def run(
|
||||
weights: str = "yolo11n.pt",
|
||||
source: str | None = None,
|
||||
device: str = "cpu",
|
||||
view_img: bool = False,
|
||||
save_img: bool = False,
|
||||
exist_ok: bool = False,
|
||||
classes: list[int] | None = None,
|
||||
line_thickness: int = 2,
|
||||
track_thickness: int = 2,
|
||||
region_thickness: int = 2,
|
||||
) -> None:
|
||||
"""Run object detection and counting within specified regions using YOLO and ByteTrack.
|
||||
|
||||
This function performs real-time object detection, tracking, and counting within user-defined polygonal or
|
||||
rectangular regions. It supports interactive region manipulation, multiple counting areas, and both live viewing and
|
||||
video saving capabilities.
|
||||
|
||||
Args:
|
||||
weights (str): Path to the YOLO model weights file.
|
||||
source (str): Path to the input video file.
|
||||
device (str): Processing device specification ('cpu', '0', '1', etc.).
|
||||
view_img (bool): Display results in a live window.
|
||||
save_img (bool): Save processed video to file.
|
||||
exist_ok (bool): Overwrite existing output files without incrementing.
|
||||
classes (list[int], optional): Specific class IDs to detect and track.
|
||||
line_thickness (int): Thickness of bounding box lines.
|
||||
track_thickness (int): Thickness of object tracking lines.
|
||||
region_thickness (int): Thickness of counting region boundaries.
|
||||
|
||||
Examples:
|
||||
Run region counting with default settings
|
||||
>>> run(source="video.mp4", view_img=True)
|
||||
|
||||
Run with custom model and specific classes
|
||||
>>> run(weights="yolo11s.pt", source="traffic.mp4", classes=[0, 2, 3], device="0")
|
||||
"""
|
||||
vid_frame_count = 0
|
||||
|
||||
# Check source path
|
||||
if not Path(source).exists():
|
||||
raise FileNotFoundError(f"Source path '{source}' does not exist.")
|
||||
|
||||
# Setup Model
|
||||
model = YOLO(f"{weights}")
|
||||
model.to("cuda") if device == "0" else model.to("cpu")
|
||||
|
||||
# Extract classes names
|
||||
names = model.names
|
||||
|
||||
# Video setup
|
||||
videocapture = cv2.VideoCapture(source)
|
||||
frame_width = int(videocapture.get(3))
|
||||
frame_height = int(videocapture.get(4))
|
||||
fps = int(videocapture.get(5))
|
||||
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
|
||||
|
||||
# Output setup
|
||||
save_dir = increment_path(Path("ultralytics_rc_output") / "exp", exist_ok)
|
||||
save_dir.mkdir(parents=True, exist_ok=True)
|
||||
video_writer = cv2.VideoWriter(str(save_dir / f"{Path(source).stem}.avi"), fourcc, fps, (frame_width, frame_height))
|
||||
|
||||
# Iterate over video frames
|
||||
while videocapture.isOpened():
|
||||
success, frame = videocapture.read()
|
||||
if not success:
|
||||
break
|
||||
vid_frame_count += 1
|
||||
|
||||
# Extract the results
|
||||
results = model.track(frame, persist=True, classes=classes)
|
||||
|
||||
if results[0].boxes.is_track:
|
||||
boxes = results[0].boxes.xyxy.cpu()
|
||||
track_ids = results[0].boxes.id.int().cpu().tolist()
|
||||
clss = results[0].boxes.cls.cpu().tolist()
|
||||
|
||||
annotator = Annotator(frame, line_width=line_thickness, example=str(names))
|
||||
|
||||
for box, track_id, cls in zip(boxes, track_ids, clss):
|
||||
annotator.box_label(box, str(names[cls]), color=colors(cls, True))
|
||||
bbox_center = (box[0] + box[2]) / 2, (box[1] + box[3]) / 2 # Bbox center
|
||||
|
||||
track = track_history[track_id] # Tracking Lines plot
|
||||
track.append((float(bbox_center[0]), float(bbox_center[1])))
|
||||
if len(track) > 30:
|
||||
track.pop(0)
|
||||
points = np.hstack(track).astype(np.int32).reshape((-1, 1, 2))
|
||||
cv2.polylines(frame, [points], isClosed=False, color=colors(cls, True), thickness=track_thickness)
|
||||
|
||||
# Check if detection inside region
|
||||
for region in counting_regions:
|
||||
if region["polygon"].contains(Point((bbox_center[0], bbox_center[1]))):
|
||||
region["counts"] += 1
|
||||
|
||||
# Draw regions (Polygons/Rectangles)
|
||||
for region in counting_regions:
|
||||
region_label = str(region["counts"])
|
||||
region_color = region["region_color"]
|
||||
region_text_color = region["text_color"]
|
||||
|
||||
polygon_coordinates = np.array(region["polygon"].exterior.coords, dtype=np.int32)
|
||||
centroid_x, centroid_y = int(region["polygon"].centroid.x), int(region["polygon"].centroid.y)
|
||||
|
||||
text_size, _ = cv2.getTextSize(
|
||||
region_label, cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.7, thickness=line_thickness
|
||||
)
|
||||
text_x = centroid_x - text_size[0] // 2
|
||||
text_y = centroid_y + text_size[1] // 2
|
||||
cv2.rectangle(
|
||||
frame,
|
||||
(text_x - 5, text_y - text_size[1] - 5),
|
||||
(text_x + text_size[0] + 5, text_y + 5),
|
||||
region_color,
|
||||
-1,
|
||||
)
|
||||
cv2.putText(
|
||||
frame, region_label, (text_x, text_y), cv2.FONT_HERSHEY_SIMPLEX, 0.7, region_text_color, line_thickness
|
||||
)
|
||||
cv2.polylines(frame, [polygon_coordinates], isClosed=True, color=region_color, thickness=region_thickness)
|
||||
|
||||
if view_img:
|
||||
if vid_frame_count == 1:
|
||||
cv2.namedWindow("Ultralytics YOLO Region Counter Movable")
|
||||
cv2.setMouseCallback("Ultralytics YOLO Region Counter Movable", mouse_callback)
|
||||
cv2.imshow("Ultralytics YOLO Region Counter Movable", frame)
|
||||
|
||||
if save_img:
|
||||
video_writer.write(frame)
|
||||
|
||||
for region in counting_regions: # Reinitialize count for each region
|
||||
region["counts"] = 0
|
||||
|
||||
if cv2.waitKey(1) & 0xFF == ord("q"):
|
||||
break
|
||||
|
||||
del vid_frame_count
|
||||
video_writer.release()
|
||||
videocapture.release()
|
||||
cv2.destroyAllWindows()
|
||||
|
||||
|
||||
def parse_opt() -> argparse.Namespace:
|
||||
"""Parse command line arguments for the region counting application."""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--weights", type=str, default="yolo11n.pt", help="initial weights path")
|
||||
parser.add_argument("--device", default="", help="cuda device, i.e. 0 or 0,1,2,3 or cpu")
|
||||
parser.add_argument("--source", type=str, required=True, help="video file path")
|
||||
parser.add_argument("--view-img", action="store_true", help="show results")
|
||||
parser.add_argument("--save-img", action="store_true", help="save results")
|
||||
parser.add_argument("--exist-ok", action="store_true", help="existing project/name ok, do not increment")
|
||||
parser.add_argument("--classes", nargs="+", type=int, help="filter by class: --classes 0, or --classes 0 2 3")
|
||||
parser.add_argument("--line-thickness", type=int, default=2, help="bounding box thickness")
|
||||
parser.add_argument("--track-thickness", type=int, default=2, help="Tracking line thickness")
|
||||
parser.add_argument("--region-thickness", type=int, default=4, help="Region thickness")
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main(options: argparse.Namespace) -> None:
|
||||
"""Execute the main region counting functionality with the provided options."""
|
||||
run(**vars(options))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
opt = parse_opt()
|
||||
main(opt)
|
||||
Reference in New Issue
Block a user