Sompote commited on
Commit
363a57d
·
verified ·
1 Parent(s): f225817

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +89 -475
app.py CHANGED
@@ -10,13 +10,12 @@ import os
10
  from dotenv import load_dotenv
11
  from collections import defaultdict
12
  import time
13
- from skimage.metrics import structural_similarity as ssim
14
 
15
  # Load environment variables
16
  load_dotenv()
17
 
18
  # Define API endpoint from environment variable
19
- API_URL = os.getenv("API_URL", "http://122.155.170.240:81")
20
  print(f"Using API URL: {API_URL}")
21
  DEFAULT_CONFIDENCE = float(os.getenv("DEFAULT_CONFIDENCE_THRESHOLD", "0.25"))
22
 
@@ -89,159 +88,12 @@ def calculate_movement(prev_center, curr_center, min_movement=10):
89
  except Exception as e:
90
  return False
91
 
92
- def extract_bbox_image(frame, bbox):
93
- """Extract image region from bounding box"""
94
- try:
95
- if frame is None or len(bbox) != 4:
96
- return None
97
-
98
- # Convert bbox to integers and ensure valid coordinates
99
- x1, y1, x2, y2 = map(int, bbox)
100
-
101
- # Handle different bbox formats
102
- if x2 < x1 or y2 < y1: # If it's x,y,w,h format
103
- x1, y1, w, h = bbox
104
- x2, y2 = x1 + w, y1 + h
105
-
106
- # Ensure coordinates are within frame bounds
107
- h, w = frame.shape[:2]
108
- x1 = max(0, min(x1, w-1))
109
- y1 = max(0, min(y1, h-1))
110
- x2 = max(x1+1, min(x2, w))
111
- y2 = max(y1+1, min(y2, h))
112
-
113
- # Extract region
114
- bbox_img = frame[y1:y2, x1:x2]
115
-
116
- # Resize to standard size for comparison (64x64)
117
- if bbox_img.size > 0:
118
- bbox_img = cv2.resize(bbox_img, (64, 64))
119
- return bbox_img
120
- return None
121
- except Exception as e:
122
- return None
123
-
124
- def calculate_histogram_similarity(img1, img2):
125
- """Calculate histogram-based similarity between two images"""
126
- try:
127
- if img1 is None or img2 is None:
128
- return 0.0
129
-
130
- # Convert to HSV for better color comparison
131
- hsv1 = cv2.cvtColor(img1, cv2.COLOR_BGR2HSV)
132
- hsv2 = cv2.cvtColor(img2, cv2.COLOR_BGR2HSV)
133
-
134
- # Calculate histograms
135
- hist1 = cv2.calcHist([hsv1], [0, 1, 2], None, [50, 60, 60], [0, 180, 0, 256, 0, 256])
136
- hist2 = cv2.calcHist([hsv2], [0, 1, 2], None, [50, 60, 60], [0, 180, 0, 256, 0, 256])
137
-
138
- # Compare histograms using correlation
139
- correlation = cv2.compareHist(hist1, hist2, cv2.HISTCMP_CORREL)
140
-
141
- # Normalize to 0-1 range
142
- return max(0, correlation)
143
- except Exception as e:
144
- return 0.0
145
-
146
- def calculate_ssim_similarity(img1, img2):
147
- """Calculate Structural Similarity Index (SSIM) between two images"""
148
- try:
149
- if img1 is None or img2 is None:
150
- return 0.0
151
-
152
- # Convert to grayscale
153
- gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
154
- gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
155
-
156
- # Calculate SSIM
157
- similarity_index = ssim(gray1, gray2)
158
-
159
- # Normalize to 0-1 range (SSIM can be negative)
160
- return max(0, (similarity_index + 1) / 2)
161
- except Exception as e:
162
- return 0.0
163
-
164
- def calculate_feature_similarity(img1, img2):
165
- """Calculate feature-based similarity using ORB features"""
166
- try:
167
- if img1 is None or img2 is None:
168
- return 0.0
169
-
170
- # Convert to grayscale
171
- gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
172
- gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
173
-
174
- # Initialize ORB detector
175
- orb = cv2.ORB_create(nfeatures=50)
176
-
177
- # Find keypoints and descriptors
178
- kp1, des1 = orb.detectAndCompute(gray1, None)
179
- kp2, des2 = orb.detectAndCompute(gray2, None)
180
-
181
- if des1 is None or des2 is None or len(des1) < 5 or len(des2) < 5:
182
- return 0.0
183
-
184
- # Match features
185
- bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
186
- matches = bf.match(des1, des2)
187
-
188
- # Calculate similarity based on good matches
189
- if len(matches) > 0:
190
- # Sort matches by distance
191
- matches = sorted(matches, key=lambda x: x.distance)
192
- good_matches = [m for m in matches if m.distance < 50] # Threshold for good matches
193
-
194
- # Similarity based on ratio of good matches
195
- similarity = len(good_matches) / max(len(kp1), len(kp2))
196
- return min(1.0, similarity)
197
-
198
- return 0.0
199
- except Exception as e:
200
- return 0.0
201
-
202
- def calculate_enhanced_bbox_similarity(bbox1, bbox2, frame1=None, frame2=None):
203
- """Enhanced similarity calculation combining geometric and visual features"""
204
- try:
205
- # Geometric similarity (IoU + distance) - 40% weight
206
- geometric_similarity = calculate_bbox_similarity(bbox1, bbox2)
207
-
208
- # If no frames provided, use only geometric similarity
209
- if frame1 is None or frame2 is None:
210
- return geometric_similarity
211
-
212
- # Extract image regions from bounding boxes
213
- img1 = extract_bbox_image(frame1, bbox1)
214
- img2 = extract_bbox_image(frame2, bbox2)
215
-
216
- if img1 is None or img2 is None:
217
- return geometric_similarity
218
-
219
- # Visual similarity components
220
- hist_similarity = calculate_histogram_similarity(img1, img2) # Color similarity
221
- ssim_similarity = calculate_ssim_similarity(img1, img2) # Structural similarity
222
- feature_similarity = calculate_feature_similarity(img1, img2) # Feature similarity
223
-
224
- # Combine all similarities with weights
225
- final_similarity = (
226
- geometric_similarity * 0.4 + # Geometric (IoU + distance)
227
- hist_similarity * 0.25 + # Color histogram
228
- ssim_similarity * 0.25 + # Structural similarity
229
- feature_similarity * 0.1 # Feature matching
230
- )
231
-
232
- return min(1.0, final_similarity)
233
-
234
- except Exception as e:
235
- return calculate_bbox_similarity(bbox1, bbox2) # Fallback to geometric only
236
-
237
  class TrackedObject:
