Spaces:
Running on Zero
Running on Zero
| import spaces | |
| import gradio as gr | |
| import numpy as np | |
| import torch | |
| from transformers import ( | |
| AutoModelForZeroShotObjectDetection, | |
| AutoProcessor, | |
| SamModel, | |
| SamProcessor, | |
| ) | |
| CUDA_DEVICE = "cuda" | |
| SAM_MODEL_ID = "facebook/sam-vit-base" | |
| GROUNDING_DINO_MODEL_ID = "IDEA-Research/grounding-dino-base" | |
| # ZeroGPU enables CUDA emulation during startup, so models should be moved to | |
| # CUDA at module scope. Importing `spaces` before `torch` is important. | |
| sam_model = SamModel.from_pretrained(SAM_MODEL_ID).to(CUDA_DEVICE).eval() | |
| sam_processor = SamProcessor.from_pretrained(SAM_MODEL_ID) | |
| dino_processor = AutoProcessor.from_pretrained(GROUNDING_DINO_MODEL_ID) | |
| dino_model = ( | |
| AutoModelForZeroShotObjectDetection.from_pretrained(GROUNDING_DINO_MODEL_ID) | |
| .to(CUDA_DEVICE) | |
| .eval() | |
| ) | |
| def infer_dino(image: np.ndarray, text_queries: list[str], score_threshold: float): | |
| query_text = ". ".join(text_queries) + "." | |
| height, width = image.shape[:2] | |
| inputs = dino_processor( | |
| images=image, | |
| text=query_text, | |
| return_tensors="pt", | |
| ).to(CUDA_DEVICE) | |
| with torch.inference_mode(): | |
| outputs = dino_model(**inputs) | |
| return dino_processor.post_process_grounded_object_detection( | |
| outputs=outputs, | |
| input_ids=inputs.input_ids, | |
| threshold=score_threshold, | |
| text_threshold=score_threshold, | |
| target_sizes=[(height, width)], | |
| )[0] | |
| def query_image(image: np.ndarray, text_queries: str, dino_threshold: float): | |
| if image is None: | |
| raise gr.Error("Please upload an image.") | |
| queries = [query.strip() for query in text_queries.split(",") if query.strip()] | |
| if not queries: | |
| raise gr.Error("Please enter at least one candidate label.") | |
| detections = infer_dino(image, queries, dino_threshold) | |
| boxes = detections["boxes"].detach().cpu() | |
| labels = detections.get("text_labels", detections.get("labels", [])) | |
| if len(boxes) == 0: | |
| return image, [] | |
| # Process every detected box in one SAM forward pass. | |
| sam_inputs = sam_processor( | |
| images=image, | |
| input_boxes=[boxes.tolist()], | |
| return_tensors="pt", | |
| ).to(CUDA_DEVICE) | |
| with torch.inference_mode(): | |
| sam_outputs = sam_model(**sam_inputs, multimask_output=True) | |
| masks = sam_processor.image_processor.post_process_masks( | |
| sam_outputs.pred_masks.detach().cpu(), | |
| sam_inputs["original_sizes"].detach().cpu(), | |
| sam_inputs["reshaped_input_sizes"].detach().cpu(), | |
| )[0] | |
| iou_scores = sam_outputs.iou_scores.detach().cpu()[0] | |
| best_mask_indices = iou_scores.argmax(dim=-1) | |
| annotations = [] | |
| for detection_index, label in enumerate(labels): | |
| best_mask_index = int(best_mask_indices[detection_index]) | |
| mask = masks[detection_index, best_mask_index].numpy().astype(bool) | |
| annotations.append((mask, str(label))) | |
| return image, annotations | |
| description = """ | |
| This Space combines Grounding DINO for open-vocabulary object detection with | |
| SAM for text-prompted image segmentation. Enter comma-separated labels such as | |
| `cat, dog, sidewalk, crosswalk`. | |
| """ | |
| demo = gr.Interface( | |
| fn=query_image, | |
| inputs=[ | |
| gr.Image(type="numpy", label="Image Input"), | |
| gr.Textbox( | |
| label="Candidate Labels", | |
| placeholder="cat, dog, sidewalk, crosswalk", | |
| ), | |
| gr.Slider( | |
| minimum=0.0, | |
| maximum=1.0, | |
| value=0.25, | |
| step=0.01, | |
| label="Grounding DINO Confidence Threshold", | |
| ), | |
| ], | |
| outputs=gr.AnnotatedImage(label="Segmentation Result"), | |
| title="Grounding DINO + SAM Zero-shot Segmentation", | |
| description=description, | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |