Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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(*'
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 507 |
inter_l1 = segment_intersects(prev_pt, current_pt, L1_p1, L1_p2)
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 518 |
crossed_l1_flag[tid] = True
|
|
|
|
| 519 |
|
| 520 |
-
#
|
|
|
|
| 521 |
inter_l2 = segment_intersects(prev_pt, current_pt, L2_p1, L2_p2)
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 677 |
-
last_exit_vid[tid] =
|
| 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 |
-
|
|
|
|
| 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
|
| 785 |
-
if
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
|
| 790 |
-
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
|
| 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': "
|
| 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,
|