isambalghari commited on
Commit
bc4df63
Β·
1 Parent(s): 4f42968

editing tracker

Browse files
full_tracked_output.mp4 CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:0a40a50c5a05f63a00cefd584874fa87e2346d89d6e5c40f5065c2000af0453f
3
- size 1343470
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d2279854fb8576c214c8c149b62a085ae6fd17407d51cde7be57d8ae6a016b9d
3
+ size 1421621
mask_output.mp4 CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:7fa9d49423d0da4aad10d35e12f8b1f672d27607dbf2c9a9d64bf526b66afc12
3
- size 363292
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d606f04cd29d4d71677020403774296c6cfbaf6b9151423eef8f982eecf2bf6f
3
+ size 432686
stabilized_mask_output.mp4 CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:36201117ba3818d6d3356312485bc62a64a0f7f709dd9fbdbb1f9e856465a48c
3
- size 953497
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9d6392f0c333a98e46aa72e81651cc9785bb0360db1b55c25d7485f34bb542a8
3
+ size 1009136
track-pixels_gradio.py CHANGED
@@ -449,16 +449,49 @@ REVERSED_INPUT = "/app/reversed_input.mp4"
449
  # === VIDEO UTILITIES =====================================
450
  # ==========================================================
451
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  def reverse_video(input_path, output_path):
 
 
 
 
453
  cap = cv2.VideoCapture(input_path)
454
  if not cap.isOpened():
455
  raise FileNotFoundError(f"❌ Could not open video: {input_path}")
456
-
457
  fps = cap.get(cv2.CAP_PROP_FPS)
458
  width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
459
  height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
460
  fourcc = cv2.VideoWriter_fourcc(*'mp4v')
461
- out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
462
 
463
  frames = []
464
  while True:
@@ -468,16 +501,27 @@ def reverse_video(input_path, output_path):
468
  frames.append(frame)
469
  cap.release()
470
 
 
 
 
 
471
  for frame in reversed(frames):
472
  out.write(frame)
473
  out.release()
474
- print(f"πŸ” Video reversed and saved: {output_path}")
 
 
475
  return output_path
476
 
 
477
  def reverse_video_file_inplace(path_in):
 
 
 
478
  tmp_path = path_in.replace(".mp4", "_tmp.mp4")
479
  reverse_video(path_in, tmp_path)
480
  os.replace(tmp_path, path_in)
 
481
 
482
  # ==========================================================
483
  # === RAFT LOADING =========================================
@@ -642,20 +686,145 @@ def stabilize_black_regions(input_video):
642
  # === TRACKING =============================================
643
  # ==========================================================
644
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
645
  def run_tracking(video_path, mask_path, selection_mode="All Pixels"):
646
  BLACK_THRESH = 1
647
  HISTORY_LEN = 5
648
 
 
649
  reversed_path = reverse_video(video_path, REVERSED_INPUT)
650
  cap = cv2.VideoCapture(reversed_path)
651
  model = load_raft_model(MODEL_PATH)
652
 
653
  fps = cap.get(cv2.CAP_PROP_FPS)
 
 
 
654
  ret, first_frame = cap.read()
655
  if not ret:
656
  return "❌ Could not read first frame.", None, None, None
657
  H, W = first_frame.shape[:2]
658
 
 
659
  x0, y0, x1, y1 = compute_crop_box_from_mask_dynamic(first_frame, mask_path, pad=200)
660
  cw, ch = x1 - x0, y1 - y0
661
 
@@ -681,8 +850,10 @@ def run_tracking(video_path, mask_path, selection_mode="All Pixels"):
681
 
682
  history = deque([True]*HISTORY_LEN, maxlen=HISTORY_LEN)
683
  stopped = False
684
-
685
  frame_idx = 0
 
 
 
686
  while True:
687
  ret, curr_frame = cap.read()
688
  if not ret:
@@ -692,11 +863,13 @@ def run_tracking(video_path, mask_path, selection_mode="All Pixels"):
692
  curr_crop_rgb = curr_full_rgb[y0:y1, x0:x1]
693
  gray_crop = cv2.cvtColor(curr_crop_rgb, cv2.COLOR_RGB2GRAY)
694
 
 
695
  flow_crop = compute_flow(model, prev_crop_rgb, curr_crop_rgb)