238
  def __init__(self, obj_id, obj_class, bbox):
239
  self.id = obj_id
240
  self.class_name = obj_class
241
- self.alternative_classes = set() # Track alternative classes (e.g., person when primary is motorcycle)
242
  self.trajectory = [] # List of center points
243
  self.bboxes = [] # List of bounding boxes
244
- self.frame_images = [] # Store recent frame images for visual comparison
245
  self.counted = False
246
  self.last_seen = 0 # Frame number when last seen
247
  self.first_seen = 0 # Frame number when first seen
@@ -251,42 +103,16 @@ class TrackedObject:
251
  self.similarity_scores = [] # Track similarity scores over time
252
  self.add_detection(bbox)
253
 
254
- def update_class(self, new_class):
255
- """Update object class, handling motorcycle+person combinations"""
256
- # Prioritize motorcycle over person (motorcycle with rider)
257
- if self.class_name == 'person' and new_class == 'motorcycle':
258
- self.alternative_classes.add(self.class_name)
259
- self.class_name = new_class
260
- elif self.class_name == 'motorcycle' and new_class == 'person':
261
- self.alternative_classes.add(new_class)
262
- # Keep motorcycle as primary class
263
- elif new_class != self.class_name:
264
- # Different class detected, add to alternatives
265
- self.alternative_classes.add(new_class)
266
-
267
- def get_primary_class(self):
268
- """Get the primary class for counting purposes"""
269
- # Always prioritize motorcycle if it's been detected
270
- if 'motorcycle' in [self.class_name] or 'motorcycle' in self.alternative_classes:
271
- return 'motorcycle'
272
- return self.class_name
273
-
274
- def add_detection(self, bbox, frame_image=None):
275
  try:
276
  center = get_box_center(bbox)
277
  if center is not None:
278
  self.trajectory.append(center)
279
  self.bboxes.append(bbox)
280
-
281
- # Store frame image for visual comparison
282
- if frame_image is not None:
283
- self.frame_images.append(frame_image.copy())
284
-
285
  # Keep only recent history to prevent memory issues
286
  if len(self.trajectory) > 50:
287
  self.trajectory = self.trajectory[-25:]
288
  self.bboxes = self.bboxes[-25:]
289
- self.frame_images = self.frame_images[-25:] if self.frame_images else []
290
  except Exception as e:
291
  pass
292
 
@@ -303,71 +129,32 @@ class TrackedObject:
303
  if is_in_red_zone:
304
  if self.red_zone_entry_frame is None:
305
  self.red_zone_entry_frame = frame_number
306
- # Mark as entered red zone immediately when first detected
307
- return "entered"
308
  self.frames_in_red_zone += 1
309
 
310
- # Check if warning should be triggered using configurable threshold
311
- if self.frames_in_red_zone > state.warning_frame_threshold and not self.warning_triggered:
312
  self.warning_triggered = True
313
- return "warning" # Return warning to indicate warning should be shown
314
  else:
315
  # Object left red zone, reset counters
316
- if self.red_zone_entry_frame is not None:
317
- # Object was in red zone and now left
318
- self.frames_in_red_zone = 0
319
- self.red_zone_entry_frame = None
320
- self.warning_triggered = False
321
- return "exited"
322
 
323
- return None
324
 
325
- def get_similarity_with(self, other_bbox, current_frame=None, similarity_threshold=0.5):
326
- """Calculate enhanced similarity with another bounding box using visual comparison"""
327
  if len(self.bboxes) == 0:
328
  return 0.0
329
 
330
  current_bbox = self.bboxes[-1]
331
-
332
- # Get the most recent frame image for comparison
333
- previous_frame = self.frame_images[-1] if self.frame_images else None
334
-
335
- # Use enhanced similarity calculation with visual comparison
336
- similarity = calculate_enhanced_bbox_similarity(
337
- current_bbox,
338
- other_bbox,
339
- previous_frame,
340
- current_frame
341
- )
342
-
343
- # Store similarity score for debugging
344
- self.similarity_scores.append({
345
- 'frame': state.frame_count,
346
- 'similarity': similarity,
347
- 'bbox': other_bbox,
348
- 'method': 'enhanced' if (previous_frame is not None and current_frame is not None) else 'geometric'
349
- })
350
-
351
- # Keep only recent similarity scores to prevent memory issues
352
- if len(self.similarity_scores) > 20:
353
- self.similarity_scores = self.similarity_scores[-10:]
354
-
355
- return similarity
356
 
357
- def is_similar_object(obj1, obj2, similarity_threshold=0.35):
358
  """Check if two objects are similar based on class, position and bounding box similarity"""
359
  try:
360
- # Allow cross-class matching for motorcycle and person (same object - person on motorcycle)
361
- class1, class2 = obj1['class'], obj2['class']
362
-
363
- # Check if classes are compatible (same class or motorcycle+person combination)
364
- compatible_classes = (
365
- class1 == class2 or # Same class
366
- (class1 == 'motorcycle' and class2 == 'person') or # Person on motorcycle
367
- (class1 == 'person' and class2 == 'motorcycle') # Motorcycle with person
368
- )
369
-
370
- if not compatible_classes:
371
  return False
372
 
373
  box1 = obj1['bbox']
@@ -386,11 +173,6 @@ def is_similar_object(obj1, obj2, similarity_threshold=0.35):
386
  bbox2 = [box2[0], box2[1], box2[0] + box2[2], box2[1] + box2[3]]
387
 
388
  similarity = calculate_bbox_similarity(bbox1, bbox2)
389
-
390
- # Use lower threshold for motorcycle+person combinations
391
- if class1 != class2 and ('motorcycle' in [class1, class2] and 'person' in [class1, class2]):
392
- return similarity > (similarity_threshold * 0.7) # 30% more lenient for cross-class
393
-
394
  return similarity > similarity_threshold
