Nisha56 commited on
Commit
ab17de2
·
1 Parent(s): cfbbb2f

Day 3: GroundingDINO detection and SAM2 prompt generation

Browse files
Files changed (1) hide show
  1. grounding.py +200 -0
grounding.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import torch
4
+ import numpy as np
5
+ from PIL import Image
6
+
7
+ # tell Python where groundingdino package is
8
+ from groundingdino.util.inference import load_model, load_image, predict
9
+
10
+ # config
11
+ GROUNDING_CONFIG = os.path.join(os.path.dirname(__file__), "models", "GroundingDINO_SwinT_OGC.py")
12
+ GROUNDING_WEIGHTS = os.path.join(os.path.dirname(__file__), "models", "groundingdino_swint_ogc.pth")
13
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
14
+
15
+ # detection thresholds
16
+ BOX_THRESHOLD = 0.30 # confidence needed to keep a box
17
+ TEXT_THRESHOLD = 0.25 # confidence needed to keep a text label
18
+
19
+ print(f"[GDINO] Using device: {DEVICE}")
20
+
21
+
22
+ def load_grounding_model():
23
+ """
24
+ Load GroundingDINO model into memory.
25
+ Call once at startup — takes ~5 seconds.
26
+ """
27
+ model = load_model(GROUNDING_CONFIG, GROUNDING_WEIGHTS)
28
+ model = model.to(DEVICE)
29
+ print("[GDINO] Model loaded successfully")
30
+ return model
31
+
32
+
33
+ def detect_objects(image_path: str, model, text_prompt: str = None) -> list:
34
+ """
35
+ Detect objects in image using text prompt.
36
+
37
+ text_prompt examples:
38
+ "product . text . logo . background"
39
+ "person . car . sky"
40
+ If None, uses a default poster-focused prompt.
41
+
42
+ Returns list of dicts:
43
+ [{label, confidence, x1, y1, x2, y2, cx, cy, w, h}]
44
+ All coordinates are absolute pixels.
45
+ """
46
+ if text_prompt is None:
47
+ text_prompt = (
48
+ "product . text . logo . background . "
49
+ "image . graphic . illustration . icon . button"
50
+ )
51
+
52
+ # load_image returns (PIL image, transformed tensor) — both needed
53
+ image_pil, image_tensor = load_image(image_path)
54
+
55
+ print(type(image_pil))
56
+ print(image_pil.shape)
57
+
58
+ img_h, img_w = image_pil.shape[:2]
59
+
60
+ # run detection
61
+ with torch.no_grad():
62
+ boxes, confidences, labels = predict(
63
+ model = model,
64
+ image = image_tensor,
65
+ caption = text_prompt,
66
+ box_threshold = BOX_THRESHOLD,
67
+ text_threshold = TEXT_THRESHOLD,
68
+ )
69
+
70
+ print(f"[GDINO] Detected {len(boxes)} objects with prompt: '{text_prompt}'")
71
+
72
+ # boxes come back as normalised (0-1) centre-format [cx, cy, w, h]
73
+ # convert to absolute pixel corner-format [x1, y1, x2, y2]
74
+ results = []
75
+ for box, conf, label in zip(boxes, confidences, labels):
76
+ cx, cy, w, h = box.tolist()
77
+
78
+ # convert normalised → absolute pixels
79
+ abs_cx = cx * img_w
80
+ abs_cy = cy * img_h
81
+ abs_w = w * img_w
82
+ abs_h = h * img_h
83
+
84
+ x1 = max(0, int(abs_cx - abs_w / 2))
85
+ y1 = max(0, int(abs_cy - abs_h / 2))
86
+ x2 = min(img_w, int(abs_cx + abs_w / 2))
87
+ y2 = min(img_h, int(abs_cy + abs_h / 2))
88
+
89
+ results.append({
90
+ "label": label,
91
+ "confidence": round(float(conf), 3),
92
+ "x1": x1, "y1": y1,
93
+ "x2": x2, "y2": y2,
94
+ "cx": int(abs_cx), "cy": int(abs_cy),
95
+ "w": x2 - x1, "h": y2 - y1,
96
+ })
97
+
98
+ # sort by confidence descending
99
+ results.sort(key=lambda r: r["confidence"], reverse=True)
100
+ return results
101
+
102
+
103
+ def boxes_to_sam_prompts(detections: list) -> tuple:
104
+ """
105
+ Convert GroundingDINO detections into SAM2 input format.
106
+
107
+ SAM2 accepts:
108
+ - point_coords: array of (x, y) centre points
109
+ - point_labels: array of 1s (foreground)
110
+
111
+ We use the centre of each detected box as a SAM2 prompt point.
112
+ This tells SAM2 exactly where to segment instead of using blind grid.
113
+
114
+ Returns: (point_coords np.array, point_labels np.array, labels list)
115
+ """
116
+ if not detections:
117
+ return None, None, []
118
+
119
+ points = []
120
+ labels = []
121
+
122
+ for det in detections:
123
+ points.append([det["cx"], det["cy"]])
124
+ labels.append(det["label"])
125
+
126
+ point_coords = np.array(points, dtype=np.float32)
127
+ point_labels = np.ones(len(points), dtype=np.int32) # 1 = foreground
128
+
129
+ return point_coords, point_labels, labels
130
+
131
+
132
+ def save_detection_debug(image_path: str, detections: list, output_dir: str = None):
133
+ """
134
+ Save debug image showing GroundingDINO bounding boxes.
135
+ Useful to visually verify detection quality before passing to SAM2.
136
+ """
137
+ import cv2
138
+
139
+ if output_dir is None:
140
+ output_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "outputs")
141
+ os.makedirs(output_dir, exist_ok=True)
142
+
143
+ image = cv2.imread(image_path)
144
+ colours = [
145
+ (255, 80, 80), (80, 255, 80), (80, 80, 255),
146
+ (255, 255, 80), (255, 80, 255), (80, 255, 255),
147
+ ]
148
+
149
+ for idx, det in enumerate(detections):
150
+ colour = colours[idx % len(colours)]
151
+ cv2.rectangle(image, (det["x1"], det["y1"]), (det["x2"], det["y2"]), colour, 2)
152
+ cv2.putText(
153
+ image,
154
+ f"{det['label']} {det['confidence']}",
155
+ (det["x1"] + 4, det["y1"] + 18),
156
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, colour, 1
157
+ )
158
+
159
+ out_path = os.path.join(output_dir, "grounding_debug.jpg")
160
+ cv2.imwrite(out_path, image)
161
+ print(f"[GDINO] Saved detection debug → {out_path}")
162
+ return out_path
163
+
164
+
165
+ # run directly to test
166
+ if __name__ == "__main__":
167
+ BACKEND_DIR = os.path.dirname(os.path.abspath(__file__))
168
+ TEST_IMAGE = os.path.join(BACKEND_DIR, "test_images", "sale_img.jpg")
169
+
170
+ if not os.path.exists(TEST_IMAGE):
171
+ print(f"ERROR: No image at {TEST_IMAGE}")
172
+ exit(1)
173
+
174
+ print("=" * 50)
175
+ print("Testing GroundingDINO detection")
176
+ print("=" * 50)
177
+
178
+ model = load_grounding_model()
179
+ detections = detect_objects(TEST_IMAGE, model)
180
+
181
+ point_coords, point_labels, sam_labels = boxes_to_sam_prompts(detections)
182
+
183
+ print("\nSAM2 Prompt Points:")
184
+ print(point_coords)
185
+
186
+ print("\nSAM2 Point Labels:")
187
+ print(point_labels)
188
+
189
+ print("\nDetection Labels:")
190
+ print(sam_labels)
191
+
192
+ print(f"\nDetected {len(detections)} objects:")
193
+ for d in detections:
194
+ print(f" {d['label']:20s} conf={d['confidence']} box=({d['x1']},{d['y1']}) → ({d['x2']},{d['y2']})")
195
+
196
+ save_detection_debug(TEST_IMAGE, detections)
197
+
198
+ print("=" * 50)
199
+ print("Open outputs/grounding_debug.jpg to see boxes.")
200
+ print("=" * 50)