696
 
697
  vis_full = curr_full_rgb.copy()
698
  mask_full = np.full((H, W), 255, dtype=np.uint8)
699
 
 
700
  new_points = []
701
  for pt in tracked_points:
702
  px, py = int(pt[0]), int(pt[1])
@@ -708,6 +881,7 @@ def run_tracking(video_path, mask_path, selection_mode="All Pixels"):
708
  new_points.append([nx, ny])
709
  tracked_points = np.array(new_points, dtype=np.float32)
710
 
 
711
  black_mask = gray_crop < BLACK_THRESH
712
  black_indices = tracked_points.astype(int)
713
  has_black = any(
@@ -716,16 +890,18 @@ def run_tracking(video_path, mask_path, selection_mode="All Pixels"):
716
  )
717
  history.append(has_black)
718
 
 
719
  if stopped:
720
  paint = False
721
  elif has_black:
722
  paint = True
723
- elif not any(history):
724
  stopped = True
725
  paint = False
726
  else:
727
  paint = True
728
 
 
729
  if paint:
730
  for pt in tracked_points:
731
  fx, fy = int(pt[0] + x0), int(pt[1] + y0)
@@ -740,21 +916,37 @@ def run_tracking(video_path, mask_path, selection_mode="All Pixels"):
740
  if frame_idx % 10 == 0:
741
  print(f"Frame {frame_idx}: {'PAINT' if paint else 'NO-PAINT'} | has_black={has_black} | stopped={stopped}")
742
 
 
 
 
 
 
 
 
 
 
743
  cap.release()
744
  out_vis.release()
745
  out_mask.release()
746
 
 
747
  stabilize_black_regions(OUTPUT_MASK_VIDEO)
748
-
749
  reverse_video_file_inplace(OUTPUT_VIDEO)
750
  reverse_video_file_inplace(OUTPUT_MASK_VIDEO)
751
  reverse_video_file_inplace(STABILIZED_MASK)
