Spaces:
Sleeping
Sleeping
| """Object detection using facebook/detr-resnet-50. | |
| The model is loaded once at import time so the (slow) cold start happens at boot | |
| rather than on the first request. For the MVP we use the high-level | |
| `object-detection` pipeline; a later version can switch to DetrImageProcessor / | |
| DetrForObjectDetection for finer control. | |
| """ | |
| import torch | |
| from PIL import Image, ImageOps | |
| from transformers import pipeline | |
| MODEL_NAME = "facebook/detr-resnet-50" | |
| DEVICE = "cuda" if torch.cuda.is_available() else "cpu" | |
| # Downscale very large uploads before inference. Keeps CPU latency and memory | |
| # bounded; boxes are reported in this (resized) space and the frontend scales by | |
| # image_size, so the visualization stays correct. | |
| MAX_SIDE = 1000 | |
| DEFAULT_THRESHOLD = 0.7 | |
| # Loaded once at module import. | |
| _detector = pipeline( | |
| "object-detection", | |
| model=MODEL_NAME, | |
| device=0 if DEVICE == "cuda" else -1, | |
| ) | |
| def _prepare(image: Image.Image) -> Image.Image: | |
| """Normalize orientation, ensure RGB, and bound the largest side.""" | |
| image = ImageOps.exif_transpose(image) # respect camera rotation | |
| image = image.convert("RGB") | |
| w, h = image.size | |
| longest = max(w, h) | |
| if longest > MAX_SIDE: | |
| scale = MAX_SIDE / longest | |
| image = image.resize((round(w * scale), round(h * scale))) | |
| return image | |
| def detect(image: Image.Image, threshold: float = DEFAULT_THRESHOLD) -> dict: | |
| """Run object detection on a PIL image. | |
| Returns a dict shaped for the frontend: | |
| { | |
| "image_size": {"width": int, "height": int}, | |
| "objects": [ | |
| {"label": str, "score": float, "box": {xmin, ymin, xmax, ymax}}, | |
| ... | |
| ] | |
| } | |
| Boxes are integer pixel coordinates in the (possibly resized) image space, | |
| matching image_size so the frontend can scale them onto its rendered image. | |
| """ | |
| image = _prepare(image) | |
| width, height = image.size | |
| raw = _detector(image) | |
| objects = [] | |
| for item in raw: | |
| score = float(item["score"]) | |
| if score < threshold: | |
| continue | |
| box = item["box"] | |
| # Clamp to image bounds so overlay boxes never spill past the edges. | |
| xmin = max(0, min(int(round(box["xmin"])), width)) | |
| ymin = max(0, min(int(round(box["ymin"])), height)) | |
| xmax = max(0, min(int(round(box["xmax"])), width)) | |
| ymax = max(0, min(int(round(box["ymax"])), height)) | |
| if xmax <= xmin or ymax <= ymin: | |
| continue | |
| objects.append( | |
| { | |
| "label": item["label"], | |
| "score": round(score, 4), | |
| "box": {"xmin": xmin, "ymin": ymin, "xmax": xmax, "ymax": ymax}, | |
| } | |
| ) | |
| # Highest-confidence objects first. | |
| objects.sort(key=lambda o: o["score"], reverse=True) | |
| return { | |
| "image_size": {"width": width, "height": height}, | |
| "objects": objects, | |
| } | |