395
  return False
396
  except Exception as e:
@@ -415,12 +197,7 @@ class State:
415
  self.red_zone_passed_objects = defaultdict(int) # Objects that passed through red zone
416
  self.red_zone_warnings = [] # Store warning messages
417
  self.time_window = 10 # Configurable time window for similarity comparison
418
- self.similarity_threshold = 0.35 # Configurable similarity threshold (lowered for better matching)
419
- self.warning_frame_threshold = 3 # Configurable warning threshold (frames in red zone)
420
- # Enhanced red zone tracking
421
- self.red_zone_entered_objects = defaultdict(int) # All objects that entered red zone
422
- self.red_zone_current_objects = defaultdict(list) # Objects currently in red zone
423
- self.red_zone_exited_objects = defaultdict(int) # Objects that exited red zone
424
 
425
  def reset_tracking(self):
426
  """Reset all tracking data"""
@@ -430,10 +207,6 @@ class State:
430
  self.frame_count = 0
431
  self.red_zone_passed_objects = defaultdict(int)
432
  self.red_zone_warnings = []
433
- # Reset enhanced red zone tracking
434
- self.red_zone_entered_objects = defaultdict(int)
435
- self.red_zone_current_objects = defaultdict(list)
436
- self.red_zone_exited_objects = defaultdict(int)
437
 
438
  state = State()
439
 
@@ -718,15 +491,12 @@ def get_segment_index(choice_text):
718
  except:
719
  return -1
720
 
721
- def update_object_tracking(objects_in_area, current_frame=None):
722
  """Update object tracking with new detections"""
723
  try:
724
  current_tracked = set() # Keep track of objects seen in this frame
725
  current_warnings = [] # Collect warnings for this frame
726
 
727
- # Clear current objects list for this frame
728
- state.red_zone_current_objects = defaultdict(list)
729
-
730
  # Match new detections with existing tracked objects
731
  for obj in objects_in_area:
732
  try:
@@ -740,97 +510,57 @@ def update_object_tracking(objects_in_area, current_frame=None):
740
  best_match_id = None
741
  best_similarity = 0.0
742
 
743
- # Try to match with existing tracked objects using cross-class similarity
744
  for obj_id, tracked in state.tracked_objects.items():
745
- # Check if object was seen recently (within time window)
746
- if state.frame_count - tracked.last_seen <= state.time_window:
747
- # Create temporary objects for similarity comparison
748
- temp_obj1 = {'class': tracked.class_name, 'bbox': tracked.bboxes[-1] if tracked.bboxes else bbox}
749
- temp_obj2 = {'class': obj_class, 'bbox': bbox}
750
-
751
- if is_similar_object(temp_obj1, temp_obj2, state.similarity_threshold):
752
- # Use enhanced similarity calculation with visual comparison
753
- similarity = tracked.get_similarity_with(bbox, current_frame)
754
 
755
  # Use the best match above threshold
756
- if similarity > best_similarity:
757
  best_similarity = similarity
758
  best_match_id = obj_id
759
 
760
  # If good match found, update existing object
761
  if best_match_id is not None:
762
  tracked = state.tracked_objects[best_match_id]
763
- tracked.add_detection(bbox, current_frame) # Pass current frame
764
- tracked.update_class(obj_class) # Update class information
765
  tracked.last_seen = state.frame_count
766
  current_tracked.add(best_match_id)
767
  matched = True
768
 
769
- # Check red zone status and handle state changes
770
- zone_status = tracked.update_red_zone_status(is_in_red_zone, state.frame_count)
771
- primary_class = tracked.get_primary_class() # Use primary class for counting
772
-
773
- if zone_status == "entered":
774
- # Object just entered red zone - count it immediately
775
- if not tracked.counted:
776
- tracked.counted = True
777
- state.red_zone_entered_objects[primary_class] += 1
778
-
779
- elif zone_status == "warning":
780
- warning_msg = f"⚠️ WARNING: {primary_class} (ID: {tracked.id}) has been in red zone for {tracked.frames_in_red_zone} frames!"
781
  current_warnings.append(warning_msg)
782
  state.red_zone_warnings.append({
783
  'frame': state.frame_count,
784
  'object_id': tracked.id,
785
- 'class': primary_class,
786
  'frames_in_zone': tracked.frames_in_red_zone,
787
  'message': warning_msg
788
  })
789
-
790
- elif zone_status == "exited":
791
- # Object exited red zone
792
- state.red_zone_exited_objects[primary_class] += 1
793
 
794
- # Add to current objects in red zone if still in zone
795
- if is_in_red_zone:
796
- display_class = primary_class
797
- if tracked.alternative_classes:
798
- display_class += f" ({'+'.join(sorted(tracked.alternative_classes))})"
799
-
800
- state.red_zone_current_objects[primary_class].append({
801
- 'id': tracked.id,
802
- 'frames_in_zone': tracked.frames_in_red_zone,
803
- 'entry_frame': tracked.red_zone_entry_frame,
804
- 'display_class': display_class
805
- })
806
 
807
  # If no match found, create new tracked object
808
  if not matched:
809
  new_obj = TrackedObject(state.next_obj_id, obj_class, bbox)
810
- new_obj.add_detection(bbox, current_frame) # Pass current frame
811
  new_obj.last_seen = state.frame_count
812
  new_obj.first_seen = state.frame_count
813
  state.tracked_objects[state.next_obj_id] = new_obj
814
  current_tracked.add(state.next_obj_id)
 
815
 
816
  # Check red zone status for new object
817
- zone_status = new_obj.update_red_zone_status(is_in_red_zone, state.frame_count)
818
- primary_class = new_obj.get_primary_class()
819
-
820
- if zone_status == "entered":
821
- # New object entered red zone immediately
822
- new_obj.counted = True
823
- state.red_zone_entered_objects[primary_class] += 1
824
-
825
- # Add to current objects in red zone
826
- state.red_zone_current_objects[primary_class].append({
827
- 'id': new_obj.id,
828
- 'frames_in_zone': new_obj.frames_in_red_zone,
829
- 'entry_frame': new_obj.red_zone_entry_frame,
830
- 'display_class': primary_class
831
- })
832
-
833
- state.next_obj_id += 1
834
 
