mlbench123 commited on
Commit
ddecbf3
·
verified ·
1 Parent(s): 2d7e9ec

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +165 -45
app.py CHANGED
@@ -18,6 +18,15 @@ from scipy.ndimage import gaussian_filter1d
18
  from ultralytics import YOLO
19
  import os
20
 
 
 
 
 
 
 
 
 
 
21
  # ---------------- Points in image (given) - adjust if needed
22
  A = (440.0, 829.0)
23
  B = (883.0, 928.0)
@@ -288,10 +297,12 @@ def process_video(
288
  prev_foot = {} # {id: (x,y)} previous foot coordinate (image space)
289
  crossed_l1_flag = {} # {id: bool} whether this id has crossed L1 (in required direction) and not yet used to count
290
  crossed_l2_counted = {} # {id: bool} whether this id has already triggered the global count by crossing L2 after L1
 
 
291
 
292
  global_counter = 0 # counts completed L1->L2 sequences
293
  completed_times = [] # for avg time taken
294
-
295
  cap = cv2.VideoCapture(input_video_path)
296
  if not cap.isOpened():
297
  raise RuntimeError("Cannot open input video: " + input_video_path)
@@ -302,8 +313,10 @@ def process_video(
302
 
303
  out_w = width + RIGHT_PANEL_W
304
  out_h = height
305
- fourcc = cv2.VideoWriter_fourcc(*'MJPG')
 
306
  writer = cv2.VideoWriter(output_video_path, fourcc, fps, (out_w, out_h))
 
307
  if not writer.isOpened():
308
  raise RuntimeError("Failed to open VideoWriter. Try different codec or path.")
309
 
@@ -502,39 +515,130 @@ def process_video(
502
  current_pt = (fx, fy)
503
 
504
  # Crossing detection for L1
505
- if prev_pt is not None:
506
- # check intersection with L1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
507
  inter_l1 = segment_intersects(prev_pt, current_pt, L1_p1, L1_p2)
508
- if inter_l1:
509
- # check direction: we want prev_sign != curr_sign and curr_sign == inside sign
510
- prev_sign = np.sign(signed_dist_to_line(prev_pt, L1_coeff))
511
- curr_sign = np.sign(signed_dist_to_line(current_pt, L1_coeff))
512
- if prev_sign == 0:
513
- prev_sign = -curr_sign if curr_sign != 0 else 1.0
514
- if curr_sign == 0:
515
- curr_sign = prev_sign
516
- if prev_sign != curr_sign and curr_sign == L1_inside_sign:
517
- # crossed L1 in correct direction (outside -> inside)
 
 
 
 
 
 
 
 
 
518
  crossed_l1_flag[tid] = True
 
519
 
520
- # check intersection with L2
 
521
  inter_l2 = segment_intersects(prev_pt, current_pt, L2_p1, L2_p2)
522
- if inter_l2:
523
- prev_sign = np.sign(signed_dist_to_line(prev_pt, L2_coeff))
524
- curr_sign = np.sign(signed_dist_to_line(current_pt, L2_coeff))
525
- if prev_sign == 0:
526
- prev_sign = -curr_sign if curr_sign != 0 else 1.0
527
- if curr_sign == 0:
528
- curr_sign = prev_sign
529
- if prev_sign != curr_sign and curr_sign == L2_inside_sign:
530
- # crossed L2 in correct direction; if previously crossed L1 and not yet counted => count
531
- if crossed_l1_flag.get(tid, False) and not crossed_l2_counted.get(tid, False):
532
- global_counter += 1
533
- crossed_l2_counted[tid] = True
534
- # once person completed crossing sequence, we keep their travel/time records intact
535
- # update prev_foot
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
536
  prev_foot[tid] = current_pt
537
 
 
538
  if inside and not prev:
539
  inside_state[tid] = True
540
  if tid not in entry_time:
@@ -673,9 +777,17 @@ def process_video(
673
  inside_state[tid] = False
674
  if tid in entry_time:
675
  accumulated_time[tid] += now - entry_time[tid]
676
- # record last exit time in video seconds (use last seen time)
677
- last_exit_vid[tid] = ls - start_time
678
  completed_times.append(accumulated_time[tid])
 
 
 
 
 
 
 
 
679
  entry_time.pop(tid, None)
680
  else:
681
  # within occlusion grace window -> keep inside state
@@ -771,8 +883,17 @@ def process_video(
771
  end_t = time.time()
772
  for tid in list(entry_time.keys()):
773
  accumulated_time[tid] += end_t - entry_time[tid]
774
- last_exit_vid[tid] = last_seen.get(tid, end_t) - start_time
 
775
  completed_times.append(accumulated_time[tid])
 
 
 
 
 
 
 
 
776
  entry_time.pop(tid, None)
777
  inside_state[tid] = False
778
 
@@ -780,19 +901,18 @@ def process_video(
780
  writer.release()
781
 
782
  # export excel (only >0)
 
783
  rows = []
784
- for tid, tot in accumulated_time.items():
785
- if tot <= 0:
786
- continue
787
- tin = first_entry_vid.get(tid, 0.0)
788
- tout = last_exit_vid.get(tid, tin)
789
- rows.append({
790
- "Passenger": int(tid),
791
- "Time in": fmt(tin),
792
- "Time out": fmt(tout),
793
- "Time in queue (seconds)": round(float(tot), 2)
794
- })
795
- df = pd.DataFrame(rows, columns=["Passenger","Time in","Time out","Time in queue (seconds)"])
796
  if len(df) > 0:
797
  df.to_excel("person_times.xlsx", index=False)
798
  else:
@@ -805,7 +925,7 @@ def process_video(
805
  if __name__ == "__main__":
806
  CONFIG = {
807
  'input_video_path': "sample_vid.mp4",
808
- 'output_video_path': "output22.avi",
809
  'model_name': "yolo11x.pt",
810
  'head_model_name': "head_detection_single_video_best.pt",
811
  'conf_threshold': 0.3,
 
18
  from ultralytics import YOLO
19
  import os
20
 
21
+ import platform
22
+ import sys
23
+
24
+ # Mac-specific optimizations
25
+ if platform.system() == "Darwin":
26
+ import os
27
+ os.environ['PYTORCH_ENABLE_MPS_FALLBACK'] = '1'
28
+ os.environ['OMP_NUM_THREADS'] = '1'
29
+
30
  # ---------------- Points in image (given) - adjust if needed
31
  A = (440.0, 829.0)
32
  B = (883.0, 928.0)
 
297
  prev_foot = {} # {id: (x,y)} previous foot coordinate (image space)
298
  crossed_l1_flag = {} # {id: bool} whether this id has crossed L1 (in required direction) and not yet used to count
299
  crossed_l2_counted = {} # {id: bool} whether this id has already triggered the global count by crossing L2 after L1
300
+ prev_l1_dist = {} # Track distance to L1
301
+ prev_l2_dist = {} # Track distance to L2
302
 
303
  global_counter = 0 # counts completed L1->L2 sequences
304
  completed_times = [] # for avg time taken
305
+ sequential_entries = []
306
  cap = cv2.VideoCapture(input_video_path)
307
  if not cap.isOpened():
308
  raise RuntimeError("Cannot open input video: " + input_video_path)
 
313
 
314
  out_w = width + RIGHT_PANEL_W
315
  out_h = height
316
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v') # or 'H264' or 'avc1'
317
+ output_video_path = "output23.mp4" # Must be .mp4 extension
318
  writer = cv2.VideoWriter(output_video_path, fourcc, fps, (out_w, out_h))
319
+
320
  if not writer.isOpened():
321
  raise RuntimeError("Failed to open VideoWriter. Try different codec or path.")
322
 
 
515
  current_pt = (fx, fy)
516
 
517
  # Crossing detection for L1
518
+ # if prev_pt is not None:
519
+ # # check intersection with L1
520
+ # inter_l1 = segment_intersects(prev_pt, current_pt, L1_p1, L1_p2)
521
+ # if inter_l1:
522
+ # # check direction: we want prev_sign != curr_sign and curr_sign == inside sign
523
+ # prev_sign = np.sign(signed_dist_to_line(prev_pt, L1_coeff))
524
+ # curr_sign = np.sign(signed_dist_to_line(current_pt, L1_coeff))
525
+ # if prev_sign == 0:
526
+ # prev_sign = -curr_sign if curr_sign != 0 else 1.0
527
+ # if curr_sign == 0:
528
+ # curr_sign = prev_sign
529
+ # if prev_sign != curr_sign and curr_sign == L1_inside_sign:
530
+ # # crossed L1 in correct direction (outside -> inside)
531
+ # crossed_l1_flag[tid] = True
532
+
533
+ # # check intersection with L2
534
+ # inter_l2 = segment_intersects(prev_pt, current_pt, L2_p1, L2_p2)
535
+ # if inter_l2:
536
+ # prev_sign = np.sign(signed_dist_to_line(prev_pt, L2_coeff))
537
+ # curr_sign = np.sign(signed_dist_to_line(current_pt, L2_coeff))
538
+ # if prev_sign == 0:
539
+ # prev_sign = -curr_sign if curr_sign != 0 else 1.0
540
+ # if curr_sign == 0:
541
+ # curr_sign = prev_sign
542
+ # if prev_sign != curr_sign and curr_sign == L2_inside_sign:
543
+ # # crossed L2 in correct direction; if previously crossed L1 and not yet counted => count
544
+ # if crossed_l1_flag.get(tid, False) and not crossed_l2_counted.get(tid, False):
545
+ # global_counter += 1
546
+ # crossed_l2_counted[tid] = True
547
+ # # Record the sequential entry
548
+ # entry_vid_time = first_entry_vid.get(tid, vid_seconds)
549
+ # sequential_entries.append({
550
+ # 'person_num': global_counter,
551
+ # 'tid': tid,
552
+ # 'entry_time': entry_vid_time,
553
+ # 'exit_time': None,
554
+ # 'duration': None
555
+ # })
556
+ # # once person completed crossing sequence, we keep their travel/time records intact
557
+ # update prev_foot
558
+ # prev_foot[tid] = current_pt
559
+
560
+
561
+ # maintain prev_foot for intersection tests
562
+ prev_pt = prev_foot.get(tid, None)
563
+ current_pt = (fx, fy)
564
+
565
+ # Calculate signed distances to both lines
566
+ curr_l1_dist = signed_dist_to_line(current_pt, L1_coeff)
567
+ curr_l2_dist = signed_dist_to_line(current_pt, L2_coeff)
568
+
569
+ # Robust crossing detection
570
+ if prev_pt is not None and tid in prev_l1_dist and tid in prev_l2_dist:
571
+ prev_l1 = prev_l1_dist[tid]
572
+ prev_l2 = prev_l2_dist[tid]
573
+
574
+ # === L1 CROSSING (3 detection methods) ===
575
+ # Method 1: Segment intersection (current method)
576
  inter_l1 = segment_intersects(prev_pt, current_pt, L1_p1, L1_p2)
577
+
578
+ # Method 2: Sign change in distance
579
+ prev_sign_l1 = np.sign(prev_l1)
580
+ curr_sign_l1 = np.sign(curr_l1_dist)
581
+ if prev_sign_l1 == 0:
582
+ prev_sign_l1 = 1.0
583
+ if curr_sign_l1 == 0:
584
+ curr_sign_l1 = prev_sign_l1
585
+ sign_change_l1 = (prev_sign_l1 != curr_sign_l1)
586
+ correct_dir_l1 = (curr_sign_l1 == L1_inside_sign)
587
+
588
+ # Method 3: Close proximity check (catches near-misses)
589
+ close_to_l1 = abs(curr_l1_dist) < 40 # within 40 pixels
590
+ was_far_l1 = abs(prev_l1) > 20 # was at least 20 pixels away
591
+ moving_toward_l1 = abs(curr_l1_dist) < abs(prev_l1) # getting closer
592
+
593
+ # Trigger L1 crossing if ANY method detects it
594
+ if (inter_l1 or (sign_change_l1 and correct_dir_l1)):
595
+ if inside and not crossed_l1_flag.get(tid, False):
596
  crossed_l1_flag[tid] = True
597
+ print(f"L1 crossed by ID {tid}")
598
 
599
+ # === L2 CROSSING (3 detection methods) ===
600
+ # Method 1: Segment intersection
601
  inter_l2 = segment_intersects(prev_pt, current_pt, L2_p1, L2_p2)
602
+
603
+ # Method 2: Sign change in distance
604
+ prev_sign_l2 = np.sign(prev_l2)
605
+ curr_sign_l2 = np.sign(curr_l2_dist)
606
+ if prev_sign_l2 == 0:
607
+ prev_sign_l2 = 1.0
608
+ if curr_sign_l2 == 0:
609
+ curr_sign_l2 = prev_sign_l2
610
+ sign_change_l2 = (prev_sign_l2 != curr_sign_l2)
611
+ correct_dir_l2 = (curr_sign_l2 == L2_inside_sign)
612
+
613
+ # Method 3: Close proximity check
614
+ close_to_l2 = abs(curr_l2_dist) < 40
615
+ was_far_l2 = abs(prev_l2) > 20
616
+ moving_toward_l2 = abs(curr_l2_dist) < abs(prev_l2)
617
+
618
+ # Trigger L2 crossing if ANY method detects it
619
+ if (inter_l2 or
620
+ (sign_change_l2 and correct_dir_l2)):
621
+ # Count only if L1 was already crossed and not yet counted
622
+ if inside and crossed_l1_flag.get(tid, False) and not crossed_l2_counted.get(tid, False):
623
+ global_counter += 1
624
+ crossed_l2_counted[tid] = True
625
+ print(f"✓ COUNTED: ID {tid} | Global count now: {global_counter}")
626
+
627
+ entry_vid_time = first_entry_vid.get(tid, vid_seconds)
628
+ sequential_entries.append({
629
+ 'person_num': global_counter,
630
+ 'tid': tid,
631
+ 'entry_time': entry_vid_time,
632
+ 'exit_time': None,
633
+ 'duration': None
634
+ })
635
+
636
+ # Update distance tracking for next frame
637
+ prev_l1_dist[tid] = curr_l1_dist
638
+ prev_l2_dist[tid] = curr_l2_dist
639
  prev_foot[tid] = current_pt
640
 
641
+
642
  if inside and not prev:
643
  inside_state[tid] = True
644
  if tid not in entry_time:
 
777
  inside_state[tid] = False
778
  if tid in entry_time:
779
  accumulated_time[tid] += now - entry_time[tid]
780
+ exit_vid_time = ls - start_time
781
+ last_exit_vid[tid] = exit_vid_time
782
  completed_times.append(accumulated_time[tid])
783
+
784
+ # Update sequential entry exit time
785
+ for entry in sequential_entries:
786
+ if entry['tid'] == tid and entry['exit_time'] is None:
787
+ entry['exit_time'] = exit_vid_time
788
+ entry['duration'] = accumulated_time[tid]
789
+ break
790
+
791
  entry_time.pop(tid, None)
792
  else:
793
  # within occlusion grace window -> keep inside state
 
883
  end_t = time.time()
884
  for tid in list(entry_time.keys()):
885
  accumulated_time[tid] += end_t - entry_time[tid]
886
+ exit_vid_time = last_seen.get(tid, end_t) - start_time
887
+ last_exit_vid[tid] = exit_vid_time
888
  completed_times.append(accumulated_time[tid])
889
+
890
+ # Update sequential entry exit time
891
+ for entry in sequential_entries:
892
+ if entry['tid'] == tid and entry['exit_time'] is None:
893
+ entry['exit_time'] = exit_vid_time
894
+ entry['duration'] = accumulated_time[tid]
895
+ break
896
+
897
  entry_time.pop(tid, None)
898
  inside_state[tid] = False
899
 
 
901
  writer.release()
902
 
903
  # export excel (only >0)
904
+ # export excel with sequential person numbers
905
  rows = []
906
+ for entry in sequential_entries:
907
+ if entry['exit_time'] is not None and entry['duration'] is not None and entry['duration'] > 0:
908
+ rows.append({
909
+ "Person": entry['person_num'],
910
+ "Time in": fmt(entry['entry_time']),
911
+ "Time out": fmt(entry['exit_time']),
912
+ "Time in queue (seconds)": round(float(entry['duration']), 2)
913
+ })
914
+
915
+ df = pd.DataFrame(rows, columns=["Person","Time in","Time out","Time in queue (seconds)"])
 
 
916
  if len(df) > 0:
917
  df.to_excel("person_times.xlsx", index=False)
918
  else:
 
925
  if __name__ == "__main__":
926
  CONFIG = {
927
  'input_video_path': "sample_vid.mp4",
928
+ 'output_video_path': "output23.avi",
929
  'model_name': "yolo11x.pt",
930
  'head_model_name': "head_detection_single_video_best.pt",
931
  'conf_threshold': 0.3,