Update app.py
Browse files
app.py
CHANGED
|
@@ -6,41 +6,82 @@ import torch
|
|
| 6 |
import time
|
| 7 |
import numpy as np
|
| 8 |
import uuid
|
| 9 |
-
|
|
|
|
| 10 |
from transformers import RTDetrForObjectDetection, RTDetrImageProcessor
|
| 11 |
-
|
| 12 |
from draw_boxes import draw_bounding_boxes
|
| 13 |
|
|
|
|
|
|
|
|
|
|
| 14 |
image_processor = RTDetrImageProcessor.from_pretrained("PekingU/rtdetr_r50vd")
|
| 15 |
model = RTDetrForObjectDetection.from_pretrained("PekingU/rtdetr_r50vd").to("cuda")
|
| 16 |
|
| 17 |
-
|
| 18 |
SUBSAMPLE = 2
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
@spaces.GPU
|
| 21 |
def stream_object_detection(video, conf_threshold):
|
| 22 |
cap = cv2.VideoCapture(video)
|
| 23 |
|
| 24 |
-
|
| 25 |
-
|
| 26 |
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
| 30 |
|
| 31 |
iterating, frame = cap.read()
|
| 32 |
-
|
| 33 |
n_frames = 0
|
|
|
|
| 34 |
|
|
|
|
| 35 |
name = f"output_{uuid.uuid4()}.mp4"
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
| 38 |
|
| 39 |
while iterating:
|
| 40 |
-
frame = cv2.resize(
|
| 41 |
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
|
|
|
| 42 |
if n_frames % SUBSAMPLE == 0:
|
| 43 |
batch.append(frame)
|
|
|
|
| 44 |
if len(batch) == 2 * desired_fps:
|
| 45 |
inputs = image_processor(images=batch, return_tensors="pt").to("cuda")
|
| 46 |
|
|
@@ -55,40 +96,76 @@ def stream_object_detection(video, conf_threshold):
|
|
| 55 |
boxes = image_processor.post_process_object_detection(
|
| 56 |
outputs,
|
| 57 |
target_sizes=torch.tensor([(height, width)] * len(batch)),
|
| 58 |
-
threshold=conf_threshold
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
| 66 |
|
| 67 |
batch = []
|
| 68 |
segment_file.release()
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
end = time.time()
|
| 71 |
print("time taken for processing boxes", end - start)
|
|
|
|
|
|
|
| 72 |
name = f"output_{uuid.uuid4()}.mp4"
|
| 73 |
-
segment_file = cv2.VideoWriter(name,
|
| 74 |
|
| 75 |
iterating, frame = cap.read()
|
| 76 |
n_frames += 1
|
| 77 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
with gr.Blocks() as app:
|
| 80 |
gr.HTML(
|
| 81 |
"""
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
|
|
|
| 86 |
gr.HTML(
|
| 87 |
"""
|
| 88 |
<h3 style='text-align: center'>
|
| 89 |
-
<a href='https://arxiv.org/abs/2304.08069' target='_blank'>arXiv</a> |
|
|
|
|
| 90 |
</h3>
|
| 91 |
-
"""
|
|
|
|
| 92 |
with gr.Row():
|
| 93 |
with gr.Column():
|
| 94 |
video = gr.Video(label="Video Source")
|
|
@@ -100,7 +177,11 @@ with gr.Blocks() as app:
|
|
| 100 |
value=0.30,
|
| 101 |
)
|
| 102 |
with gr.Column():
|
| 103 |
-
output_video = gr.Video(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
video.upload(
|
| 106 |
fn=stream_object_detection,
|
|
@@ -108,5 +189,5 @@ with gr.Blocks() as app:
|
|
| 108 |
outputs=[output_video],
|
| 109 |
)
|
| 110 |
|
| 111 |
-
if __name__ ==
|
| 112 |
-
app.launch()
|
|
|
|
| 6 |
import time
|
| 7 |
import numpy as np
|
| 8 |
import uuid
|
| 9 |
+
import os
|
| 10 |
+
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,
|
| 34 |
+
"-c:v", "libx264",
|
| 35 |
+
"-preset", "veryfast",
|
| 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 |
|
|
|
|
| 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")
|
|
|
|
| 177 |
value=0.30,
|
| 178 |
)
|
| 179 |
with gr.Column():
|
| 180 |
+
output_video = gr.Video(
|
| 181 |
+
label="Processed Video",
|
| 182 |
+
streaming=True,
|
| 183 |
+
autoplay=True
|
| 184 |
+
)
|
| 185 |
|
| 186 |
video.upload(
|
| 187 |
fn=stream_object_detection,
|
|
|
|
| 189 |
outputs=[output_video],
|
| 190 |
)
|
| 191 |
|
| 192 |
+
if __name__ == "__main__":
|
| 193 |
+
app.launch()
|