makeitfr commited on
Commit
db2c47d
·
verified ·
1 Parent(s): ece5fea

Upload ui_element_locator.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. ui_element_locator.py +256 -0
ui_element_locator.py ADDED
@@ -0,0 +1,256 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+ from typing import Dict, Any, Tuple, Optional, List
7
+ from PIL import Image
8
+
9
+ def to_rgb(img: np.ndarray) -> Optional[np.ndarray]:
10
+ """Converts image to BGR format (3 channels). Handles None input."""
11
+ if img is None:
12
+ return None
13
+ if len(img.shape) == 2:
14
+ # Grayscale to BGR
15
+ return cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
16
+ if img.shape[2] == 4:
17
+ # BGRA to BGR (removes alpha channel)
18
+ return cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
19
+ # Already BGR or RGB (assuming OpenCV reads as BGR)
20
+ return img
21
+
22
+ def match_ui_elements_in_image(
23
+ original_image_path: str,
24
+ cropped_templates_dir: str = 'cropped_images',
25
+ threshold: float = 0.7,
26
+ output_json: Optional[str] = None
27
+ ) -> Dict[str, Any]:
28
+ """
29
+ Matches cropped UI element templates against an original image.
30
+ Returns coordinates of all matched elements.
31
+
32
+ Args:
33
+ original_image_path: Path to the original image (e.g., Screenshot.png)
34
+ cropped_templates_dir: Directory containing cropped UI images
35
+ threshold: Confidence threshold for matches (0-1)
36
+ output_json: Optional path to save results as JSON
37
+
38
+ Returns:
39
+ Dictionary with match results
40
+ """
41
+
42
+ print(f"[UI Locator] Loading original image: {original_image_path}")
43
+ original_img = cv2.imread(original_image_path, cv2.IMREAD_UNCHANGED)
44
+ original_img_rgb = to_rgb(original_img)
45
+
46
+ if original_img_rgb is None:
47
+ raise ValueError(f"Failed to load image: {original_image_path}")
48
+
49
+ print(f"[UI Locator] Original image size: {original_img_rgb.shape}")
50
+ img_height, img_width = original_img_rgb.shape[:2]
51
+
52
+ # Load all cropped templates
53
+ print(f"[UI Locator] Loading cropped templates from: {cropped_templates_dir}")
54
+ templates = {}
55
+ template_files = sorted(Path(cropped_templates_dir).glob('crop_*.png'))
56
+
57
+ if not template_files:
58
+ raise ValueError(f"No cropped templates found in {cropped_templates_dir}")
59
+
60
+ for template_file in template_files:
61
+ template_img = cv2.imread(str(template_file), cv2.IMREAD_UNCHANGED)
62
+ if template_img is not None:
63
+ template_img_rgb = to_rgb(template_img)
64
+ templates[template_file.name] = template_img_rgb
65
+ else:
66
+ print(f"[WARNING] Could not load template: {template_file.name}")
67
+
68
+ print(f"[UI Locator] Loaded {len(templates)} templates")
69
+
70
+ # Match each template
71
+ matches = []
72
+ skipped = 0
73
+
74
+ for i, (template_name, template_img) in enumerate(templates.items()):
75
+ try:
76
+ # Skip templates that are too large
77
+ if template_img.shape[0] > img_height or template_img.shape[1] > img_width:
78
+ skipped += 1
79
+ continue
80
+
81
+ # Skip very small templates (likely noise)
82
+ if template_img.shape[0] < 4 or template_img.shape[1] < 4:
83
+ skipped += 1
84
+ continue
85
+
86
+ # Perform template matching
87
+ result = cv2.matchTemplate(original_img_rgb, template_img, cv2.TM_CCOEFF_NORMED)
88
+ _, max_val, _, max_loc = cv2.minMaxLoc(result)
89
+
90
+ # Only record matches above threshold
91
+ if max_val >= threshold:
92
+ template_h, template_w = template_img.shape[:2]
93
+ x1, y1 = max_loc
94
+ x2 = x1 + template_w
95
+ y2 = y1 + template_h
96
+
97
+ # Calculate center
98
+ center_x = (x1 + x2) / 2
99
+ center_y = (y1 + y2) / 2
100
+
101
+ matches.append({
102
+ 'template_id': template_name.replace('.png', ''),
103
+ 'template_file': template_name,
104
+ 'confidence': float(max_val),
105
+ 'bbox': {
106
+ 'x1': int(x1),
107
+ 'y1': int(y1),
108
+ 'x2': int(x2),
109
+ 'y2': int(y2),
110
+ 'width': int(template_w),
111
+ 'height': int(template_h)
112
+ },
113
+ 'center': {
114
+ 'x': int(center_x),
115
+ 'y': int(center_y)
116
+ },
117
+ 'bbox_ratio': {
118
+ 'x1': x1 / img_width,
119
+ 'y1': y1 / img_height,
120
+ 'x2': x2 / img_width,
121
+ 'y2': y2 / img_height
122
+ }
123
+ })
124
+
125
+ if (i + 1) % 20 == 0:
126
+ print(f"[UI Locator] Processed {i + 1}/{len(templates)} templates...")
127
+
128
+ except Exception as e:
129
+ print(f"[WARNING] Failed to match {template_name}: {str(e)}")
130
+ skipped += 1
131
+ continue
132
+
133
+ # Sort matches by confidence
134
+ matches.sort(key=lambda x: x['confidence'], reverse=True)
135
+
136
+ result = {
137
+ 'source_image': original_image_path,
138
+ 'image_size': {
139
+ 'width': img_width,
140
+ 'height': img_height
141
+ },
142
+ 'templates_directory': cropped_templates_dir,
143
+ 'templates_loaded': len(templates),
144
+ 'templates_skipped': skipped,
145
+ 'threshold': threshold,
146
+ 'matches_found': len(matches),
147
+ 'matches': matches
148
+ }
149
+
150
+ print(f"\n[UI Locator] Matching complete!")
151
+ print(f"[UI Locator] Found {len(matches)} matches above threshold {threshold}")
152
+ print(f"[UI Locator] Skipped {skipped} templates (too large or too small)")
153
+
154
+ # Save results as JSON
155
+ if output_json:
156
+ with open(output_json, 'w') as f:
157
+ json.dump(result, f, indent=2)
158
+ print(f"[UI Locator] Results saved to: {output_json}")
159
+
160
+ return result
161
+
162
+ def visualize_matches(
163
+ original_image_path: str,
164
+ matches_data: Dict[str, Any],
165
+ output_image_path: Optional[str] = None
166
+ ) -> np.ndarray:
167
+ """
168
+ Visualize matched UI elements on the original image.
169
+
170
+ Args:
171
+ original_image_path: Path to original image
172
+ matches_data: Results from match_ui_elements_in_image
173
+ output_image_path: Optional path to save visualization
174
+
175
+ Returns:
176
+ Annotated image with bounding boxes
177
+ """
178
+
179
+ print(f"[Visualization] Loading image: {original_image_path}")
180
+ img = cv2.imread(original_image_path)
181
+
182
+ if img is None:
183
+ raise ValueError(f"Failed to load visualization image: {original_image_path}")
184
+
185
+ # Draw bounding boxes for each match
186
+ for match in matches_data['matches']:
187
+ bbox = match['bbox']
188
+ center = match['center']
189
+ confidence = match['confidence']
190
+ template_id = match['template_id']
191
+
192
+ # Draw bounding box
193
+ color = (0, 255, 0) # Green
194
+ thickness = 2
195
+ cv2.rectangle(img, (bbox['x1'], bbox['y1']), (bbox['x2'], bbox['y2']), color, thickness)
196
+
197
+ # Draw center point
198
+ cv2.circle(img, (center['x'], center['y']), 3, (0, 0, 255), -1) # Red center point
199
+
200
+ # Draw label
201
+ label = f"ID:{template_id} ({confidence:.2f})"
202
+ cv2.putText(img, label, (bbox['x1'], bbox['y1'] - 5),
203
+ cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 0, 0), 1)
204
+
205
+ if output_image_path:
206
+ cv2.imwrite(output_image_path, img)
207
+ print(f"[Visualization] Saved to: {output_image_path}")
208
+
209
+ return img
210
+
211
+ if __name__ == "__main__":
212
+ import sys
213
+ from pathlib import Path
214
+ from config import get_screenshot_path, get_output_path, CROPPED_IMAGES_DIR
215
+
216
+ # Paths using config
217
+ original_image = get_screenshot_path('Screenshot.png')
218
+ cropped_dir = str(CROPPED_IMAGES_DIR)
219
+ output_json = get_output_path('ui_elements_coordinates.json')
220
+ output_viz = get_output_path('ui_elements_visualization.png')
221
+
222
+ print("=" * 70)
223
+ print("UI Element Locator - Template Matching Tool")
224
+ print("=" * 70)
225
+
226
+ # Match UI elements
227
+ results = match_ui_elements_in_image(
228
+ original_image_path=original_image,
229
+ cropped_templates_dir=cropped_dir,
230
+ threshold=0.7,
231
+ output_json=output_json
232
+ )
233
+
234
+ print(f"\n[Summary]")
235
+ print(f"Total UI elements found: {results['matches_found']}")
236
+ print(f"Image size: {results['image_size']['width']}x{results['image_size']['height']}")
237
+
238
+ # Show top matches
239
+ print(f"\n[Top 10 Matches by Confidence]")
240
+ print("-" * 70)
241
+ for i, match in enumerate(results['matches'][:10], 1):
242
+ bbox = match['bbox']
243
+ center = match['center']
244
+ print(f"{i}. {match['template_id']} - Confidence: {match['confidence']:.4f}")
245
+ print(f" Center: ({center['x']}, {center['y']}) | Bbox: ({bbox['x1']}, {bbox['y1']}) -> ({bbox['x2']}, {bbox['y2']})")
246
+
247
+ # Visualize matches
248
+ try:
249
+ print(f"\n[Visualization] Creating annotated image...")
250
+ visualize_matches(original_image, results, output_viz)
251
+ except Exception as e:
252
+ print(f"[ERROR] Visualization failed: {str(e)}")
253
+
254
+ print(f"\n[Output Files]")
255
+ print(f"JSON Results: {output_json}")
256
+ print(f"Visualization: {output_viz}")