mchi8sp2 commited on
Commit
5f94b6a
·
verified ·
1 Parent(s): 49fe77c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -102
app.py CHANGED
@@ -11,23 +11,26 @@ import subprocess
11
  from transformers import RTDetrForObjectDetection, RTDetrImageProcessor
12
  from draw_boxes import draw_bounding_boxes
13
 
14
- # -------------------------------------------------
15
- # Model loading (same as original)
16
- # -------------------------------------------------
 
17
  image_processor = RTDetrImageProcessor.from_pretrained("PekingU/rtdetr_r50vd")
18
  model = RTDetrForObjectDetection.from_pretrained("PekingU/rtdetr_r50vd").to("cuda")
19
 
20
  SUBSAMPLE = 2
 
21
 
22
 
23
  def reencode_to_browser_h264(input_path: str) -> str:
24
- """
25
- Force a browser-compatible H.264 stream.
26
- - libx264
27
- - yuv420p (mandatory for Chrome/Firefox/Safari)
28
- - +faststart (moov atom at the beginning → progressive playback)
29
- """
30
  output_path = input_path.replace(".mp4", "_h264.mp4")
 
 
31
  cmd = [
32
  "ffmpeg", "-y",
33
  "-i", input_path,
@@ -36,145 +39,134 @@ def reencode_to_browser_h264(input_path: str) -> str:
36
  "-crf", "23",
37
  "-pix_fmt", "yuv420p",
38
  "-movflags", "+faststart",
39
- "-an", # no audio
40
  output_path
41
  ]
42
- subprocess.run(
43
- cmd,
44
- check=True,
45
- stdout=subprocess.DEVNULL,
46
- stderr=subprocess.DEVNULL
47
- )
48
- # Clean intermediate file
49
- if os.path.exists(input_path):
 
 
 
50
  os.remove(input_path)
51
- return output_path
 
 
 
 
 
 
 
 
52
 
53
 
54
  @spaces.GPU
55
  def stream_object_detection(video, conf_threshold):
 
56
  cap = cv2.VideoCapture(video)
 
 
57
 
58
  fps = int(cap.get(cv2.CAP_PROP_FPS)) or 30