835
  except Exception as e:
836
  continue
@@ -839,21 +569,13 @@ def update_object_tracking(objects_in_area, current_frame=None):
839
  for obj_id, tracked in state.tracked_objects.items():
840
  if obj_id not in current_tracked:
841
  # Object not seen in current frame, update red zone status
842
- zone_status = tracked.update_red_zone_status(False, state.frame_count)
843
- if zone_status == "exited":
844
- # Object exited red zone
845
- primary_class = tracked.get_primary_class()
846
- state.red_zone_exited_objects[primary_class] += 1
847
 
848
  # Remove objects that haven't been seen for a while
849
  if state.frame_count > state.time_window:
850
  to_remove = []
851
  for obj_id, tracked in state.tracked_objects.items():
852
  if state.frame_count - tracked.last_seen > state.time_window * 2: # Remove after 2x time window
853
- # If object was in red zone when lost, count as exited
854
- if tracked.red_zone_entry_frame is not None:
855
- primary_class = tracked.get_primary_class()
856
- state.red_zone_exited_objects[primary_class] += 1
857
  to_remove.append(obj_id)
858
 
859
  for obj_id in to_remove:
@@ -867,73 +589,36 @@ def update_object_tracking(objects_in_area, current_frame=None):
867
  print(f"Error in update_object_tracking: {str(e)}")
868
 
869
  def get_red_zone_summary():
870
- """Generate comprehensive summary of objects in red zone with proper grouping"""
871
  summary = []
872
 
873
- # Header
874
- summary.append("🔴 RED ZONE MONITORING REPORT")
875
- summary.append("=" * 40)
876
-
877
- # Objects that entered red zone (all time)
878
- if state.red_zone_entered_objects:
879
- summary.append("\n📊 OBJECTS ENTERED RED ZONE:")
880
- total_entered = sum(state.red_zone_entered_objects.values())
881
- summary.append(f"Total objects entered: {total_entered}")
882
 
883
- for obj_class, count in sorted(state.red_zone_entered_objects.items()):
884
  summary.append(f" • {obj_class}: {count}")
885
- else:
886
- summary.append("\n📊 OBJECTS ENTERED RED ZONE:")
887
- summary.append("No objects have entered the red zone yet")
888
 
889
- # Objects currently in red zone
890
- current_total = sum(len(objects) for objects in state.red_zone_current_objects.values())
891
- if current_total > 0:
892
- summary.append(f"\n🚨 CURRENTLY IN RED ZONE ({current_total} objects):")
893
-
894
- for obj_class, objects in sorted(state.red_zone_current_objects.items()):
895
- if objects:
896
- summary.append(f" {obj_class} ({len(objects)} objects):")
897
- for obj_info in objects:
898
- display_class = obj_info.get('display_class', obj_class)
899
- summary.append(f" - ID {obj_info['id']}: {obj_info['frames_in_zone']} frames (entered: frame {obj_info['entry_frame']}) [{display_class}]")
900
- else:
901
- summary.append("\n🚨 CURRENTLY IN RED ZONE:")
902
- summary.append("No objects currently in red zone")
903
 
904
- # Objects that exited red zone
905
- if state.red_zone_exited_objects:
906
- summary.append("\n✅ OBJECTS EXITED RED ZONE:")
907
- total_exited = sum(state.red_zone_exited_objects.values())
908
- summary.append(f"Total objects exited: {total_exited}")
909
-
910
- for obj_class, count in sorted(state.red_zone_exited_objects.items()):
911
- summary.append(f" • {obj_class}: {count}")
912
 
913
- # Recent warnings
914
- recent_warnings = [w for w in state.red_zone_warnings if state.frame_count - w['frame'] <= 10]
915
  if recent_warnings:
916
  summary.append("\n⚠️ RECENT WARNINGS:")
917
- for warning in recent_warnings[-5:]: # Show last 5 warnings
918
- summary.append(f" • Frame {warning['frame']}: {warning['class']} (ID: {warning['object_id']}) - {warning['frames_in_zone']} frames in zone")
919
 
920
- # Statistics summary
921
- summary.append(f"\n📈 STATISTICS:")
922
- summary.append(f" • Total unique objects tracked: {len(state.tracked_objects)}")
923
- summary.append(f" • Active warnings: {len([w for w in state.red_zone_warnings if state.frame_count - w['frame'] <= 5])}")
924
- summary.append(f" • Frame: {state.frame_count}")
925
- summary.append(f" • Warning threshold: {state.warning_frame_threshold} frames")
926
-
927
- # Show object combination info
928
- combined_objects = 0
929
- for tracked in state.tracked_objects.values():
930
- if tracked.alternative_classes:
931
- combined_objects += 1
932
-
933
- if combined_objects > 0:
934
- summary.append(f" • Objects with combined detections: {combined_objects}")
935
-
936
- return "\n".join(summary)
937
 
938
  def process_frame(frame, confidence):
939
  """Process a video frame using cached protection area"""
@@ -1017,7 +702,7 @@ def process_frame(frame, confidence):
1017
 
1018
  # Update object tracking
1019
  state.frame_count += 1
1020
- update_object_tracking(objects_in_area, processed_img)
1021
 
1022
  # Cache detections for next frame
1023
  state.previous_detections = objects_in_area
@@ -1128,59 +813,18 @@ def generate_final_summary():
1128
  summary_lines.append(f" • Total frames processed: {state.frame_count}")
1129
  summary_lines.append(f" • Time window used: {state.time_window} frames")
1130
  summary_lines.append(f" • Similarity threshold: {state.similarity_threshold:.2f}")
1131
- summary_lines.append(f" • Warning threshold: {state.warning_frame_threshold} frames")
1132
 
1133
- # Enhanced red zone summary
1134
- if state.red_zone_entered_objects:
1135
- summary_lines.append(f"\n🔴 RED ZONE ANALYSIS:")
1136
- total_entered = sum(state.red_zone_entered_objects.values())
1137
- total_exited = sum(state.red_zone_exited_objects.values())
1138
-
1139
- summary_lines.append(f" • Total objects entered red zone: {total_entered}")
1140
- summary_lines.append(f" • Total objects exited red zone: {total_exited}")
1141
- summary_lines.append(f" • Objects still in red zone: {total_entered - total_exited}")
1142
 
