Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -6,10 +6,9 @@ from PIL import Image, ImageDraw, ImageFont
|
|
| 6 |
import json
|
| 7 |
import random
|
| 8 |
from transformers import (
|
| 9 |
-
AutoModelForZeroShotObjectDetection,
|
| 10 |
AutoProcessor,
|
| 11 |
-
|
| 12 |
-
|
| 13 |
DepthAnythingForDepthEstimation,
|
| 14 |
)
|
| 15 |
|
|
@@ -22,18 +21,17 @@ print(f"Using device: {device}")
|
|
| 22 |
# -----------------------------
|
| 23 |
# Model loading
|
| 24 |
# -----------------------------
|
| 25 |
-
# 1.
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
DEPTH_ID = "depth-anything/Depth-Anything-V2-Small-hf"
|
| 37 |
depth_processor = AutoProcessor.from_pretrained(DEPTH_ID)
|
| 38 |
depth_model = DepthAnythingForDepthEstimation.from_pretrained(DEPTH_ID).to(device).eval()
|
| 39 |
|
|
@@ -41,35 +39,38 @@ depth_model = DepthAnythingForDepthEstimation.from_pretrained(DEPTH_ID).to(devic
|
|
| 41 |
# -----------------------------
|
| 42 |
# Pipeline steps
|
| 43 |
# -----------------------------
|
| 44 |
-
def
|
| 45 |
-
"""Step 1:
|
| 46 |
-
inputs = g_processor(images=image, text=text_prompt, return_tensors="pt").to(device)
|
| 47 |
-
with torch.no_grad():
|
| 48 |
-
outputs = g_model(**inputs)
|
| 49 |
-
results = g_processor.post_process_grounded_object_detection(
|
| 50 |
-
outputs,
|
| 51 |
-
inputs.input_ids,
|
| 52 |
-
threshold=threshold,
|
| 53 |
-
text_threshold=threshold,
|
| 54 |
-
target_sizes=[image.size[::-1]],
|
| 55 |
-
)
|
| 56 |
-
return results[0]
|
| 57 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
-
|
| 60 |
-
"""Step 2: Use detected boxes as SAM prompts -> binary masks."""
|
| 61 |
-
if len(boxes) == 0:
|
| 62 |
-
return []
|
| 63 |
-
input_boxes = [[[float(b[0]), float(b[1]), float(b[2]), float(b[3])] for b in boxes]]
|
| 64 |
-
inputs = sam_processor(image, input_boxes=input_boxes, return_tensors="pt").to(device)
|
| 65 |
with torch.no_grad():
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
|
| 75 |
def estimate_depth(image: Image.Image):
|
|
@@ -93,8 +94,7 @@ def estimate_depth(image: Image.Image):
|
|
| 93 |
def average_depth_per_mask(depth_map, masks):
|
| 94 |
"""Step 4: Compute mean depth within each binary mask."""
|
| 95 |
out = []
|
| 96 |
-
for
|
| 97 |
-
m = mask[0].numpy().astype(bool) # H x W
|
| 98 |
if m.sum() == 0:
|
| 99 |
out.append(None)
|
| 100 |
else:
|
|
@@ -118,7 +118,7 @@ def render_visualization(image, boxes, labels, masks, avg_depths):
|
|
| 118 |
continue
|
| 119 |
color = colors[i % len(colors)]
|
| 120 |
# Fill mask
|
| 121 |
-
m = masks[i]
|
| 122 |
rgba = np.zeros((m.shape[0], m.shape[1], 4), dtype=np.uint8)
|
| 123 |
rgba[m] = color + (120,) # alpha
|
| 124 |
m_img = Image.fromarray(rgba, mode="RGBA")
|
|
@@ -140,17 +140,12 @@ def run_pipeline(image, text_prompt, threshold):
|
|
| 140 |
|
| 141 |
image_pil = Image.fromarray(image).convert("RGB")
|
| 142 |
|
| 143 |
-
# Step 1 β
|
| 144 |
-
|
| 145 |
-
boxes = detection["boxes"].tolist()
|
| 146 |
-
labels = detection["labels"]
|
| 147 |
|
| 148 |
if len(boxes) == 0:
|
| 149 |
return image, json.dumps({"error": "No objects detected"}), None
|
| 150 |
|
| 151 |
-
# Step 2 β Segmentation
|
| 152 |
-
masks = segment_with_sam(image_pil, boxes)
|
| 153 |
-
|
| 154 |
# Step 3 β Depth estimation
|
| 155 |
depth_map = estimate_depth(image_pil)
|
| 156 |
|
|
@@ -207,8 +202,8 @@ demo = gr.Interface(
|
|
| 207 |
],
|
| 208 |
title="π§ Object Depth Comparison Pipeline",
|
| 209 |
description=(
|
| 210 |
-
"Pipeline: Image β
|
| 211 |
-
"Depth Anything V2 (depth map) β Avg depth per mask β JSON comparison.\n\n"
|
| 212 |
"Tip: write the object classes you expect, e.g. `person, car`."
|
| 213 |
)
|
| 214 |
)
|
|
|
|
| 6 |
import json
|
| 7 |
import random
|
| 8 |
from transformers import (
|
|
|
|
| 9 |
AutoProcessor,
|
| 10 |
+
Sam3Model,
|
| 11 |
+
Sam3Processor,
|
| 12 |
DepthAnythingForDepthEstimation,
|
| 13 |
)
|
| 14 |
|
|
|
|
| 21 |
# -----------------------------
|
| 22 |
# Model loading
|
| 23 |
# -----------------------------
|
| 24 |
+
# 1. SAM3 (promptable concept segmentation -> detects + segments objects from a
|
| 25 |
+
# text prompt in a single step, replacing the old Grounding DINO + SAM combo).
|
| 26 |
+
# NOTE: facebook/sam3 is a gated Meta model on the Hub β the environment needs
|
| 27 |
+
# a HF token that has accepted the model license (e.g. `huggingface-cli login`
|
| 28 |
+
# or the `HF_TOKEN` env var) or `from_pretrained` will fail to download it.
|
| 29 |
+
SAM3_ID = "facebook/sam3"
|
| 30 |
+
sam3_processor = Sam3Processor.from_pretrained(SAM3_ID)
|
| 31 |
+
sam3_model = Sam3Model.from_pretrained(SAM3_ID).to(device).eval()
|
| 32 |
+
|
| 33 |
+
# 2. Depth Anything V2 Large (monocular depth estimation)
|
| 34 |
+
DEPTH_ID = "depth-anything/Depth-Anything-V2-Large-hf"
|
|
|
|
| 35 |
depth_processor = AutoProcessor.from_pretrained(DEPTH_ID)
|
| 36 |
depth_model = DepthAnythingForDepthEstimation.from_pretrained(DEPTH_ID).to(device).eval()
|
| 37 |
|
|
|
|
| 39 |
# -----------------------------
|
| 40 |
# Pipeline steps
|
| 41 |
# -----------------------------
|
| 42 |
+
def detect_and_segment(image: Image.Image, text_prompt: str, threshold: float = 0.3):
|
| 43 |
+
"""Step 1+2: SAM3 promptable concept segmentation -> boxes + masks + labels.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
+
Runs one SAM3 forward pass per requested class, reusing the image's vision
|
| 46 |
+
embeddings so the (expensive) vision backbone only runs once per image.
|
| 47 |
+
"""
|
| 48 |
+
class_names = [c.strip() for c in text_prompt.split(",") if c.strip()]
|
| 49 |
+
if not class_names:
|
| 50 |
+
return [], [], []
|
| 51 |
|
| 52 |
+
img_inputs = sam3_processor(images=image, return_tensors="pt").to(device)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
with torch.no_grad():
|
| 54 |
+
vision_embeds = sam3_model.get_vision_features(pixel_values=img_inputs.pixel_values)
|
| 55 |
+
|
| 56 |
+
all_boxes, all_labels, all_masks = [], [], []
|
| 57 |
+
for class_name in class_names:
|
| 58 |
+
text_inputs = sam3_processor(text=class_name, return_tensors="pt").to(device)
|
| 59 |
+
with torch.no_grad():
|
| 60 |
+
outputs = sam3_model(vision_embeds=vision_embeds, **text_inputs)
|
| 61 |
+
result = sam3_processor.post_process_instance_segmentation(
|
| 62 |
+
outputs,
|
| 63 |
+
threshold=threshold,
|
| 64 |
+
mask_threshold=0.5,
|
| 65 |
+
target_sizes=img_inputs.get("original_sizes").tolist(),
|
| 66 |
+
)[0]
|
| 67 |
+
|
| 68 |
+
for box, mask in zip(result["boxes"], result["masks"]):
|
| 69 |
+
all_boxes.append([float(v) for v in box.tolist()])
|
| 70 |
+
all_labels.append(class_name)
|
| 71 |
+
all_masks.append(mask.cpu().numpy().astype(bool))
|
| 72 |
+
|
| 73 |
+
return all_boxes, all_labels, all_masks
|
| 74 |
|
| 75 |
|
| 76 |
def estimate_depth(image: Image.Image):
|
|
|
|
| 94 |
def average_depth_per_mask(depth_map, masks):
|
| 95 |
"""Step 4: Compute mean depth within each binary mask."""
|
| 96 |
out = []
|
| 97 |
+
for m in masks: # each mask is already an H x W bool array
|
|
|
|
| 98 |
if m.sum() == 0:
|
| 99 |
out.append(None)
|
| 100 |
else:
|
|
|
|
| 118 |
continue
|
| 119 |
color = colors[i % len(colors)]
|
| 120 |
# Fill mask
|
| 121 |
+
m = masks[i] # already an H x W bool array
|
| 122 |
rgba = np.zeros((m.shape[0], m.shape[1], 4), dtype=np.uint8)
|
| 123 |
rgba[m] = color + (120,) # alpha
|
| 124 |
m_img = Image.fromarray(rgba, mode="RGBA")
|
|
|
|
| 140 |
|
| 141 |
image_pil = Image.fromarray(image).convert("RGB")
|
| 142 |
|
| 143 |
+
# Step 1+2 β SAM3 detection + segmentation
|
| 144 |
+
boxes, labels, masks = detect_and_segment(image_pil, text_prompt, threshold)
|
|
|
|
|
|
|
| 145 |
|
| 146 |
if len(boxes) == 0:
|
| 147 |
return image, json.dumps({"error": "No objects detected"}), None
|
| 148 |
|
|
|
|
|
|
|
|
|
|
| 149 |
# Step 3 β Depth estimation
|
| 150 |
depth_map = estimate_depth(image_pil)
|
| 151 |
|
|
|
|
| 202 |
],
|
| 203 |
title="π§ Object Depth Comparison Pipeline",
|
| 204 |
description=(
|
| 205 |
+
"Pipeline: Image β SAM3 (text-prompted boxes+masks per class) β "
|
| 206 |
+
"Depth Anything V2 Large (depth map) β Avg depth per mask β JSON comparison.\n\n"
|
| 207 |
"Tip: write the object classes you expect, e.g. `person, car`."
|
| 208 |
)
|
| 209 |
)
|