Spaces:
Running on Zero
Running on Zero
Upload folder using huggingface_hub
Browse files- .gitattributes +1 -35
- README.md +112 -7
- app.py +155 -0
- requirements.txt +4 -0
.gitattributes
CHANGED
|
@@ -1,35 +1 @@
|
|
| 1 |
-
*.
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
+
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -1,12 +1,117 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
app_file: app.py
|
| 9 |
-
pinned:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: CCTV Customer Analytics
|
| 3 |
+
emoji: "📊"
|
| 4 |
+
colorFrom: red
|
| 5 |
+
colorTo: yellow
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 5.12.0
|
| 8 |
app_file: app.py
|
| 9 |
+
pinned: true
|
| 10 |
+
license: mit
|
| 11 |
+
tags:
|
| 12 |
+
- object-detection
|
| 13 |
+
- tracking
|
| 14 |
+
- yolov8
|
| 15 |
+
- rt-detr
|
| 16 |
+
- computer-vision
|
| 17 |
+
- analytics
|
| 18 |
+
- bytetrack
|
| 19 |
+
- supervision
|
| 20 |
+
- retail-analytics
|
| 21 |
+
- people-counting
|
| 22 |
+
short_description: Object detection, tracking & counting for CCTV
|
| 23 |
---
|
| 24 |
|
| 25 |
+
# CCTV Customer Analytics
|
| 26 |
+
|
| 27 |
+
<p align="center">
|
| 28 |
+
<img src="https://img.shields.io/badge/YOLOv8-Ultralytics-blue" alt="YOLOv8">
|
| 29 |
+
<img src="https://img.shields.io/badge/RT--DETR-Transformer-green" alt="RT-DETR">
|
| 30 |
+
<img src="https://img.shields.io/badge/ByteTrack-MOT-orange" alt="ByteTrack">
|
| 31 |
+
<img src="https://img.shields.io/badge/Supervision-Roboflow-purple" alt="Supervision">
|
| 32 |
+
</p>
|
| 33 |
+
|
| 34 |
+
Real-time object detection, multi-object tracking, and line crossing counting for CCTV analytics applications. Upload a video to detect, track, and count objects (people, vehicles, etc.) crossing a configurable line.
|
| 35 |
+
|
| 36 |
+
## Features
|
| 37 |
+
|
| 38 |
+
### Detection Models
|
| 39 |
+
| Model | Speed | Accuracy | Best For |
|
| 40 |
+
|-------|-------|----------|----------|
|
| 41 |
+
| **YOLOv8n** | Very Fast | Good | Real-time, edge devices |
|
| 42 |
+
| **YOLOv8s** | Fast | Better | Balanced performance |
|
| 43 |
+
| **YOLOv8m** | Medium | High | Higher accuracy needs |
|
| 44 |
+
| **RT-DETR-l** | Medium | High | Dense/crowded scenes |
|
| 45 |
+
|
| 46 |
+
### Tracking
|
| 47 |
+
- **ByteTrack**: State-of-the-art multi-object tracking with high accuracy
|
| 48 |
+
- **BoT-SORT**: Alternative tracker for comparison
|
| 49 |
+
|
| 50 |
+
### Analytics
|
| 51 |
+
- **Line Crossing Detection**: Count objects entering/exiting across a configurable line
|
| 52 |
+
- **Per-Class Statistics**: Separate counts for each object type (person, car, truck, etc.)
|
| 53 |
+
- **Movement Traces**: Visualize object trajectories over time
|
| 54 |
+
|
| 55 |
+
## Use Cases
|
| 56 |
+
|
| 57 |
+
### Retail Analytics
|
| 58 |
+
- Customer foot traffic counting
|
| 59 |
+
- Store entrance/exit monitoring
|
| 60 |
+
- Peak hours analysis
|
| 61 |
+
- Conversion rate calculation
|
| 62 |
+
|
| 63 |
+
### Traffic Monitoring
|
| 64 |
+
- Vehicle counting at intersections
|
| 65 |
+
- Pedestrian flow analysis
|
| 66 |
+
- Traffic pattern recognition
|
| 67 |
+
|
| 68 |
+
### Security & Surveillance
|
| 69 |
+
- Entrance monitoring
|
| 70 |
+
- Occupancy tracking
|
| 71 |
+
- Perimeter breach detection
|
| 72 |
+
|
| 73 |
+
## Technical Details
|
| 74 |
+
|
| 75 |
+
### Architecture
|
| 76 |
+
```
|
| 77 |
+
Video Input → YOLOv8/RT-DETR Detection → ByteTrack MOT → Line Crossing Counter → Annotated Output
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
### Supported Object Classes
|
| 81 |
+
The system can detect and track 80 COCO classes including:
|
| 82 |
+
- **People**: person
|
| 83 |
+
- **Vehicles**: car, motorcycle, bus, truck, bicycle
|
| 84 |
+
- **Animals**: dog, cat, horse, sheep, cow
|
| 85 |
+
- And many more...
|
| 86 |
+
|
| 87 |
+
### Configuration Options
|
| 88 |
+
- **Detection Confidence**: Adjust sensitivity (0.1 - 0.9)
|
| 89 |
+
- **IOU Threshold**: Non-max suppression threshold
|
| 90 |
+
- **Track Buffer**: Frames to keep lost tracks alive
|
| 91 |
+
- **Class Filter**: Focus on specific object types
|
| 92 |
+
- **Line Position**: Adjustable counting line
|
| 93 |
+
|
| 94 |
+
## Example Results
|
| 95 |
+
|
| 96 |
+
| Scenario | Objects Tracked | Accuracy |
|
| 97 |
+
|----------|-----------------|----------|
|
| 98 |
+
| Retail Entrance | People | ~95% |
|
| 99 |
+
| Street Traffic | Vehicles + Pedestrians | ~92% |
|
| 100 |
+
| Parking Lot | Vehicles | ~94% |
|
| 101 |
+
|
| 102 |
+
## References
|
| 103 |
+
|
| 104 |
+
- [YOLOv8](https://github.com/ultralytics/ultralytics) - Ultralytics Object Detection
|
| 105 |
+
- [RT-DETR](https://arxiv.org/abs/2304.08069) - Real-Time Detection Transformer
|
| 106 |
+
- [ByteTrack](https://arxiv.org/abs/2110.06864) - Simple and Effective Multi-Object Tracking
|
| 107 |
+
- [Supervision](https://github.com/roboflow/supervision) - Computer Vision Tools by Roboflow
|
| 108 |
+
|
| 109 |
+
## Author
|
| 110 |
+
|
| 111 |
+
**Ogulcan Aydogan**
|
| 112 |
+
- HuggingFace: [@ogulcanaydogan](https://huggingface.co/ogulcanaydogan)
|
| 113 |
+
- GitHub: [@ogulcanaydogan](https://github.com/ogulcanaydogan)
|
| 114 |
+
|
| 115 |
+
## License
|
| 116 |
+
|
| 117 |
+
MIT License - Feel free to use for commercial and non-commercial purposes.
|
app.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import spaces
|
| 3 |
+
import cv2
|
| 4 |
+
import numpy as np
|
| 5 |
+
import tempfile
|
| 6 |
+
import os
|
| 7 |
+
from collections import defaultdict
|
| 8 |
+
|
| 9 |
+
import supervision as sv
|
| 10 |
+
from ultralytics import YOLO
|
| 11 |
+
|
| 12 |
+
# COCO class names
|
| 13 |
+
COCO_CLASSES = {
|
| 14 |
+
0: "person", 1: "bicycle", 2: "car", 3: "motorcycle", 5: "bus", 7: "truck"
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
MODEL_CACHE = {}
|
| 18 |
+
|
| 19 |
+
def get_model(model_name: str):
|
| 20 |
+
if model_name not in MODEL_CACHE:
|
| 21 |
+
model_map = {
|
| 22 |
+
"YOLOv8n (Fast)": "yolov8n.pt",
|
| 23 |
+
"YOLOv8s (Balanced)": "yolov8s.pt",
|
| 24 |
+
}
|
| 25 |
+
MODEL_CACHE[model_name] = YOLO(model_map.get(model_name, "yolov8n.pt"))
|
| 26 |
+
return MODEL_CACHE[model_name]
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
@spaces.GPU(duration=120)
|
| 30 |
+
def process_video(video_path, detection_model, confidence, line_position):
|
| 31 |
+
if video_path is None:
|
| 32 |
+
return None, "Please upload a video."
|
| 33 |
+
|
| 34 |
+
model = get_model(detection_model)
|
| 35 |
+
|
| 36 |
+
cap = cv2.VideoCapture(video_path)
|
| 37 |
+
if not cap.isOpened():
|
| 38 |
+
return None, "Failed to open video."
|
| 39 |
+
|
| 40 |
+
fps = int(cap.get(cv2.CAP_PROP_FPS)) or 30
|
| 41 |
+
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 42 |
+
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 43 |
+
|
| 44 |
+
output_path = tempfile.mktemp(suffix=".mp4")
|
| 45 |
+
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
|
| 46 |
+
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
|
| 47 |
+
|
| 48 |
+
tracker = sv.ByteTrack(
|
| 49 |
+
track_activation_threshold=0.25,
|
| 50 |
+
lost_track_buffer=30,
|
| 51 |
+
minimum_matching_threshold=0.8,
|
| 52 |
+
frame_rate=fps,
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
line_y = int(height * line_position)
|
| 56 |
+
line_start = sv.Point(0, line_y)
|
| 57 |
+
line_end = sv.Point(width, line_y)
|
| 58 |
+
line_zone = sv.LineZone(start=line_start, end=line_end)
|
| 59 |
+
|
| 60 |
+
box_annotator = sv.BoxAnnotator(thickness=2)
|
| 61 |
+
label_annotator = sv.LabelAnnotator(text_scale=0.5, text_thickness=1)
|
| 62 |
+
trace_annotator = sv.TraceAnnotator(thickness=2, trace_length=30)
|
| 63 |
+
line_annotator = sv.LineZoneAnnotator(thickness=2, text_scale=0.5)
|
| 64 |
+
|
| 65 |
+
total_in = 0
|
| 66 |
+
total_out = 0
|
| 67 |
+
class_counts = defaultdict(lambda: {"in": 0, "out": 0})
|
| 68 |
+
frame_idx = 0
|
| 69 |
+
|
| 70 |
+
while True:
|
| 71 |
+
ret, frame = cap.read()
|
| 72 |
+
if not ret:
|
| 73 |
+
break
|
| 74 |
+
|
| 75 |
+
results = model.predict(frame, conf=confidence, verbose=False)[0]
|
| 76 |
+
detections = sv.Detections.from_ultralytics(results)
|
| 77 |
+
detections = tracker.update_with_detections(detections)
|
| 78 |
+
|
| 79 |
+
crossed_in, crossed_out = line_zone.trigger(detections)
|
| 80 |
+
|
| 81 |
+
if crossed_in.any():
|
| 82 |
+
for idx in np.where(crossed_in)[0]:
|
| 83 |
+
class_id = int(detections.class_id[idx]) if detections.class_id is not None else 0
|
| 84 |
+
class_name = COCO_CLASSES.get(class_id, f"class_{class_id}")
|
| 85 |
+
class_counts[class_name]["in"] += 1
|
| 86 |
+
total_in += 1
|
| 87 |
+
|
| 88 |
+
if crossed_out.any():
|
| 89 |
+
for idx in np.where(crossed_out)[0]:
|
| 90 |
+
class_id = int(detections.class_id[idx]) if detections.class_id is not None else 0
|
| 91 |
+
class_name = COCO_CLASSES.get(class_id, f"class_{class_id}")
|
| 92 |
+
class_counts[class_name]["out"] += 1
|
| 93 |
+
total_out += 1
|
| 94 |
+
|
| 95 |
+
annotated = frame.copy()
|
| 96 |
+
annotated = trace_annotator.annotate(annotated, detections)
|
| 97 |
+
annotated = box_annotator.annotate(annotated, detections)
|
| 98 |
+
|
| 99 |
+
labels = []
|
| 100 |
+
for idx in range(len(detections)):
|
| 101 |
+
class_id = int(detections.class_id[idx]) if detections.class_id is not None else 0
|
| 102 |
+
class_name = COCO_CLASSES.get(class_id, f"class_{class_id}")
|
| 103 |
+
track_id = detections.tracker_id[idx] if detections.tracker_id is not None else 0
|
| 104 |
+
labels.append(f"{class_name} #{track_id}")
|
| 105 |
+
|
| 106 |
+
annotated = label_annotator.annotate(annotated, detections, labels)
|
| 107 |
+
annotated = line_annotator.annotate(annotated, line_zone)
|
| 108 |
+
|
| 109 |
+
cv2.putText(annotated, f"IN: {total_in} | OUT: {total_out}",
|
| 110 |
+
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
|
| 111 |
+
|
| 112 |
+
out.write(annotated)
|
| 113 |
+
frame_idx += 1
|
| 114 |
+
|
| 115 |
+
cap.release()
|
| 116 |
+
out.release()
|
| 117 |
+
|
| 118 |
+
final_path = tempfile.mktemp(suffix=".mp4")
|
| 119 |
+
os.system(f"ffmpeg -y -i {output_path} -c:v libx264 -preset fast -crf 23 {final_path} -loglevel quiet")
|
| 120 |
+
|
| 121 |
+
if os.path.exists(final_path) and os.path.getsize(final_path) > 0:
|
| 122 |
+
os.remove(output_path)
|
| 123 |
+
output_path = final_path
|
| 124 |
+
|
| 125 |
+
stats = f"## Results\n\n**Entered:** {total_in}\n**Exited:** {total_out}\n**Net:** {total_in - total_out}\n\n"
|
| 126 |
+
for cls, counts in sorted(class_counts.items()):
|
| 127 |
+
stats += f"- {cls}: IN={counts['in']}, OUT={counts['out']}\n"
|
| 128 |
+
stats += f"\n**Frames:** {frame_idx}"
|
| 129 |
+
|
| 130 |
+
return output_path, stats
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
demo = gr.Interface(
|
| 134 |
+
fn=process_video,
|
| 135 |
+
inputs=[
|
| 136 |
+
gr.Video(label="Upload Video"),
|
| 137 |
+
gr.Dropdown(
|
| 138 |
+
choices=["YOLOv8n (Fast)", "YOLOv8s (Balanced)"],
|
| 139 |
+
value="YOLOv8s (Balanced)",
|
| 140 |
+
label="Model"
|
| 141 |
+
),
|
| 142 |
+
gr.Slider(0.1, 0.9, value=0.3, step=0.05, label="Confidence"),
|
| 143 |
+
gr.Slider(0.1, 0.9, value=0.5, step=0.05, label="Line Position"),
|
| 144 |
+
],
|
| 145 |
+
outputs=[
|
| 146 |
+
gr.Video(label="Processed Video"),
|
| 147 |
+
gr.Markdown(label="Statistics")
|
| 148 |
+
],
|
| 149 |
+
title="CCTV Customer Analytics",
|
| 150 |
+
description="Upload a video to detect, track, and count objects crossing a line.",
|
| 151 |
+
allow_flagging="never"
|
| 152 |
+
)
|
| 153 |
+
|
| 154 |
+
if __name__ == "__main__":
|
| 155 |
+
demo.launch()
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
ultralytics
|
| 2 |
+
supervision
|
| 3 |
+
opencv-python-headless
|
| 4 |
+
lapx
|