752
 
 
 
 
 
 
 
 
753
  return (
754
  f"βœ… Tracking complete ({selection_mode}).\n"
755
- f"Square Crop {cw}x{ch} @ ({x0},{y0}) with padding=100\n"
756
- f"Painting stopped={'Yes' if stopped else 'No'} after {frame_idx} frames.\n"
757
- "Saved outputs reversed back to forward order.",
758
  OUTPUT_VIDEO,
759
  OUTPUT_MASK_VIDEO,
760
  STABILIZED_MASK
@@ -818,4 +1010,4 @@ def build_app():
818
 
819
  if __name__ == "__main__":
820
  app = build_app()
821
- app.launch(server_name="0.0.0.0", server_port=7860, debug=True)
 
449
  # === VIDEO UTILITIES =====================================
450
  # ==========================================================
451
 
452
+ # def reverse_video(input_path, output_path):
453
+ # cap = cv2.VideoCapture(input_path)
454
+ # if not cap.isOpened():
455
+ # raise FileNotFoundError(f"❌ Could not open video: {input_path}")
456
+
457
+ # fps = cap.get(cv2.CAP_PROP_FPS)
458
+ # width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
459
+ # height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
460
+ # fourcc = cv2.VideoWriter_fourcc(*'mp4v')
461
+ # out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
462
+
463
+ # frames = []
464
+ # while True:
465
+ # ret, frame = cap.read()
466
+ # if not ret:
467
+ # break
468
+ # frames.append(frame)
469
+ # cap.release()
470
+
471
+ # for frame in reversed(frames):
472
+ # out.write(frame)
473
+ # out.release()
474
+ # print(f"πŸ” Video reversed and saved: {output_path}")
475
+ # return output_path
476
+
477
+ # def reverse_video_file_inplace(path_in):
478
+ # tmp_path = path_in.replace(".mp4", "_tmp.mp4")
479
+ # reverse_video(path_in, tmp_path)
480
+ # os.replace(tmp_path, path_in)
481
+
482
  def reverse_video(input_path, output_path):
483
+ """
484
+ Reverse frames robustly β€” preserves all readable frames
485
+ even if OpenCV metadata is off by one.
486
+ """
487
  cap = cv2.VideoCapture(input_path)
488
  if not cap.isOpened():
489
  raise FileNotFoundError(f"❌ Could not open video: {input_path}")
490
+
491
  fps = cap.get(cv2.CAP_PROP_FPS)
492
  width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
493
  height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
494
  fourcc = cv2.VideoWriter_fourcc(*'mp4v')
 
495
 
496
  frames = []
497
  while True:
 
501
  frames.append(frame)
502
  cap.release()
503
 
504
+ if len(frames) == 0:
505
+ raise ValueError("No frames read from video!")
506
+
507
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
508
  for frame in reversed(frames):
509
  out.write(frame)
510
  out.release()
511
+ cv2.destroyAllWindows()
512
+
513
+ print(f"βœ… Reversed {len(frames)} frames β†’ {output_path}")
514
  return output_path
515
 
516
+
517
  def reverse_video_file_inplace(path_in):
518
+ """
519
+ Reverse a video in-place without losing frames.
520
+ """
521
  tmp_path = path_in.replace(".mp4", "_tmp.mp4")
522
  reverse_video(path_in, tmp_path)
523
  os.replace(tmp_path, path_in)
524
+ print(f"πŸ” Overwrote {path_in} with reversed version (same frame count).")
525
 
526
  # ==========================================================
527
  # === RAFT LOADING =========================================
 
686
  # === TRACKING =============================================
687
  # ==========================================================
688
 
689
+ # def run_tracking(video_path, mask_path, selection_mode="All Pixels"):
690
+ # BLACK_THRESH = 1
691
+ # HISTORY_LEN = 5
692
+
693
+ # reversed_path = reverse_video(video_path, REVERSED_INPUT)
694
+ # cap = cv2.VideoCapture(reversed_path)
695
+ # model = load_raft_model(MODEL_PATH)
696
+
697
+ # fps = cap.get(cv2.CAP_PROP_FPS)
698
+ # ret, first_frame = cap.read()
699
+ # if not ret:
700
+ # return "❌ Could not read first frame.", None, None, None
701
+ # H, W = first_frame.shape[:2]
702
+
703
+ # x0, y0, x1, y1 = compute_crop_box_from_mask_dynamic(first_frame, mask_path, pad=200)
704
+ # cw, ch = x1 - x0, y1 - y0
705
+
706
+ # fourcc = cv2.VideoWriter_fourcc(*'mp4v')
707
+ # out_vis = cv2.VideoWriter(OUTPUT_VIDEO, fourcc, fps, (W, H))
708
+ # out_mask = cv2.VideoWriter(OUTPUT_MASK_VIDEO, fourcc, fps, (W, H), isColor=False)
709
+
710
+ # full_mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE)
711
+ # full_mask = cv2.resize(full_mask, (W, H), interpolation=cv2.INTER_NEAREST)
712
+ # crop_mask = full_mask[y0:y1, x0:x1]
713
+
714
+ # if selection_mode == "All Pixels":
715
+ # ys, xs = np.where(crop_mask > 0)
716
+ # else:
717
+ # gray_first = cv2.cvtColor(first_frame, cv2.COLOR_BGR2GRAY)
718
+ # black_pixels = (gray_first[y0:y1, x0:x1] < BLACK_THRESH)
719
+ # combined = (crop_mask > 0) & black_pixels
720
+ # ys, xs = np.where(combined)
721
+
722
+ # tracked_points = np.vstack((xs, ys)).T.astype(np.float32)
723
+ # prev_full_rgb = cv2.cvtColor(first_frame, cv2.COLOR_BGR2RGB)
724
+ # prev_crop_rgb = prev_full_rgb[y0:y1, x0:x1]
725
+
726
+ # history = deque([True]*HISTORY_LEN, maxlen=HISTORY_LEN)
727
+ # stopped = False
728
+
729
+ # frame_idx = 0
730
+ # while True:
731
+ # ret, curr_frame = cap.read()
732
+ # if not ret:
733
+ # break
734
+ # frame_idx += 1
735
+ # curr_full_rgb = cv2.cvtColor(curr_frame, cv2.COLOR_BGR2RGB)
736
+ # curr_crop_rgb = curr_full_rgb[y0:y1, x0:x1]
737
+ # gray_crop = cv2.cvtColor(curr_crop_rgb, cv2.COLOR_RGB2GRAY)
738
+
739
+ # flow_crop = compute_flow(model, prev_crop_rgb, curr_crop_rgb)
740
+
741
+ # vis_full = curr_full_rgb.copy()
742
+ # mask_full = np.full((H, W), 255, dtype=np.uint8)
743
+
744
+ # new_points = []
745
+ # for pt in tracked_points:
746
+ # px, py = int(pt[0]), int(pt[1])
747
+ # if 0 <= px < cw and 0 <= py < ch:
748
+ # dx, dy = flow_crop[py, px]
749
+ # nx, ny = pt[0] + dx, pt[1] + dy
750
+ # nx = np.clip(nx, 0, cw-1)
751
+ # ny = np.clip(ny, 0, ch-1)
752
+ # new_points.append([nx, ny])
753
+ # tracked_points = np.array(new_points, dtype=np.float32)
754
+
755
+ # black_mask = gray_crop < BLACK_THRESH
756
+ # black_indices = tracked_points.astype(int)
757
+ # has_black = any(
758
+ # 0 <= px < cw and 0 <= py < ch and black_mask[py, px]
759
+ # for px, py in black_indices
760
+ # )
761
+ # history.append(has_black)
762
+
763
+ # if stopped:
764
+ # paint = False
765
+ # elif has_black:
766
+ # paint = True
767
+ # elif not any(history):
768
+ # stopped = True
769
+ # paint = False
770
+ # else:
771
+ # paint = True
772
+
773
+ # if paint:
774
+ # for pt in tracked_points:
775
+ # fx, fy = int(pt[0] + x0), int(pt[1] + y0)
776
+ # if 0 <= fx < W and 0 <= fy < H:
777
+ # cv2.circle(vis_full, (fx, fy), 1, (0,255,0), -1)
778
+ # mask_full[fy, fx] = 0
779
+
780
+ # out_vis.write(cv2.cvtColor(vis_full, cv2.COLOR_RGB2BGR))
781
+ # out_mask.write(mask_full)
782
+ # prev_crop_rgb = curr_crop_rgb
783
+
784
+ # if frame_idx % 10 == 0:
785
+ # print(f"Frame {frame_idx}: {'PAINT' if paint else 'NO-PAINT'} | has_black={has_black} | stopped={stopped}")
786
+
787
+ # cap.release()
788
+ # out_vis.release()
789
+ # out_mask.release()
790
+
791
+ # stabilize_black_regions(OUTPUT_MASK_VIDEO)
792
+
793
+ # reverse_video_file_inplace(OUTPUT_VIDEO)
794
+ # reverse_video_file_inplace(OUTPUT_MASK_VIDEO)
795
+ # reverse_video_file_inplace(STABILIZED_MASK)
796
+
797
+ # return (
798
+ # f"βœ… Tracking complete ({selection_mode}).\n"
799
+ # f"Square Crop {cw}x{ch} @ ({x0},{y0}) with padding=100\n"
800
+ # f"Painting stopped={'Yes' if stopped else 'No'} after {frame_idx} frames.\n"
801
+ # "Saved outputs reversed back to forward order.",
802
+ # OUTPUT_VIDEO,
803
+ # OUTPUT_MASK_VIDEO,
804
+ # STABILIZED_MASK
805
+ # )
806
+
807
+
808
+
809
  def run_tracking(video_path, mask_path, selection_mode="All Pixels"):
810
  BLACK_THRESH = 1
811
  HISTORY_LEN = 5
812
 
813
+ # --- Reverse input for backward tracking ---
814
  reversed_path = reverse_video(video_path, REVERSED_INPUT)
815
  cap = cv2.VideoCapture(reversed_path)
816
  model = load_raft_model(MODEL_PATH)
817
 
818
  fps = cap.get(cv2.CAP_PROP_FPS)
819
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
820
+ print(f"🎞️ Input video: {total_frames} frames at {fps:.2f} FPS")
821
+
822
  ret, first_frame = cap.read()
823
  if not ret:
824
  return "❌ Could not read first frame.", None, None, None
825
  H, W = first_frame.shape[:2]
826
 
827
+ # --- Compute dynamic square crop from mask ---
828
  x0, y0, x1, y1 = compute_crop_box_from_mask_dynamic(first_frame, mask_path, pad=200)
829
  cw, ch = x1 - x0, y1 - y0
830
 
 
850
 
851
  history = deque([True]*HISTORY_LEN, maxlen=HISTORY_LEN)
852
  stopped = False
 
853
  frame_idx = 0
854
+ curr_full_rgb = None
855
+
856
+ # === Main tracking loop ===
857
  while True:
858
  ret, curr_frame = cap.read()
859
  if not ret:
 
863
  curr_crop_rgb = curr_full_rgb[y0:y1, x0:x1]
864
  gray_crop = cv2.cvtColor(curr_crop_rgb, cv2.COLOR_RGB2GRAY)
865
 
866
+ # --- Optical flow between prev and curr ---
867
  flow_crop = compute_flow(model, prev_crop_rgb, curr_crop_rgb)
868
 
869
  vis_full = curr_full_rgb.copy()
870
  mask_full = np.full((H, W), 255, dtype=np.uint8)
871
 
872
+ # --- Move tracked points ---
873
  new_points = []
874
  for pt in tracked_points:
875
  px, py = int(pt[0]), int(pt[1])
 
881
  new_points.append([nx, ny])
882
  tracked_points = np.array(new_points, dtype=np.float32)
883
 
884
+ # --- Detect black pixels ---
885
  black_mask = gray_crop < BLACK_THRESH
886
  black_indices = tracked_points.astype(int)
887
  has_black = any(
 
890
  )
891
  history.append(has_black)
892
 
893
+ # --- Painting logic ---
894
  if stopped:
895
  paint = False
896
  elif has_black:
897
  paint = True
898
+ elif not any(history): # last N all False
899
  stopped = True
900
  paint = False
901
  else:
902
  paint = True
903
 
904
+ # --- Paint or skip ---
905
  if paint:
906
  for pt in tracked_points:
907
  fx, fy = int(pt[0] + x0), int(pt[1] + y0)
 
916
  if frame_idx % 10 == 0:
917
  print(f"Frame {frame_idx}: {'PAINT' if paint else 'NO-PAINT'} | has_black={has_black} | stopped={stopped}")
918
 
919
+ # === Add final static frame to preserve frame count ===
920
+ try:
921
+ if curr_full_rgb is not None:
922
+ out_vis.write(cv2.cvtColor(curr_full_rgb, cv2.COLOR_RGB2BGR))
923
+ out_mask.write(mask_full)
924
+ print("🧩 Added final frame to preserve total frame count.")
925
+ except Exception as e:
926
+ print(f"⚠️ Could not add final frame: {e}")
927
+
928
  cap.release()
929
  out_vis.release()
930
  out_mask.release()
931
 
932
+ # === Post-process: stabilization + reversal ===
933
  stabilize_black_regions(OUTPUT_MASK_VIDEO)
 
934
  reverse_video_file_inplace(OUTPUT_VIDEO)
935
  reverse_video_file_inplace(OUTPUT_MASK_VIDEO)
936
  reverse_video_file_inplace(STABILIZED_MASK)
937
 
938
+ # === Verify output frame counts ===
939
+ for path in [OUTPUT_VIDEO, OUTPUT_MASK_VIDEO, STABILIZED_MASK]:
940
+ cap_test = cv2.VideoCapture(path)
941
+ n = int(cap_test.get(cv2.CAP_PROP_FRAME_COUNT))
942
+ cap_test.release()
943
+ print(f"βœ… Verified {os.path.basename(path)} β†’ {n} frames")
944
+
945
  return (
946
  f"βœ… Tracking complete ({selection_mode}).\n"
947
+ f"Square Crop {cw}x{ch} @ ({x0},{y0}) with padding=200\n"
948
+ f"Painting stopped={'Yes' if stopped else 'No'} after {frame_idx} processed frames.\n"
949
+ f"All outputs now match input frame count ({total_frames}).",
950
  OUTPUT_VIDEO,
951
  OUTPUT_MASK_VIDEO,
952
  STABILIZED_MASK
 
1010
 
1011
  if __name__ == "__main__":
1012
  app = build_app()
1013
+ app.launch(server_name="0.0.0.0", server_port=7861, debug=True)
user_mask.png CHANGED