1143
- summary_lines.append(f"\n 📋 BREAKDOWN BY OBJECT CLASS:")
1144
-
1145
- # Combine all object classes that appeared in red zone
1146
- all_classes = set(state.red_zone_entered_objects.keys()) | set(state.red_zone_exited_objects.keys())
1147
-
1148
- for obj_class in sorted(all_classes):
1149
- entered = state.red_zone_entered_objects.get(obj_class, 0)
1150
- exited = state.red_zone_exited_objects.get(obj_class, 0)
1151
- still_in = entered - exited
1152
-
1153
- summary_lines.append(f" {obj_class}:")
1154
- summary_lines.append(f" - Entered: {entered}")
1155
- summary_lines.append(f" - Exited: {exited}")
1156
- summary_lines.append(f" - Still in zone: {still_in}")
1157
  else:
1158
- summary_lines.append(f"\n🔴 RED ZONE ANALYSIS:")
1159
- summary_lines.append(f" • No objects detected in red zone during processing")
1160
-
1161
- # Object combination analysis
1162
- combined_objects = []
1163
- motorcycle_person_combinations = 0
1164
-
1165
- for obj_id, tracked in state.tracked_objects.items():
1166
- if tracked.alternative_classes:
1167
- combo_info = f"ID {obj_id}: {tracked.class_name}"
1168
- if tracked.alternative_classes:
1169
- combo_info += f" + {', '.join(sorted(tracked.alternative_classes))}"
1170
- combined_objects.append(combo_info)
1171
-
1172
- # Count motorcycle+person combinations specifically
1173
- if (tracked.class_name == 'motorcycle' and 'person' in tracked.alternative_classes) or \
1174
- (tracked.class_name == 'person' and 'motorcycle' in tracked.alternative_classes):
1175
- motorcycle_person_combinations += 1
1176
-
1177
- if combined_objects:
1178
- summary_lines.append(f"\n🔗 OBJECT COMBINATIONS DETECTED:")
1179
- summary_lines.append(f" • Total combined detections: {len(combined_objects)}")
1180
- summary_lines.append(f" • Motorcycle+Person combinations: {motorcycle_person_combinations}")
1181
- summary_lines.append(f" • Details:")
1182
- for combo in combined_objects:
1183
- summary_lines.append(f" - {combo}")
1184
 
1185
  # Warning summary
1186
  if state.red_zone_warnings:
@@ -1195,14 +839,14 @@ def generate_final_summary():
1195
  for obj_class, count in sorted(warning_by_class.items()):
1196
  summary_lines.append(f" - {obj_class}: {count} warnings")
1197
 
1198
- # Show detailed warning log
1199
  if len(state.red_zone_warnings) > 0:
1200
- summary_lines.append(f"\n 📋 Warning Log (last 10):")
1201
- for warning in state.red_zone_warnings[-10:]: # Last 10 warnings
1202
  summary_lines.append(f" - Frame {warning['frame']}: {warning['class']} (ID: {warning['object_id']}) - {warning['frames_in_zone']} frames in zone")
1203
  else:
1204
  summary_lines.append(f"\n⚠️ WARNING SUMMARY:")
1205
- summary_lines.append(f" • No warnings generated (no objects stayed in red zone > {state.warning_frame_threshold} frames)")
1206
 
1207
  # Active tracking summary
1208
  total_tracked = len(state.tracked_objects)
@@ -1210,17 +854,15 @@ def generate_final_summary():
1210
  summary_lines.append(f"\n📈 OBJECT TRACKING SUMMARY:")
1211
  summary_lines.append(f" • Total unique objects tracked: {total_tracked}")
1212
 
1213
- # Group by primary class
1214
  objects_by_class = defaultdict(int)
1215
  for obj in state.tracked_objects.values():
1216
- primary_class = obj.get_primary_class()
1217
- objects_by_class[primary_class] += 1
1218
 
1219
  for obj_class, count in sorted(objects_by_class.items()):
1220
  summary_lines.append(f" - {obj_class}: {count}")
1221
 
1222
  summary_lines.append("\n✅ Processing completed successfully!")
1223
- summary_lines.append("\nNote: Objects detected as both motorcycle and person are counted as motorcycle (person riding motorcycle)")
1224
 
1225
  return "\n".join(summary_lines)
1226
 
@@ -1253,7 +895,7 @@ def update_selected_segments(selected):
1253
  state.selected_segments = selected
1254
  return gr.update()
1255
 
1256
- def process_video_wrapper(video, confidence=DEFAULT_CONFIDENCE, target_fps=1, time_window=10, similarity_threshold=0.35, warning_frame_threshold=3):
1257
  """Wrapper around process_video to handle full-size video processing"""
1258
  if video is None:
1259
  yield None, "Please upload a video"
