Spaces:
Sleeping
Sleeping
elon-trump
commited on
Commit
•
f6c1f46
1
Parent(s):
be9c91a
Upload 8 files
Browse files- .gitattributes +1 -0
- Dockerfile +27 -0
- Object_Flow_Tracker.py +334 -0
- Screencast from 25-10-24 04:51:15 PM IST.webm +3 -0
- requirements.txt +6 -0
- runtime.txt +1 -0
- yolov8n.pt +3 -0
.gitattributes
CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* 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
|
|
|
|
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
|
36 |
+
Screencast[[:space:]]from[[:space:]]25-10-24[[:space:]]04:51:15[[:space:]]PM[[:space:]]IST.webm filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Use a Python 3.10 image
|
2 |
+
FROM python:3.10-slim
|
3 |
+
|
4 |
+
# Set the working directory inside the container
|
5 |
+
WORKDIR /app
|
6 |
+
|
7 |
+
# Install system dependencies (needed for OpenCV)
|
8 |
+
RUN apt-get update && apt-get install -y \
|
9 |
+
libgl1-mesa-glx \
|
10 |
+
libglib2.0-0 \
|
11 |
+
&& rm -rf /var/lib/apt/lists/*
|
12 |
+
|
13 |
+
# Copy the requirements.txt to install dependencies
|
14 |
+
COPY requirements.txt .
|
15 |
+
|
16 |
+
# Install the Python dependencies
|
17 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
18 |
+
|
19 |
+
# Copy the application code
|
20 |
+
COPY . .
|
21 |
+
|
22 |
+
# Expose the port for Streamlit (default is 8501)
|
23 |
+
EXPOSE 8501
|
24 |
+
|
25 |
+
# Command to run your application (use Streamlit as example)
|
26 |
+
CMD ["streamlit", "run", "app.py"]
|
27 |
+
|
Object_Flow_Tracker.py
ADDED
@@ -0,0 +1,334 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import numpy as np
|
3 |
+
from typing import Union, Tuple, List, Dict
|
4 |
+
import time
|
5 |
+
import os
|
6 |
+
from ultralytics import YOLO
|
7 |
+
import torch
|
8 |
+
import logging
|
9 |
+
import sys
|
10 |
+
from collections import deque
|
11 |
+
|
12 |
+
# Configure logging
|
13 |
+
logging.basicConfig(
|
14 |
+
level=logging.INFO,
|
15 |
+
format='%(asctime)s - %(levelname)s - %(message)s',
|
16 |
+
stream=sys.stdout
|
17 |
+
)
|
18 |
+
logger = logging.getLogger(__name__)
|
19 |
+
|
20 |
+
class YOLOFlowTracker:
|
21 |
+
def __init__(self,
|
22 |
+
yolo_model_path: str,
|
23 |
+
confidence_threshold: float = 0.5,
|
24 |
+
window_size: int = 21,
|
25 |
+
max_level: int = 3,
|
26 |
+
track_len: int = 10,
|
27 |
+
grid_size: int = 20): # Grid cell size in pixels
|
28 |
+
"""Initialize the tracker with detailed error checking."""
|
29 |
+
try:
|
30 |
+
logger.info("Initializing YOLOFlowTracker...")
|
31 |
+
|
32 |
+
# Initialize YOLO model
|
33 |
+
self.yolo_model = YOLO(yolo_model_path)
|
34 |
+
self.conf_threshold = confidence_threshold
|
35 |
+
|
36 |
+
# Store configuration
|
37 |
+
self.window_size = window_size
|
38 |
+
self.max_level = max_level
|
39 |
+
self.track_len = track_len
|
40 |
+
self.grid_size = grid_size
|
41 |
+
|
42 |
+
# Initialize tracking parameters
|
43 |
+
self.lk_params = dict(
|
44 |
+
winSize=(window_size, window_size),
|
45 |
+
maxLevel=max_level,
|
46 |
+
criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 30, 0.01)
|
47 |
+
)
|
48 |
+
|
49 |
+
# Initialize state variables
|
50 |
+
self.prev_gray = None
|
51 |
+
self.tracks = {}
|
52 |
+
self.track_ids = 0
|
53 |
+
self.prev_time = time.time()
|
54 |
+
self.fps = 0
|
55 |
+
self.frame_count = 0
|
56 |
+
self.prev_points = None
|
57 |
+
self.prev_grid_flow = None
|
58 |
+
|
59 |
+
# Device information
|
60 |
+
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
61 |
+
if self.device == "cuda":
|
62 |
+
self.device_name = torch.cuda.get_device_name(0)
|
63 |
+
else:
|
64 |
+
self.device_name = "CPU"
|
65 |
+
|
66 |
+
logger.info("YOLOFlowTracker initialized successfully")
|
67 |
+
|
68 |
+
except Exception as e:
|
69 |
+
logger.error(f"Failed to initialize YOLOFlowTracker: {str(e)}")
|
70 |
+
raise
|
71 |
+
|
72 |
+
def detect_objects(self, frame: np.ndarray) -> List[Dict]:
|
73 |
+
"""Detect objects using YOLO-v8."""
|
74 |
+
try:
|
75 |
+
results = self.yolo_model(frame, verbose=False)[0]
|
76 |
+
detections = []
|
77 |
+
|
78 |
+
for box in results.boxes:
|
79 |
+
x1, y1, x2, y2 = map(int, box.xyxy[0].tolist())
|
80 |
+
conf = float(box.conf)
|
81 |
+
|
82 |
+
if conf > self.conf_threshold:
|
83 |
+
detection = {
|
84 |
+
'bbox': (x1, y1, x2, y2),
|
85 |
+
'confidence': conf,
|
86 |
+
'center': (int((x1 + x2) / 2), int((y1 + y2) / 2))
|
87 |
+
}
|
88 |
+
detections.append(detection)
|
89 |
+
|
90 |
+
return detections
|
91 |
+
|
92 |
+
except Exception as e:
|
93 |
+
logger.error(f"Error in object detection: {str(e)}")
|
94 |
+
return []
|
95 |
+
|
96 |
+
def create_grid_points(self, bbox: Tuple[int, int, int, int], margin: int = 50) -> np.ndarray:
|
97 |
+
"""Create a grid of points around the bounding box."""
|
98 |
+
x1, y1, x2, y2 = bbox
|
99 |
+
|
100 |
+
# Add margin around bbox
|
101 |
+
x1 = max(0, x1 - margin)
|
102 |
+
y1 = max(0, y1 - margin)
|
103 |
+
x2 = x2 + margin
|
104 |
+
y2 = y2 + margin
|
105 |
+
|
106 |
+
# Create grid points
|
107 |
+
x_points = np.arange(x1, x2, self.grid_size)
|
108 |
+
y_points = np.arange(y1, y2, self.grid_size)
|
109 |
+
|
110 |
+
# Create meshgrid
|
111 |
+
xv, yv = np.meshgrid(x_points, y_points)
|
112 |
+
|
113 |
+
# Reshape to Nx2 array of points
|
114 |
+
return np.stack([xv.flatten(), yv.flatten()], axis=1).astype(np.float32)
|
115 |
+
|
116 |
+
def draw_grid_flow(self, img: np.ndarray, points: np.ndarray,
|
117 |
+
next_points: np.ndarray, status: np.ndarray) -> None:
|
118 |
+
"""Draw grid flow arrows with professional styling."""
|
119 |
+
mask = status.flatten() == 1
|
120 |
+
points = points[mask]
|
121 |
+
next_points = next_points[mask]
|
122 |
+
|
123 |
+
# Calculate flow vectors
|
124 |
+
flow_vectors = next_points - points
|
125 |
+
|
126 |
+
# Calculate vector magnitudes
|
127 |
+
magnitudes = np.linalg.norm(flow_vectors, axis=1)
|
128 |
+
max_magnitude = np.max(magnitudes) if magnitudes.size > 0 else 1
|
129 |
+
|
130 |
+
for pt1, pt2, magnitude in zip(points, next_points, magnitudes):
|
131 |
+
# Skip very small movements
|
132 |
+
if magnitude < 0.5:
|
133 |
+
continue
|
134 |
+
|
135 |
+
# Normalize magnitude for color intensity
|
136 |
+
intensity = min(magnitude / max_magnitude, 1.0)
|
137 |
+
|
138 |
+
# Create color gradient from blue to yellow based on magnitude
|
139 |
+
color = (0,
|
140 |
+
int(255 * intensity), # Green component
|
141 |
+
int(255 * (1 - intensity))) # Red component
|
142 |
+
|
143 |
+
# Calculate arrow properties
|
144 |
+
angle = np.arctan2(pt2[1] - pt1[1], pt2[0] - pt1[0])
|
145 |
+
length = np.sqrt((pt2[0] - pt1[0])**2 + (pt2[1] - pt1[1])**2)
|
146 |
+
|
147 |
+
# Draw arrow with adaptive size
|
148 |
+
arrow_length = min(length, self.grid_size * 0.8)
|
149 |
+
arrow_head_size = min(5 + intensity * 5, arrow_length * 0.3)
|
150 |
+
|
151 |
+
# Calculate end point
|
152 |
+
end_pt = (int(pt1[0] + arrow_length * np.cos(angle)),
|
153 |
+
int(pt1[1] + arrow_length * np.sin(angle)))
|
154 |
+
|
155 |
+
# Draw arrow shaft with anti-aliasing
|
156 |
+
cv2.line(img, tuple(map(int, pt1)), end_pt, color, 1, cv2.LINE_AA)
|
157 |
+
|
158 |
+
# Draw arrow head
|
159 |
+
arrow_head_angle = np.pi / 6 # 30 degrees
|
160 |
+
pt_left = (
|
161 |
+
int(end_pt[0] - arrow_head_size * np.cos(angle + arrow_head_angle)),
|
162 |
+
int(end_pt[1] - arrow_head_size * np.sin(angle + arrow_head_angle))
|
163 |
+
)
|
164 |
+
pt_right = (
|
165 |
+
int(end_pt[0] - arrow_head_size * np.cos(angle - arrow_head_angle)),
|
166 |
+
int(end_pt[1] - arrow_head_size * np.sin(angle - arrow_head_angle))
|
167 |
+
)
|
168 |
+
|
169 |
+
# Draw arrow head with anti-aliasing
|
170 |
+
cv2.polylines(img, [np.array([end_pt, pt_left, pt_right])],
|
171 |
+
True, color, 1, cv2.LINE_AA)
|
172 |
+
|
173 |
+
def draw_performance_metrics(self, img: np.ndarray) -> None:
|
174 |
+
"""Draw FPS and device information on the frame."""
|
175 |
+
current_time = time.time()
|
176 |
+
self.frame_count += 1
|
177 |
+
if current_time - self.prev_time >= 1.0:
|
178 |
+
self.fps = self.frame_count
|
179 |
+
self.frame_count = 0
|
180 |
+
self.prev_time = current_time
|
181 |
+
|
182 |
+
# Create semi-transparent overlay
|
183 |
+
overlay = img.copy()
|
184 |
+
cv2.rectangle(overlay, (10, 10), (250, 70), (0, 0, 0), -1)
|
185 |
+
cv2.addWeighted(overlay, 0.3, img, 0.7, 0, img)
|
186 |
+
|
187 |
+
# Draw metrics
|
188 |
+
metrics = [
|
189 |
+
f"FPS: {self.fps}",
|
190 |
+
f"Device: {self.device_name}",
|
191 |
+
f"Grid Size: {self.grid_size}px"
|
192 |
+
]
|
193 |
+
|
194 |
+
for i, text in enumerate(metrics):
|
195 |
+
cv2.putText(img, text, (20, 30 + i*20),
|
196 |
+
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
|
197 |
+
|
198 |
+
def process_frame(self, frame: np.ndarray) -> np.ndarray:
|
199 |
+
"""Process a single frame with detection and tracking."""
|
200 |
+
try:
|
201 |
+
if frame is None:
|
202 |
+
logger.error("Received empty frame")
|
203 |
+
return None
|
204 |
+
|
205 |
+
# Convert frame to grayscale for optical flow
|
206 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
207 |
+
vis = frame.copy()
|
208 |
+
|
209 |
+
# Initialize previous frame if needed
|
210 |
+
if self.prev_gray is None:
|
211 |
+
self.prev_gray = gray
|
212 |
+
return vis
|
213 |
+
|
214 |
+
# Detect objects
|
215 |
+
detections = self.detect_objects(frame)
|
216 |
+
|
217 |
+
for det in detections:
|
218 |
+
# Draw bounding box
|
219 |
+
x1, y1, x2, y2 = det['bbox']
|
220 |
+
cv2.rectangle(vis, (x1, y1), (x2, y2), (0, 255, 0), 2)
|
221 |
+
|
222 |
+
# Create grid points around the detected object
|
223 |
+
grid_points = self.create_grid_points(det['bbox'])
|
224 |
+
|
225 |
+
if grid_points.size > 0:
|
226 |
+
# Calculate optical flow for grid points
|
227 |
+
next_points, status, _ = cv2.calcOpticalFlowPyrLK(
|
228 |
+
self.prev_gray, gray, grid_points,
|
229 |
+
None, **self.lk_params
|
230 |
+
)
|
231 |
+
|
232 |
+
if next_points is not None:
|
233 |
+
# Draw flow grid
|
234 |
+
self.draw_grid_flow(vis, grid_points, next_points, status)
|
235 |
+
|
236 |
+
# Draw main object motion arrow
|
237 |
+
center = det['center']
|
238 |
+
mean_flow = np.mean(next_points[status.flatten() == 1] -
|
239 |
+
grid_points[status.flatten() == 1], axis=0)
|
240 |
+
if not np.isnan(mean_flow).any():
|
241 |
+
end_point = (int(center[0] + mean_flow[0] * 2),
|
242 |
+
int(center[1] + mean_flow[1] * 2))
|
243 |
+
cv2.arrowedLine(vis, center, end_point,
|
244 |
+
(0, 255, 255), 3, cv2.LINE_AA, tipLength=0.3)
|
245 |
+
|
246 |
+
# Draw performance metrics
|
247 |
+
self.draw_performance_metrics(vis)
|
248 |
+
|
249 |
+
# Update previous frame
|
250 |
+
self.prev_gray = gray.copy()
|
251 |
+
|
252 |
+
return vis
|
253 |
+
|
254 |
+
except Exception as e:
|
255 |
+
logger.error(f"Error processing frame: {str(e)}")
|
256 |
+
return frame if frame is not None else None
|
257 |
+
|
258 |
+
def process_video(self, source: Union[str, int], display: bool = True) -> None:
|
259 |
+
"""Process video stream with detection and tracking."""
|
260 |
+
cap = None
|
261 |
+
window_name = 'Object Flow Tracking'
|
262 |
+
|
263 |
+
try:
|
264 |
+
logger.info(f"Opening video source: {source}")
|
265 |
+
cap = cv2.VideoCapture(source)
|
266 |
+
|
267 |
+
if not cap.isOpened():
|
268 |
+
raise ValueError(f"Failed to open video source: {source}")
|
269 |
+
|
270 |
+
if display:
|
271 |
+
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
|
272 |
+
|
273 |
+
while True:
|
274 |
+
ret, frame = cap.read()
|
275 |
+
if not ret:
|
276 |
+
logger.info("End of video stream")
|
277 |
+
break
|
278 |
+
|
279 |
+
processed_frame = self.process_frame(frame)
|
280 |
+
|
281 |
+
if processed_frame is not None and display:
|
282 |
+
try:
|
283 |
+
cv2.imshow(window_name, processed_frame)
|
284 |
+
if cv2.waitKey(1) & 0xFF == ord('q'):
|
285 |
+
logger.info("User requested quit")
|
286 |
+
break
|
287 |
+
except Exception as e:
|
288 |
+
logger.error(f"Error displaying frame: {str(e)}")
|
289 |
+
|
290 |
+
time.sleep(0.001)
|
291 |
+
|
292 |
+
except Exception as e:
|
293 |
+
logger.error(f"Error in video processing: {str(e)}")
|
294 |
+
|
295 |
+
finally:
|
296 |
+
if cap is not None:
|
297 |
+
cap.release()
|
298 |
+
if display:
|
299 |
+
cv2.destroyAllWindows()
|
300 |
+
for _ in range(5):
|
301 |
+
cv2.waitKey(1)
|
302 |
+
|
303 |
+
def main():
|
304 |
+
try:
|
305 |
+
logger.info("Starting object flow tracking application...")
|
306 |
+
|
307 |
+
cuda_available = torch.cuda.is_available()
|
308 |
+
device = "cuda" if cuda_available else "cpu"
|
309 |
+
logger.info(f"Using device: {device}")
|
310 |
+
|
311 |
+
model_path = "yolov8n.pt"
|
312 |
+
if not os.path.exists(model_path):
|
313 |
+
logger.error(f"YOLO model not found at: {model_path}")
|
314 |
+
return
|
315 |
+
|
316 |
+
tracker = YOLOFlowTracker(
|
317 |
+
yolo_model_path=model_path,
|
318 |
+
confidence_threshold=0.5,
|
319 |
+
grid_size=20 # Adjust grid density
|
320 |
+
)
|
321 |
+
|
322 |
+
tracker.process_video('Data/2.mp4') # Use camera index 0
|
323 |
+
|
324 |
+
except KeyboardInterrupt:
|
325 |
+
logger.info("Application terminated by user")
|
326 |
+
except Exception as e:
|
327 |
+
logger.error(f"Application failed: {str(e)}")
|
328 |
+
finally:
|
329 |
+
cv2.destroyAllWindows()
|
330 |
+
for _ in range(5):
|
331 |
+
cv2.waitKey(1)
|
332 |
+
|
333 |
+
if __name__ == "__main__":
|
334 |
+
main()
|
Screencast from 25-10-24 04:51:15 PM IST.webm
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:5b9d881f7fce27e02b2face616563a202a9a5f01766e258bb22066b0f0eaf957
|
3 |
+
size 21185916
|
requirements.txt
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
streamlit
|
2 |
+
opencv-python-headless
|
3 |
+
ultralytics
|
4 |
+
torch
|
5 |
+
numpy==1.23.5
|
6 |
+
streamlit-webrtc
|
runtime.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
python-3.10
|
yolov8n.pt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:f59b3d833e2ff32e194b5bb8e08d211dc7c5bdf144b90d2c8412c47ccfc83b36
|
3 |
+
size 6549796
|