Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -42,8 +42,8 @@ FFMPEG_AVAILABLE = check_ffmpeg()
|
|
| 42 |
class BYTETracker:
|
| 43 |
def __init__(self, track_thresh=0.3, track_buffer=90, match_thresh=0.5, frame_rate=30):
|
| 44 |
self.track_thresh = track_thresh
|
| 45 |
-
self.track_buffer = track_buffer
|
| 46 |
-
self.match_thresh = match_thresh
|
| 47 |
self.frame_rate = frame_rate
|
| 48 |
self.next_id = 1
|
| 49 |
self.tracks = {}
|
|
@@ -63,7 +63,6 @@ class BYTETracker:
|
|
| 63 |
best_iou = 0
|
| 64 |
best_track_id = None
|
| 65 |
|
| 66 |
-
# Try to match with existing tracks
|
| 67 |
for track_id, track_info in self.tracks.items():
|
| 68 |
if current_time - track_info['last_seen'] > self.track_buffer / self.frame_rate:
|
| 69 |
continue
|
|
@@ -160,7 +159,7 @@ class BYTETracker:
|
|
| 160 |
iou = intersection_area / (box1_area + box2_area - intersection_area)
|
| 161 |
return iou
|
| 162 |
|
| 163 |
-
def _is_same_worker(self, pos1, pos2, threshold=300):
|
| 164 |
x1, y1 = pos1
|
| 165 |
x2, y2 = pos2
|
| 166 |
distance = np.sqrt((x1 - x2)**2 + (y1 - y2)**2)
|
|
@@ -209,14 +208,15 @@ CONFIG = {
|
|
| 209 |
"VIOLATION_COOLDOWN": 30.0,
|
| 210 |
"WORKER_TRACKING_DURATION": 5.0,
|
| 211 |
"MAX_PROCESSING_TIME": 60,
|
| 212 |
-
"FRAME_SKIP": 1,
|
| 213 |
-
"BATCH_SIZE":
|
| 214 |
"PARALLEL_WORKERS": max(1, cpu_count() - 1),
|
| 215 |
-
"TRACK_BUFFER": 90,
|
| 216 |
"TRACK_THRESH": 0.3,
|
| 217 |
-
"MATCH_THRESH": 0.5,
|
| 218 |
"SNAPSHOT_QUALITY": 95,
|
| 219 |
-
"MAX_WORKER_DISTANCE": 300
|
|
|
|
| 220 |
}
|
| 221 |
|
| 222 |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
@@ -235,6 +235,9 @@ def load_model():
|
|
| 235 |
torch.hub.download_url_to_file('https://github.com/ultralytics/assets/releases/download/v8.3.0/yolov8n.pt', model_path)
|
| 236 |
|
| 237 |
model = YOLO(model_path).to(device)
|
|
|
|
|
|
|
|
|
|
| 238 |
logger.info(f"Model classes: {model.names}")
|
| 239 |
return model
|
| 240 |
except Exception as e:
|
|
@@ -245,6 +248,9 @@ model = load_model()
|
|
| 245 |
|
| 246 |
# ========================== # Helper Functions # ==========================
|
| 247 |
def preprocess_frame(frame):
|
|
|
|
|
|
|
|
|
|
| 248 |
frame = cv2.convertScaleAbs(frame, alpha=1.2, beta=20)
|
| 249 |
return frame
|
| 250 |
|
|
@@ -542,9 +548,8 @@ def process_video(video_data, temp_dir):
|
|
| 542 |
frame_rate=fps
|
| 543 |
)
|
| 544 |
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
unique_violations = {} # Keyed by violation type to ensure uniqueness
|
| 548 |
snapshots = []
|
| 549 |
start_time = time.time()
|
| 550 |
frame_skip = CONFIG["FRAME_SKIP"]
|
|
@@ -579,17 +584,30 @@ def process_video(video_data, temp_dir):
|
|
| 579 |
break
|
| 580 |
|
| 581 |
try:
|
| 582 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 583 |
except Exception as e:
|
| 584 |
logger.error(f"Model inference failed: {e}")
|
| 585 |
raise ValueError(f"Failed to process video frames with YOLO model: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 586 |
|
| 587 |
for i, (result, frame_idx) in enumerate(zip(results, batch_indices)):
|
| 588 |
current_time = frame_idx / fps
|
| 589 |
|
| 590 |
-
if time.time() - start_time >
|
| 591 |
progress = (processed_frames / total_frames) * 100
|
| 592 |
-
|
|
|
|
|
|
|
| 593 |
start_time = time.time()
|
| 594 |
|
| 595 |
boxes = result.boxes
|
|
@@ -631,16 +649,13 @@ def process_video(video_data, temp_dir):
|
|
| 631 |
if label is None:
|
| 632 |
continue
|
| 633 |
|
| 634 |
-
# Map all tracker IDs to a single worker ID (since we know there's only one worker)
|
| 635 |
if not worker_id_mapping:
|
| 636 |
-
worker_id_mapping[tracker_id] = 1
|
| 637 |
else:
|
| 638 |
-
# Map all subsequent tracker IDs to the same worker ID
|
| 639 |
worker_id_mapping[tracker_id] = worker_id_mapping[list(worker_id_mapping.keys())[0]]
|
| 640 |
|
| 641 |
worker_id = worker_id_mapping[tracker_id]
|
| 642 |
|
| 643 |
-
# Use violation type as key to ensure uniqueness per worker
|
| 644 |
violation_key = (worker_id, label)
|
| 645 |
|
| 646 |
if violation_key not in unique_violations:
|
|
@@ -654,7 +669,8 @@ def process_video(video_data, temp_dir):
|
|
| 654 |
"timestamp": current_time
|
| 655 |
}
|
| 656 |
|
| 657 |
-
snapshot_frame =
|
|
|
|
| 658 |
snapshot_frame = draw_detections(snapshot_frame, [detection])
|
| 659 |
|
| 660 |
cv2.putText(
|
|
@@ -687,6 +703,10 @@ def process_video(video_data, temp_dir):
|
|
| 687 |
|
| 688 |
logger.info(f"Captured snapshot for {label} violation by worker {worker_id} at {current_time:.2f}s")
|
| 689 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 690 |
cap.release()
|
| 691 |
processing_time = time.time() - start_time
|
| 692 |
logger.info(f"Processing complete in {processing_time:.2f}s")
|
|
@@ -764,6 +784,8 @@ def process_video(video_data, temp_dir):
|
|
| 764 |
logger.info(f"Cleaned up temporary video file: {video_path}")
|
| 765 |
except Exception as e:
|
| 766 |
logger.error(f"Failed to clean up temporary video file {video_path}: {e}")
|
|
|
|
|
|
|
| 767 |
|
| 768 |
def gradio_interface(video_file):
|
| 769 |
temp_dir = None
|
|
@@ -808,6 +830,8 @@ def gradio_interface(video_file):
|
|
| 808 |
if temp_dir and os.path.exists(temp_dir):
|
| 809 |
shutil.rmtree(temp_dir, ignore_errors=True)
|
| 810 |
logger.info(f"Cleaned up temporary directory: {temp_dir}")
|
|
|
|
|
|
|
| 811 |
|
| 812 |
# ========================== # Gradio Interface # ==========================
|
| 813 |
interface = gr.Interface(
|
|
|
|
| 42 |
class BYTETracker:
|
| 43 |
def __init__(self, track_thresh=0.3, track_buffer=90, match_thresh=0.5, frame_rate=30):
|
| 44 |
self.track_thresh = track_thresh
|
| 45 |
+
self.track_buffer = track_buffer
|
| 46 |
+
self.match_thresh = match_thresh
|
| 47 |
self.frame_rate = frame_rate
|
| 48 |
self.next_id = 1
|
| 49 |
self.tracks = {}
|
|
|
|
| 63 |
best_iou = 0
|
| 64 |
best_track_id = None
|
| 65 |
|
|
|
|
| 66 |
for track_id, track_info in self.tracks.items():
|
| 67 |
if current_time - track_info['last_seen'] > self.track_buffer / self.frame_rate:
|
| 68 |
continue
|
|
|
|
| 159 |
iou = intersection_area / (box1_area + box2_area - intersection_area)
|
| 160 |
return iou
|
| 161 |
|
| 162 |
+
def _is_same_worker(self, pos1, pos2, threshold=300):
|
| 163 |
x1, y1 = pos1
|
| 164 |
x2, y2 = pos2
|
| 165 |
distance = np.sqrt((x1 - x2)**2 + (y1 - y2)**2)
|
|
|
|
| 208 |
"VIOLATION_COOLDOWN": 30.0,
|
| 209 |
"WORKER_TRACKING_DURATION": 5.0,
|
| 210 |
"MAX_PROCESSING_TIME": 60,
|
| 211 |
+
"FRAME_SKIP": 1,
|
| 212 |
+
"BATCH_SIZE": 4, # Reduced to 4 to lower memory usage
|
| 213 |
"PARALLEL_WORKERS": max(1, cpu_count() - 1),
|
| 214 |
+
"TRACK_BUFFER": 90,
|
| 215 |
"TRACK_THRESH": 0.3,
|
| 216 |
+
"MATCH_THRESH": 0.5,
|
| 217 |
"SNAPSHOT_QUALITY": 95,
|
| 218 |
+
"MAX_WORKER_DISTANCE": 300,
|
| 219 |
+
"TARGET_RESOLUTION": (640, 360) # Added to resize frames
|
| 220 |
}
|
| 221 |
|
| 222 |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
|
|
| 235 |
torch.hub.download_url_to_file('https://github.com/ultralytics/assets/releases/download/v8.3.0/yolov8n.pt', model_path)
|
| 236 |
|
| 237 |
model = YOLO(model_path).to(device)
|
| 238 |
+
# Enable FP16 inference if on GPU
|
| 239 |
+
if device.type == "cuda":
|
| 240 |
+
model.model.half()
|
| 241 |
logger.info(f"Model classes: {model.names}")
|
| 242 |
return model
|
| 243 |
except Exception as e:
|
|
|
|
| 248 |
|
| 249 |
# ========================== # Helper Functions # ==========================
|
| 250 |
def preprocess_frame(frame):
|
| 251 |
+
# Resize frame to target resolution
|
| 252 |
+
target_res = CONFIG["TARGET_RESOLUTION"]
|
| 253 |
+
frame = cv2.resize(frame, target_res, interpolation=cv2.INTER_LINEAR)
|
| 254 |
frame = cv2.convertScaleAbs(frame, alpha=1.2, beta=20)
|
| 255 |
return frame
|
| 256 |
|
|
|
|
| 548 |
frame_rate=fps
|
| 549 |
)
|
| 550 |
|
| 551 |
+
worker_id_mapping = {}
|
| 552 |
+
unique_violations = {}
|
|
|
|
| 553 |
snapshots = []
|
| 554 |
start_time = time.time()
|
| 555 |
frame_skip = CONFIG["FRAME_SKIP"]
|
|
|
|
| 584 |
break
|
| 585 |
|
| 586 |
try:
|
| 587 |
+
# Convert frames to tensor and move to device
|
| 588 |
+
batch_frames_tensor = [torch.from_numpy(frame).permute(2, 0, 1).float() / 255.0 for frame in batch_frames]
|
| 589 |
+
batch_frames_tensor = torch.stack(batch_frames_tensor).to(device)
|
| 590 |
+
if device.type == "cuda":
|
| 591 |
+
batch_frames_tensor = batch_frames_tensor.half()
|
| 592 |
+
|
| 593 |
+
results = model(batch_frames_tensor, device=device, conf=0.1, verbose=False)
|
| 594 |
except Exception as e:
|
| 595 |
logger.error(f"Model inference failed: {e}")
|
| 596 |
raise ValueError(f"Failed to process video frames with YOLO model: {str(e)}")
|
| 597 |
+
finally:
|
| 598 |
+
# Clear memory
|
| 599 |
+
batch_frames = [] # Clear the list to free memory
|
| 600 |
+
if device.type == "cuda":
|
| 601 |
+
torch.cuda.empty_cache()
|
| 602 |
|
| 603 |
for i, (result, frame_idx) in enumerate(zip(results, batch_indices)):
|
| 604 |
current_time = frame_idx / fps
|
| 605 |
|
| 606 |
+
if time.time() - start_time > 0.5: # Yield more frequently
|
| 607 |
progress = (processed_frames / total_frames) * 100
|
| 608 |
+
elapsed_time = time.time() - start_time
|
| 609 |
+
fps_processed = processed_frames / elapsed_time if elapsed_time > 0 else 0
|
| 610 |
+
yield f"Processing video... {progress:.1f}% complete (Frame {processed_frames}/{total_frames}, {fps_processed:.1f} FPS)", "", "", "", ""
|
| 611 |
start_time = time.time()
|
| 612 |
|
| 613 |
boxes = result.boxes
|
|
|
|
| 649 |
if label is None:
|
| 650 |
continue
|
| 651 |
|
|
|
|
| 652 |
if not worker_id_mapping:
|
| 653 |
+
worker_id_mapping[tracker_id] = 1
|
| 654 |
else:
|
|
|
|
| 655 |
worker_id_mapping[tracker_id] = worker_id_mapping[list(worker_id_mapping.keys())[0]]
|
| 656 |
|
| 657 |
worker_id = worker_id_mapping[tracker_id]
|
| 658 |
|
|
|
|
| 659 |
violation_key = (worker_id, label)
|
| 660 |
|
| 661 |
if violation_key not in unique_violations:
|
|
|
|
| 669 |
"timestamp": current_time
|
| 670 |
}
|
| 671 |
|
| 672 |
+
snapshot_frame = batch_frames_tensor[i].cpu().numpy().transpose(1, 2, 0) * 255
|
| 673 |
+
snapshot_frame = snapshot_frame.astype(np.uint8)
|
| 674 |
snapshot_frame = draw_detections(snapshot_frame, [detection])
|
| 675 |
|
| 676 |
cv2.putText(
|
|
|
|
| 703 |
|
| 704 |
logger.info(f"Captured snapshot for {label} violation by worker {worker_id} at {current_time:.2f}s")
|
| 705 |
|
| 706 |
+
# Clear snapshots periodically to reduce memory usage
|
| 707 |
+
if len(snapshots) > 100:
|
| 708 |
+
snapshots = snapshots[-10:] # Keep only the last 10 snapshots in memory
|
| 709 |
+
|
| 710 |
cap.release()
|
| 711 |
processing_time = time.time() - start_time
|
| 712 |
logger.info(f"Processing complete in {processing_time:.2f}s")
|
|
|
|
| 784 |
logger.info(f"Cleaned up temporary video file: {video_path}")
|
| 785 |
except Exception as e:
|
| 786 |
logger.error(f"Failed to clean up temporary video file {video_path}: {e}")
|
| 787 |
+
if device.type == "cuda":
|
| 788 |
+
torch.cuda.empty_cache()
|
| 789 |
|
| 790 |
def gradio_interface(video_file):
|
| 791 |
temp_dir = None
|
|
|
|
| 830 |
if temp_dir and os.path.exists(temp_dir):
|
| 831 |
shutil.rmtree(temp_dir, ignore_errors=True)
|
| 832 |
logger.info(f"Cleaned up temporary directory: {temp_dir}")
|
| 833 |
+
if device.type == "cuda":
|
| 834 |
+
torch.cuda.empty_cache()
|
| 835 |
|
| 836 |
# ========================== # Gradio Interface # ==========================
|
| 837 |
interface = gr.Interface(
|