59
  desired_fps = max(1, fps // SUBSAMPLE)
 
60
 
61
- # Force even dimensions (H.264 requirement)
62
  orig_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
63
  orig_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
64
- width = (orig_w // 2) // 2 * 2
65
- height = (orig_h // 2) // 2 * 2
 
 
 
 
66
 
67
  iterating, frame = cap.read()
68
  n_frames = 0
69
  batch = []
70
-
71
- # First segment
72
- name = f"output_{uuid.uuid4()}.mp4"
73
- # We deliberately use mp4v for writing reliability,
74
- # then re-encode. This is the most robust pattern on HF Spaces.
75
  fourcc = cv2.VideoWriter_fourcc(*"mp4v")
76
  segment_file = cv2.VideoWriter(name, fourcc, desired_fps, (width, height))
77
 
78
- while iterating:
79
- frame = cv2.resize(frame, (width, height))
80
- frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
81
-
82
- if n_frames % SUBSAMPLE == 0:
83
- batch.append(frame)
84
-
85
- if len(batch) == 2 * desired_fps:
86
- inputs = image_processor(images=batch, return_tensors="pt").to("cuda")
87
-
88
- print(f"starting batch of size {len(batch)}")
89
- start = time.time()
90
- with torch.no_grad():
91
- outputs = model(**inputs)
92
- end = time.time()
93
- print("time taken for inference", end - start)
94
-
95
- start = time.time()
96
- boxes = image_processor.post_process_object_detection(
97
- outputs,
98
- target_sizes=torch.tensor([(height, width)] * len(batch)),
99
- threshold=conf_threshold
100
- )
101
-
102
- for array, box in zip(batch, boxes):
103
- pil_image = draw_bounding_boxes(
104
- Image.fromarray(array), box, model, conf_threshold
105
- )
106
- frame_bgr = np.array(pil_image)[:, :, ::-1].copy()
107
- segment_file.write(frame_bgr)
108
-
109
- batch = []
110
- segment_file.release()
111
-
112
- # === Critical fix: convert to real H.264 before yielding ===
113
- playable_name = reencode_to_browser_h264(name)
114
- yield playable_name
115
-
116
- end = time.time()
117
- print("time taken for processing boxes", end - start)
118
-
119
- # Prepare next segment
120
- name = f"output_{uuid.uuid4()}.mp4"
121
- segment_file = cv2.VideoWriter(name, fourcc, desired_fps, (width, height))
122
 
123
- iterating, frame = cap.read()
124
- n_frames += 1
125
 
126
- # Flush remaining frames if any
127
- if batch:
128
- inputs = image_processor(images=batch, return_tensors="pt").to("cuda")
129
  with torch.no_grad():
130
  outputs = model(**inputs)
 
 
 
131
  boxes = image_processor.post_process_object_detection(
132
  outputs,
133
- target_sizes=torch.tensor([(height, width)] * len(batch)),
134
  threshold=conf_threshold
135
  )
136
- for array, box in zip(batch, boxes):
 
137
  pil_image = draw_bounding_boxes(
138
  Image.fromarray(array), box, model, conf_threshold
139
  )
140
  frame_bgr = np.array(pil_image)[:, :, ::-1].copy()
141
- segment_file.write(frame_bgr)
 
 
 
 
 
 
 
 
 
 
 
142
 
143
- segment_file.release()
144
- playable_name = reencode_to_browser_h264(name)
145
- yield playable_name
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
 
147
  cap.release()
 
148
 
149
 
150
- # -------------------------------------------------
151
- # Gradio UI (unchanged structure)
152
- # -------------------------------------------------
153
  with gr.Blocks() as app:
154
- gr.HTML(
155
- """
156
  <h1 style='text-align: center'>
157
- Video Object Detection with RT-DETR (Browser-Compatible Fix)
 
158
  </h1>
159
- """
160
- )
161
- gr.HTML(
162
- """
163
- <h3 style='text-align: center'>
164
- <a href='https://arxiv.org/abs/2304.08069' target='_blank'>arXiv</a> |
165
- <a href='https://huggingface.co/PekingU/rtdetr_r101vd_coco_o365' target='_blank'>Model</a>
166
- </h3>
167
- """
168
- )
169
  with gr.Row():
170
  with gr.Column():
171
  video = gr.Video(label="Video Source")
172
  conf_threshold = gr.Slider(
173
  label="Confidence Threshold",
174
- minimum=0.0,
175
- maximum=1.0,
176
- step=0.05,
177
- value=0.30,
178
  )
179
  with gr.Column():
180
  output_video = gr.Video(
 
11
  from transformers import RTDetrForObjectDetection, RTDetrImageProcessor
12
  from draw_boxes import draw_bounding_boxes
13
 
14
+ print("=" * 60)
15
+ print("RT-DETR Space Browser-compatible version starting")
16
+ print("=" * 60)
17
+
18
  image_processor = RTDetrImageProcessor.from_pretrained("PekingU/rtdetr_r50vd")
19
  model = RTDetrForObjectDetection.from_pretrained("PekingU/rtdetr_r50vd").to("cuda")
20
 
21
  SUBSAMPLE = 2
22
+ SEGMENT_SECONDS = 1.0 # smaller segments → faster first output
23
 
24
 
25
  def reencode_to_browser_h264(input_path: str) -> str:
26
+ """Force H.264 that Chrome can actually play."""
27
+ if not os.path.exists(input_path):
28
+ print(f"[ERROR] Input file does not exist: {input_path}")
29
+ return input_path
30
+
 
31
  output_path = input_path.replace(".mp4", "_h264.mp4")
32
+ print(f"[RE-ENCODE] {input_path} → {output_path}")
33
+
34
  cmd = [
35
  "ffmpeg", "-y",
36
  "-i", input_path,
 
39
  "-crf", "23",
40
  "-pix_fmt", "yuv420p",
41
  "-movflags", "+faststart",
42
+ "-an",
43
  output_path
44
  ]
45
+
46
+ try:
47
+ result = subprocess.run(
48
+ cmd,
49
+ check=True,
50
+ stdout=subprocess.PIPE,
51
+ stderr=subprocess.PIPE,
52
+ text=True
53
+ )
54
+ print(f"[RE-ENCODE] SUCCESS → {output_path}")
55
+ # remove intermediate
56
  os.remove(input_path)
57
+ return output_path
58
+ except subprocess.CalledProcessError as e:
59
+ print(f"[RE-ENCODE] FAILED!")
60
+ print("ffmpeg stderr:", e.stderr)
61
+ # return original as last resort
62
+ return input_path
63
+ except FileNotFoundError:
64
+ print("[RE-ENCODE] ffmpeg not found in PATH!")
65
+ return input_path
66
 
67
 
68
  @spaces.GPU
69
  def stream_object_detection(video, conf_threshold):
70
+ print(f"\n[START] New video received. conf_threshold={conf_threshold}")
71
  cap = cv2.VideoCapture(video)
72
+ if not cap.isOpened():
73
+ raise gr.Error("Cannot open the uploaded video")
74
 
75
  fps = int(cap.get(cv2.CAP_PROP_FPS)) or 30
76
  desired_fps = max(1, fps // SUBSAMPLE)
77
+ print(f"[INFO] original fps={fps}, desired_fps={desired_fps}")
78
 
 
79
  orig_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
80
  orig_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
81
+ width = max(2, (orig_w // 2) // 2 * 2)
82
+ height = max(2, (orig_h // 2) // 2 * 2)
83
+ print(f"[INFO] output size = {width}x{height}")
84
+
85
+ frames_per_segment = max(1, int(desired_fps * SEGMENT_SECONDS))
86
+ print(f"[INFO] frames_per_segment = {frames_per_segment}")
87
 
88
  iterating, frame = cap.read()
89
  n_frames = 0
90
  batch = []
91
+ name = f"/tmp/output_{uuid.uuid4()}.mp4" # use /tmp for safety
 
 
 
 
92
  fourcc = cv2.VideoWriter_fourcc(*"mp4v")
93
  segment_file = cv2.VideoWriter(name, fourcc, desired_fps, (width, height))
94
 
95
+ def flush_segment(current_batch, writer, current_name):
96
+ if len(current_batch) == 0:
97
+ writer.release()
98
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
+ print(f"[PROCESS] batch size = {len(current_batch)}")
101
+ inputs = image_processor(images=current_batch, return_tensors="pt").to("cuda")
102
 
103
+ start = time.time()
 
 
104
  with torch.no_grad():
105
  outputs = model(**inputs)
106
+ print(f"time taken for inference {time.time() - start:.3f}")
107
+
108
+ start = time.time()
109
  boxes = image_processor.post_process_object_detection(
110
  outputs,
111
+ target_sizes=torch.tensor([(height, width)] * len(current_batch)),
112
  threshold=conf_threshold
113
  )
114
+
115
+ for array, box in zip(current_batch, boxes):
116
  pil_image = draw_bounding_boxes(
117
  Image.fromarray(array), box, model, conf_threshold
118
  )
119
  frame_bgr = np.array(pil_image)[:, :, ::-1].copy()
120
+ writer.write(frame_bgr)
121
+
122
+ writer.release()
123
+ print(f"time taken for processing boxes {time.time() - start:.3f}")
124
+
125
+ playable = reencode_to_browser_h264(current_name)
126
+ print(f"[YIELD] → {playable}")
127
+ return playable
128
+
129
+ while iterating:
130
+ frame = cv2.resize(frame, (width, height))
131
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
132
 
133
+ if n_frames % SUBSAMPLE == 0:
134
+ batch.append(frame)
135
+
136
+ if len(batch) >= frames_per_segment:
137
+ playable = flush_segment(batch, segment_file, name)
138
+ if playable:
139
+ yield playable
140
+ batch = []
141
+ name = f"/tmp/output_{uuid.uuid4()}.mp4"
142
+ segment_file = cv2.VideoWriter(name, fourcc, desired_fps, (width, height))
143
+
144
+ iterating, frame = cap.read()
145
+ n_frames += 1
146
+
147
+ # Final remaining frames
148
+ print(f"[FINAL] remaining frames in batch = {len(batch)}")
149
+ playable = flush_segment(batch, segment_file, name)
150
+ if playable:
151
+ yield playable
152
 
153
  cap.release()
154
+ print("[END] Video processing finished\n")
155
 
156
 
 
 
 
157
  with gr.Blocks() as app:
158
+ gr.HTML("""
 
159
  <h1 style='text-align: center'>
160
+ RT-DETR Object Detection<br>
161
+ <span style='font-size:0.55em;color:#555'>(Short video + Chrome H.264 fixed)</span>
162
  </h1>
163
+ """)
 
 
 
 
 
 
 
 
 
164
  with gr.Row():
165
  with gr.Column():
166
  video = gr.Video(label="Video Source")
167
  conf_threshold = gr.Slider(
168
  label="Confidence Threshold",
169
+ minimum=0.0, maximum=1.0, step=0.05, value=0.30
 
 
 
170
  )
171
  with gr.Column():
172
  output_video = gr.Video(