@@ -1263,7 +905,6 @@ def process_video_wrapper(video, confidence=DEFAULT_CONFIDENCE, target_fps=1, ti
1263
  state.reset_tracking()
1264
  state.time_window = time_window
1265
  state.similarity_threshold = similarity_threshold
1266
- state.warning_frame_threshold = warning_frame_threshold
1267
 
1268
  protection_area = []
1269
  if state.selected_segments and state.detected_segments:
@@ -1282,7 +923,7 @@ def process_video_wrapper(video, confidence=DEFAULT_CONFIDENCE, target_fps=1, ti
1282
  return
1283
 
1284
  try:
1285
- yield None, f"🚀 Starting video processing...\n⚙️ Time window: {time_window} frames\n⚙️ Similarity threshold: {similarity_threshold:.2f}\n⚙️ Warning threshold: {warning_frame_threshold} frames"
1286
 
1287
  for frame, status in process_video(video, confidence, target_fps):
1288
  yield frame, status
@@ -1290,32 +931,16 @@ def process_video_wrapper(video, confidence=DEFAULT_CONFIDENCE, target_fps=1, ti
1290
  except Exception as e:
1291
  yield None, f"Error processing video: {str(e)}"
1292
 
1293
- # Enhanced Gradio interface
1294
  with gr.Blocks(title="Enhanced Rail Traffic Monitor") as demo:
1295
  gr.Markdown("""
1296
  # Enhanced Rail Traffic Monitoring System
1297
 
1298
  ## Features:
1299
- - **Smart Object Tracking**: Uses enhanced similarity method combining geometric and visual features
1300
- - **Visual Similarity Comparison**: Compares actual images within bounding boxes using multiple methods
1301
- - **Comprehensive Red Zone Monitoring**: Reports ALL objects entering the red zone
1302
- - **Enhanced Grouping**: Groups objects by class with detailed statistics
1303
- - **Real-time Status**: Shows objects currently in zone, entered, and exited
1304
- - **Configurable Warning System**: Alerts when objects stay in red zone for too long
1305
- - **Configurable Parameters**: Adjust time window, similarity threshold, and warning criteria
1306
-
1307
- ## Enhanced Similarity Methods:
1308
- - **Geometric Similarity** (40%): IoU + center distance
1309
- - **Color Histogram** (25%): HSV color distribution comparison
1310
- - **Structural Similarity** (25%): SSIM for shape and texture
1311
- - **Feature Matching** (10%): ORB keypoint matching
1312
- - **Default Threshold**: 0.35 (more lenient for better object matching)
1313
-
1314
- ## Red Zone Reporting:
1315
- - **Objects Entered**: Total count of all objects that entered the red zone
1316
- - **Currently in Zone**: Real-time list of objects currently in the red zone
1317
- - **Objects Exited**: Count of objects that have left the red zone
1318
- - **Detailed Grouping**: All statistics grouped by object class (train, car, person, etc.)
1319
 
1320
  ## Setup Instructions:
1321
 
@@ -1327,16 +952,14 @@ with gr.Blocks(title="Enhanced Rail Traffic Monitor") as demo:
1327
  1. Click "Extract Protection Area" to automatically detect rail segments
1328
 
1329
  **Processing:**
1330
- 3. Adjust detection confidence, processing frame rate, time window, similarity threshold, and warning threshold
1331
  4. Click "Process Video" to analyze
1332
 
1333
- The system will show comprehensive real-time results including:
1334
- - All objects that entered the red zone (grouped by class)
1335
- - Objects currently in red zone with detailed info
1336
- - Objects that exited the red zone
1337
- - Enhanced tracking with visual similarity comparison
1338
- - Configurable warnings for objects staying too long in red zone
1339
- - Complete tracking statistics
1340
  """)
1341
 
1342
  with gr.Row():
@@ -1373,21 +996,12 @@ with gr.Blocks(title="Enhanced Rail Traffic Monitor") as demo:
1373
  similarity_threshold_slider = gr.Slider(
1374
  minimum=0.1,
1375
  maximum=0.9,
1376
- value=0.35,
1377
  step=0.05,
1378
  label="Similarity Threshold",
1379
  info="Threshold for considering objects as the same (higher = stricter)"
1380
  )
1381
-
1382
- with gr.Row():
1383
- warning_threshold_slider = gr.Slider(
1384
- minimum=1,
1385
- maximum=20,
1386
- value=3,
1387
- step=1,
1388
- label="Warning Frame Threshold",
1389
- info="Number of frames in red zone before triggering warning"
1390
- )
1391
  with gr.Column():
1392
  preview_image = gr.Image(
1393
  label="Click to Select Protection Area (Original Size)",
@@ -1447,7 +1061,7 @@ with gr.Blocks(title="Enhanced Rail Traffic Monitor") as demo:
1447
 
1448
  process_btn.click(
1449
  fn=process_video_wrapper,
1450
- inputs=[video_input, confidence, fps_slider, time_window_slider, similarity_threshold_slider, warning_threshold_slider],
1451
  outputs=[video_output, text_output]
1452
  )
1453
 
 
10
  from dotenv import load_dotenv
11
  from collections import defaultdict
12
  import time
 
13
 
14
  # Load environment variables
15
  load_dotenv()
16
 
17
  # Define API endpoint from environment variable
18
+ API_URL = os.getenv("API_URL", "http://localhost:8000")
19
  print(f"Using API URL: {API_URL}")
20
  DEFAULT_CONFIDENCE = float(os.getenv("DEFAULT_CONFIDENCE_THRESHOLD", "0.25"))
21
 
 
88
  except Exception as e:
89
  return False
90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  class TrackedObject:
92
  def __init__(self, obj_id, obj_class, bbox):
93
  self.id = obj_id
94
  self.class_name = obj_class
 
95
  self.trajectory = [] # List of center points
96
  self.bboxes = [] # List of bounding boxes
 
97
  self.counted = False
98
  self.last_seen = 0 # Frame number when last seen
99
  self.first_seen = 0 # Frame number when first seen
 
103
  self.similarity_scores = [] # Track similarity scores over time
104
  self.add_detection(bbox)
105
 
106
+ def add_detection(self, bbox):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  try:
108
  center = get_box_center(bbox)
109
  if center is not None:
110
  self.trajectory.append(center)
111
  self.bboxes.append(bbox)
 
 
 
 
 
112
  # Keep only recent history to prevent memory issues
113
  if len(self.trajectory) > 50:
114
  self.trajectory = self.trajectory[-25:]
115
  self.bboxes = self.bboxes[-25:]
 
116
  except Exception as e:
117
  pass
118
 
 
129
  if is_in_red_zone:
130
  if self.red_zone_entry_frame is None:
131
  self.red_zone_entry_frame = frame_number
 
 
132
  self.frames_in_red_zone += 1
133
 
134
+ # Check if warning should be triggered
135
+ if self.frames_in_red_zone > 3 and not self.warning_triggered:
136
  self.warning_triggered = True
137
+ return True # Return True to indicate warning should be shown
138
  else:
139
  # Object left red zone, reset counters
140
+ self.frames_in_red_zone = 0
141
+ self.red_zone_entry_frame = None
142
+ self.warning_triggered = False
 
 
 
143
 
144
+ return False
145
 
146
+ def get_similarity_with(self, other_bbox, similarity_threshold=0.5):
147
+ """Calculate similarity with another bounding box"""
148
  if len(self.bboxes) == 0:
149
  return 0.0
150
 
151
  current_bbox = self.bboxes[-1]
152
+ return calculate_bbox_similarity(current_bbox, other_bbox)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
+ def is_similar_object(obj1, obj2, similarity_threshold=0.6):
155
  """Check if two objects are similar based on class, position and bounding box similarity"""
156
  try:
157
+ if obj1['class'] != obj2['class']:
 
 
 
 
 
 
 
 
 
 
158
  return False
159
 
160
  box1 = obj1['bbox']
 
173
  bbox2 = [box2[0], box2[1], box2[0] + box2[2], box2[1] + box2[3]]
174
 
175
  similarity = calculate_bbox_similarity(bbox1, bbox2)
 
 
 
 
 
176
  return similarity > similarity_threshold
177
  return False
178
  except Exception as e:
 
197
  self.red_zone_passed_objects = defaultdict(int) # Objects that passed through red zone
198
  self.red_zone_warnings = [] # Store warning messages
199
  self.time_window = 10 # Configurable time window for similarity comparison
200
+ self.similarity_threshold = 0.6 # Configurable similarity threshold
 
 
 
 
 
201
 
202
  def reset_tracking(self):
203
  """Reset all tracking data"""
 
207
  self.frame_count = 0
208
  self.red_zone_passed_objects = defaultdict(int)
209
  self.red_zone_warnings = []
 
 
 
 
210
 
211
  state = State()
212
 
 
491
  except:
492
  return -1
493
 
494
+ def update_object_tracking(objects_in_area):
495
  """Update object tracking with new detections"""
496
  try:
497
  current_tracked = set() # Keep track of objects seen in this frame
498
  current_warnings = [] # Collect warnings for this frame
499
 
 
 
 
500
  # Match new detections with existing tracked objects
501
  for obj in objects_in_area:
502
  try:
 
510
  best_match_id = None
511
  best_similarity = 0.0
512
 
513
+ # Try to match with existing tracked objects using similarity method
514
  for obj_id, tracked in state.tracked_objects.items():
515
+ if tracked.class_name == obj_class:
516
+ # Check if object was seen recently (within time window)
517
+ if state.frame_count - tracked.last_seen <= state.time_window:
518
+ similarity = tracked.get_similarity_with(bbox)
 
 
 
 
 
519
 
520
  # Use the best match above threshold
521
+ if similarity > state.similarity_threshold and similarity > best_similarity:
522
  best_similarity = similarity
523
  best_match_id = obj_id
524
 
525
  # If good match found, update existing object
526
  if best_match_id is not None:
527
  tracked = state.tracked_objects[best_match_id]
528
+ tracked.add_detection(bbox)
 
529
  tracked.last_seen = state.frame_count
530
  current_tracked.add(best_match_id)
531
  matched = True
532
 
533
+ # Check red zone status and warnings
534
+ warning_triggered = tracked.update_red_zone_status(is_in_red_zone, state.frame_count)
535
+ if warning_triggered:
536
+ warning_msg = f"⚠️ WARNING: {tracked.class_name} (ID: {tracked.id}) has been in red zone for {tracked.frames_in_red_zone} frames!"
 
 
 
 
 
 
 
 
537
  current_warnings.append(warning_msg)
538
  state.red_zone_warnings.append({
539
  'frame': state.frame_count,
540
  'object_id': tracked.id,
541
+ 'class': tracked.class_name,
542
  'frames_in_zone': tracked.frames_in_red_zone,
543
  'message': warning_msg
544
  })
 
 
 
 
545
 
546
+ # Check if object should be counted (only count objects that actually move through the zone)
547
+ if not tracked.counted and tracked.has_movement() and is_in_red_zone:
548
+ # Additional check: object should have been tracked for at least a few frames
549
+ if len(tracked.trajectory) >= 3:
550
+ tracked.counted = True
551
+ state.red_zone_passed_objects[obj_class] += 1
 
 
 
 
 
 
552
 
553
  # If no match found, create new tracked object
554
  if not matched:
555
  new_obj = TrackedObject(state.next_obj_id, obj_class, bbox)
 
556
  new_obj.last_seen = state.frame_count
557
  new_obj.first_seen = state.frame_count
558
  state.tracked_objects[state.next_obj_id] = new_obj
559
  current_tracked.add(state.next_obj_id)
560
+ state.next_obj_id += 1
561
 
562
  # Check red zone status for new object
563
+ new_obj.update_red_zone_status(is_in_red_zone, state.frame_count)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
 
565
  except Exception as e:
566
  continue
 
569
  for obj_id, tracked in state.tracked_objects.items():
570
  if obj_id not in current_tracked:
571
  # Object not seen in current frame, update red zone status
572
+ tracked.update_red_zone_status(False, state.frame_count)
 
 
 
 
573
 
574
  # Remove objects that haven't been seen for a while
575
  if state.frame_count > state.time_window:
576
  to_remove = []
577
  for obj_id, tracked in state.tracked_objects.items():
578
  if state.frame_count - tracked.last_seen > state.time_window * 2: # Remove after 2x time window
 
 
 
 
579
  to_remove.append(obj_id)
580
 
581
  for obj_id in to_remove:
 
589
  print(f"Error in update_object_tracking: {str(e)}")
590
 
591
  def get_red_zone_summary():
592
+ """Generate summary of objects that passed through red zone"""
593
  summary = []
594
 
595
+ if state.red_zone_passed_objects:
596
+ summary.append("🔴 RED ZONE PASSAGE SUMMARY:")
597
+ total_objects = sum(state.red_zone_passed_objects.values())
598
+ summary.append(f"Total objects passed: {total_objects}")
 
 
 
 
 
599
 
600
+ for obj_class, count in sorted(state.red_zone_passed_objects.items()):
601
  summary.append(f" • {obj_class}: {count}")
 
 
 
602
 
603
+ # Add current objects in red zone
604
+ current_in_zone = []
605
+ for obj_id, tracked in state.tracked_objects.items():
606
+ if tracked.frames_in_red_zone > 0:
607
+ current_in_zone.append(f"{tracked.class_name} (ID: {tracked.id}, {tracked.frames_in_red_zone} frames)")
 
 
 
 
 
 
 
 
 
608
 
609
+ if current_in_zone:
610
+ summary.append("\n🚨 CURRENTLY IN RED ZONE:")
611
+ for obj_info in current_in_zone:
612
+ summary.append(f" • {obj_info}")
 
 
 
 
613
 
614
+ # Add recent warnings
615
+ recent_warnings = [w for w in state.red_zone_warnings if state.frame_count - w['frame'] <= 5]
616
  if recent_warnings:
617
  summary.append("\n⚠️ RECENT WARNINGS:")
618
+ for warning in recent_warnings[-3:]: # Show last 3 warnings
619
+ summary.append(f" • Frame {warning['frame']}: {warning['message']}")
620
 
621
+ return "\n".join(summary) if summary else "No objects detected in red zone yet."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
622
 
623
  def process_frame(frame, confidence):
624
  """Process a video frame using cached protection area"""
 
702
 
703
  # Update object tracking
704
  state.frame_count += 1
705
+ update_object_tracking(objects_in_area)
706
 
707
  # Cache detections for next frame
708
  state.previous_detections = objects_in_area
 
813
  summary_lines.append(f" • Total frames processed: {state.frame_count}")
814
  summary_lines.append(f" • Time window used: {state.time_window} frames")
815
  summary_lines.append(f" • Similarity threshold: {state.similarity_threshold:.2f}")
 
816
 
817
+ # Red zone passage summary
818
+ if state.red_zone_passed_objects:
819
+ summary_lines.append(f"\n🔴 RED ZONE PASSAGE SUMMARY:")
820
+ total_passed = sum(state.red_zone_passed_objects.values())
821
+ summary_lines.append(f" • Total objects passed through red zone: {total_passed}")
 
 
 
 
822
 
823
+ for obj_class, count in sorted(state.red_zone_passed_objects.items()):
824
+ summary_lines.append(f" - {obj_class}: {count}")
 
 
 
 
 
 
 
 
 
 
 
 
825
  else:
826
+ summary_lines.append(f"\n🔴 RED ZONE PASSAGE SUMMARY:")
827
+ summary_lines.append(f" • No objects detected passing through red zone")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
828
 
829
  # Warning summary
830
  if state.red_zone_warnings:
 
839
  for obj_class, count in sorted(warning_by_class.items()):
840
  summary_lines.append(f" - {obj_class}: {count} warnings")
841
 
842
+ # Show last few warnings
843
  if len(state.red_zone_warnings) > 0:
844
+ summary_lines.append(f"\n 📋 Recent warnings:")
845
+ for warning in state.red_zone_warnings[-5:]: # Last 5 warnings
846
  summary_lines.append(f" - Frame {warning['frame']}: {warning['class']} (ID: {warning['object_id']}) - {warning['frames_in_zone']} frames in zone")
847
  else:
848
  summary_lines.append(f"\n⚠️ WARNING SUMMARY:")
849
+ summary_lines.append(f" • No warnings generated (no objects stayed in red zone > 3 frames)")
850
 
851
  # Active tracking summary
852
  total_tracked = len(state.tracked_objects)
 
854
  summary_lines.append(f"\n📈 OBJECT TRACKING SUMMARY:")
855
  summary_lines.append(f" • Total unique objects tracked: {total_tracked}")
856
 
857
+ # Group by class
858
  objects_by_class = defaultdict(int)
859
  for obj in state.tracked_objects.values():
860
+ objects_by_class[obj.class_name] += 1
 
861
 
862
  for obj_class, count in sorted(objects_by_class.items()):
863
  summary_lines.append(f" - {obj_class}: {count}")
864
 
865
  summary_lines.append("\n✅ Processing completed successfully!")
 
866
 
867
  return "\n".join(summary_lines)
868
 
 
895
  state.selected_segments = selected
896
  return gr.update()
897
 
898
+ def process_video_wrapper(video, confidence=DEFAULT_CONFIDENCE, target_fps=1, time_window=10, similarity_threshold=0.6):
899
  """Wrapper around process_video to handle full-size video processing"""
900
  if video is None:
901
  yield None, "Please upload a video"
 
905
  state.reset_tracking()
906
  state.time_window = time_window
907
  state.similarity_threshold = similarity_threshold
 
908
 
909
  protection_area = []
910
  if state.selected_segments and state.detected_segments:
 
923
  return
924
 
925
  try:
926
+ yield None, f"🚀 Starting video processing...\n⚙️ Time window: {time_window} frames\n⚙️ Similarity threshold: {similarity_threshold:.2f}"
927
 
928
  for frame, status in process_video(video, confidence, target_fps):
929
  yield frame, status
 
931
  except Exception as e:
932
  yield None, f"Error processing video: {str(e)}"
933
 
934
+ # Update the Gradio interface
935
  with gr.Blocks(title="Enhanced Rail Traffic Monitor") as demo:
936
  gr.Markdown("""
937
  # Enhanced Rail Traffic Monitoring System
938
 
939
  ## Features:
940
+ - **Smart Object Tracking**: Uses similarity method to track objects across frames
941
+ - **Red Zone Monitoring**: Counts objects passing through the red zone
942
+ - **Warning System**: Alerts when objects stay in red zone for more than 3 frames
943
+ - **Configurable Parameters**: Adjust time window and similarity threshold
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
944
 
945
  ## Setup Instructions:
946
 
 
952
  1. Click "Extract Protection Area" to automatically detect rail segments
953
 
954
  **Processing:**
955
+ 3. Adjust detection confidence, processing frame rate, time window, and similarity threshold
956
  4. Click "Process Video" to analyze
957
 
958
+ The system will show real-time results including:
959
+ - Objects currently in red zone
960
+ - Total count of objects that passed through
961
+ - Warnings for objects staying too long in red zone
962
+ - Tracking statistics
 
 
963
  """)
964
 
965
  with gr.Row():
 
996
  similarity_threshold_slider = gr.Slider(
997
  minimum=0.1,
998
  maximum=0.9,
999
+ value=0.6,
1000
  step=0.05,
1001
  label="Similarity Threshold",
1002
  info="Threshold for considering objects as the same (higher = stricter)"
1003
  )
1004
+
 
 
 
 
 
 
 
 
 
1005
  with gr.Column():
1006
  preview_image = gr.Image(
1007
  label="Click to Select Protection Area (Original Size)",
 
1061
 
1062
  process_btn.click(
1063
  fn=process_video_wrapper,
1064
+ inputs=[video_input, confidence, fps_slider, time_window_slider, similarity_threshold_slider],
1065
  outputs=[video_output, text_output]
1066
  )
1067