Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
@@ -39,6 +39,40 @@ class GPUSatelliteModelGenerator:
|
|
39 |
[47, 94, 100],
|
40 |
[73, 131, 135]
|
41 |
])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
|
43 |
# Convert reference colors to HSV on GPU
|
44 |
self.shadow_colors_hsv = cp.asarray(cv2.cvtColor(
|
@@ -66,14 +100,15 @@ class GPUSatelliteModelGenerator:
|
|
66 |
self.road_tolerance = {'hue': 10, 'sat': 0.12, 'val': 0.15}
|
67 |
self.water_tolerance = {'hue': 20, 'sat': 0.15, 'val': 0.20}
|
68 |
|
69 |
-
#
|
70 |
self.colors = {
|
71 |
'black': cp.array([0, 0, 0]), # Shadows
|
72 |
'blue': cp.array([255, 0, 0]), # Water
|
73 |
'green': cp.array([0, 255, 0]), # Vegetation
|
74 |
'gray': cp.array([128, 128, 128]), # Roads
|
75 |
'brown': cp.array([0, 140, 255]), # Terrain
|
76 |
-
'white': cp.array([255, 255, 255]) # Buildings
|
|
|
77 |
}
|
78 |
|
79 |
self.min_area_for_clustering = 1000
|
@@ -105,7 +140,7 @@ class GPUSatelliteModelGenerator:
|
|
105 |
)
|
106 |
|
107 |
def segment_image_gpu(self, img):
|
108 |
-
"""GPU-accelerated image segmentation with
|
109 |
# Transfer image to GPU
|
110 |
gpu_img = cp.asarray(img)
|
111 |
gpu_hsv = cp.asarray(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))
|
@@ -114,40 +149,43 @@ class GPUSatelliteModelGenerator:
|
|
114 |
output = cp.zeros_like(gpu_img)
|
115 |
|
116 |
# Create a sliding window view for neighborhood analysis
|
117 |
-
pad = 2
|
118 |
gpu_hsv_pad = cp.pad(gpu_hsv, ((pad, pad), (pad, pad), (0, 0)), mode='edge')
|
119 |
|
120 |
# Prepare flattened HSV data
|
121 |
hsv_pixels = gpu_hsv.reshape(-1, 3)
|
122 |
|
123 |
-
# Initialize masks
|
124 |
shadow_mask = cp.zeros((height * width,), dtype=bool)
|
125 |
road_mask = cp.zeros((height * width,), dtype=bool)
|
126 |
water_mask = cp.zeros((height * width,), dtype=bool)
|
|
|
127 |
|
128 |
-
#
|
129 |
for ref_hsv in self.shadow_colors_hsv:
|
130 |
-
# Lower the threshold for shadows to catch more subtle variations
|
131 |
temp_tolerance = {
|
132 |
-
'hue': self.shadow_tolerance['hue'] * 1.2,
|
133 |
'sat': self.shadow_tolerance['sat'] * 1.1,
|
134 |
'val': self.shadow_tolerance['val'] * 1.2
|
135 |
}
|
136 |
shadow_mask |= self.gpu_color_distance_hsv(hsv_pixels.T, ref_hsv, temp_tolerance)
|
137 |
|
138 |
for ref_hsv in self.road_colors_hsv:
|
139 |
-
# Adjusted road detection with focus on value component
|
140 |
temp_tolerance = {
|
141 |
-
'hue': self.road_tolerance['hue'] * 1.3,
|
142 |
-
'sat': self.road_tolerance['sat'] * 1.2,
|
143 |
-
'val': self.road_tolerance['val']
|
144 |
}
|
145 |
road_mask |= self.gpu_color_distance_hsv(hsv_pixels.T, ref_hsv, temp_tolerance)
|
146 |
|
147 |
for ref_hsv in self.water_colors_hsv:
|
148 |
water_mask |= self.gpu_color_distance_hsv(hsv_pixels.T, ref_hsv, self.water_tolerance)
|
149 |
|
150 |
-
#
|
|
|
|
|
|
|
|
|
151 |
h, s, v = hsv_pixels.T
|
152 |
h = h * 2 # Convert to 0-360 range
|
153 |
s = s / 255
|
@@ -156,20 +194,22 @@ class GPUSatelliteModelGenerator:
|
|
156 |
# Enhanced vegetation detection
|
157 |
vegetation_mask = ((h >= 40) & (h <= 150) & (s >= 0.15))
|
158 |
|
159 |
-
#
|
160 |
-
terrain_mask = (
|
161 |
-
|
|
|
|
|
162 |
|
163 |
# Apply brightness-based corrections for roads
|
164 |
-
gray_mask = (s <= 0.2) & (v >= 0.4) & (v <= 0.85)
|
165 |
-
road_mask |= gray_mask & ~(shadow_mask | water_mask | vegetation_mask | terrain_mask)
|
166 |
|
167 |
-
# Enhanced shadow detection
|
168 |
-
dark_mask = (v <= 0.3)
|
169 |
-
shadow_mask |= dark_mask & ~(water_mask | road_mask)
|
170 |
|
171 |
# Building mask (everything that's not another category)
|
172 |
-
building_mask = ~(shadow_mask | water_mask | road_mask | vegetation_mask | terrain_mask)
|
173 |
|
174 |
# Apply masks to create output
|
175 |
output_flat = output.reshape(-1, 3)
|
@@ -178,52 +218,56 @@ class GPUSatelliteModelGenerator:
|
|
178 |
output_flat[road_mask] = self.colors['gray']
|
179 |
output_flat[vegetation_mask] = self.colors['green']
|
180 |
output_flat[terrain_mask] = self.colors['brown']
|
|
|
181 |
output_flat[building_mask] = self.colors['white']
|
182 |
|
183 |
segmented = output.reshape(height, width, 3)
|
184 |
|
185 |
-
# Enhanced
|
186 |
kernel = cp.ones((3, 3), dtype=bool)
|
187 |
kernel[1, 1] = False
|
188 |
|
189 |
-
# Two-pass cleanup
|
190 |
for _ in range(2):
|
191 |
for color_name, color_value in self.colors.items():
|
192 |
if cp.array_equal(color_value, self.colors['white']):
|
193 |
continue
|
194 |
|
195 |
-
# Create and dilate mask for current color
|
196 |
color_mask = cp.all(segmented == color_value, axis=2)
|
197 |
dilated = binary_dilation(color_mask, structure=kernel)
|
198 |
|
199 |
-
# Find isolated building pixels
|
200 |
building_pixels = cp.all(segmented == self.colors['white'], axis=2)
|
201 |
neighbor_count = binary_dilation(color_mask, structure=kernel).astype(int)
|
202 |
|
203 |
-
#
|
204 |
-
|
|
|
|
|
|
|
205 |
|
206 |
-
# Update isolated pixels
|
207 |
segmented[surrounded] = color_value
|
208 |
|
209 |
return segmented
|
210 |
-
|
211 |
def estimate_heights_gpu(self, img, segmented):
|
212 |
-
"""GPU-accelerated height estimation"""
|
213 |
gpu_segmented = cp.asarray(segmented)
|
214 |
-
buildings_mask = cp.
|
|
|
|
|
|
|
215 |
shadows_mask = cp.all(gpu_segmented == self.colors['black'], axis=2)
|
216 |
|
217 |
# Connected components labeling on GPU
|
218 |
labeled_array, num_features = cp_label(buildings_mask)
|
219 |
|
220 |
# Calculate areas using GPU
|
221 |
-
areas = cp.bincount(labeled_array.ravel())[1:]
|
222 |
max_area = cp.max(areas) if len(areas) > 0 else 1
|
223 |
|
224 |
height_map = cp.zeros_like(labeled_array, dtype=cp.float32)
|
225 |
|
226 |
-
# Process each building
|
227 |
for label in range(1, num_features + 1):
|
228 |
building_mask = (labeled_array == label)
|
229 |
if not cp.any(building_mask):
|
@@ -232,12 +276,18 @@ class GPUSatelliteModelGenerator:
|
|
232 |
area = areas[label-1]
|
233 |
size_factor = 0.3 + 0.7 * (area / max_area)
|
234 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
235 |
# Calculate shadow influence
|
236 |
dilated = binary_dilation(building_mask, structure=cp.ones((5,5)))
|
237 |
shadow_ratio = cp.sum(dilated & shadows_mask) / cp.sum(dilated)
|
238 |
shadow_factor = 0.2 + 0.8 * shadow_ratio
|
239 |
|
240 |
-
# Height calculation based on size and shadows
|
241 |
final_height = size_factor * shadow_factor
|
242 |
height_map[building_mask] = final_height
|
243 |
|
|
|
39 |
[47, 94, 100],
|
40 |
[73, 131, 135]
|
41 |
])
|
42 |
+
|
43 |
+
# Output colors (BGR for OpenCV)
|
44 |
+
|
45 |
+
self.roof_colors = cp.array([
|
46 |
+
[191, 148, 124],
|
47 |
+
[190, 142, 121],
|
48 |
+
[184, 154, 139],
|
49 |
+
[178, 118, 118],
|
50 |
+
[164, 109, 107],
|
51 |
+
[155, 113, 105],
|
52 |
+
[153, 111, 106],
|
53 |
+
[155, 95, 96],
|
54 |
+
[135, 82, 87],
|
55 |
+
[117, 82, 78],
|
56 |
+
[113, 62, 50],
|
57 |
+
[166, 144, 135]
|
58 |
+
])
|
59 |
+
|
60 |
+
# Convert roof colors to HSV
|
61 |
+
self.roof_colors_hsv = cp.asarray(cv2.cvtColor(
|
62 |
+
self.roof_colors.get().reshape(-1, 1, 3).astype(np.uint8),
|
63 |
+
cv2.COLOR_RGB2HSV
|
64 |
+
).reshape(-1, 3))
|
65 |
+
|
66 |
+
# Normalize roof HSV values
|
67 |
+
self.roof_colors_hsv[:, 0] = self.roof_colors_hsv[:, 0] * 2
|
68 |
+
self.roof_colors_hsv[:, 1:] = self.roof_colors_hsv[:, 1:] / 255
|
69 |
+
|
70 |
+
# Add roof tolerance (tighter than terrain to avoid confusion)
|
71 |
+
self.roof_tolerance = {
|
72 |
+
'hue': 8, # Tighter hue tolerance to differentiate from terrain
|
73 |
+
'sat': 0.15,
|
74 |
+
'val': 0.15
|
75 |
+
}
|
76 |
|
77 |
# Convert reference colors to HSV on GPU
|
78 |
self.shadow_colors_hsv = cp.asarray(cv2.cvtColor(
|
|
|
100 |
self.road_tolerance = {'hue': 10, 'sat': 0.12, 'val': 0.15}
|
101 |
self.water_tolerance = {'hue': 20, 'sat': 0.15, 'val': 0.20}
|
102 |
|
103 |
+
# Colors dictionary in [B, G, R]
|
104 |
self.colors = {
|
105 |
'black': cp.array([0, 0, 0]), # Shadows
|
106 |
'blue': cp.array([255, 0, 0]), # Water
|
107 |
'green': cp.array([0, 255, 0]), # Vegetation
|
108 |
'gray': cp.array([128, 128, 128]), # Roads
|
109 |
'brown': cp.array([0, 140, 255]), # Terrain
|
110 |
+
'white': cp.array([255, 255, 255]), # Buildings
|
111 |
+
'salmon': cp.array([128, 128, 255]) # Roofs
|
112 |
}
|
113 |
|
114 |
self.min_area_for_clustering = 1000
|
|
|
140 |
)
|
141 |
|
142 |
def segment_image_gpu(self, img):
|
143 |
+
"""GPU-accelerated image segmentation with roof detection"""
|
144 |
# Transfer image to GPU
|
145 |
gpu_img = cp.asarray(img)
|
146 |
gpu_hsv = cp.asarray(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))
|
|
|
149 |
output = cp.zeros_like(gpu_img)
|
150 |
|
151 |
# Create a sliding window view for neighborhood analysis
|
152 |
+
pad = 2
|
153 |
gpu_hsv_pad = cp.pad(gpu_hsv, ((pad, pad), (pad, pad), (0, 0)), mode='edge')
|
154 |
|
155 |
# Prepare flattened HSV data
|
156 |
hsv_pixels = gpu_hsv.reshape(-1, 3)
|
157 |
|
158 |
+
# Initialize masks including roofs
|
159 |
shadow_mask = cp.zeros((height * width,), dtype=bool)
|
160 |
road_mask = cp.zeros((height * width,), dtype=bool)
|
161 |
water_mask = cp.zeros((height * width,), dtype=bool)
|
162 |
+
roof_mask = cp.zeros((height * width,), dtype=bool)
|
163 |
|
164 |
+
# Color matching for predefined categories
|
165 |
for ref_hsv in self.shadow_colors_hsv:
|
|
|
166 |
temp_tolerance = {
|
167 |
+
'hue': self.shadow_tolerance['hue'] * 1.2,
|
168 |
'sat': self.shadow_tolerance['sat'] * 1.1,
|
169 |
'val': self.shadow_tolerance['val'] * 1.2
|
170 |
}
|
171 |
shadow_mask |= self.gpu_color_distance_hsv(hsv_pixels.T, ref_hsv, temp_tolerance)
|
172 |
|
173 |
for ref_hsv in self.road_colors_hsv:
|
|
|
174 |
temp_tolerance = {
|
175 |
+
'hue': self.road_tolerance['hue'] * 1.3,
|
176 |
+
'sat': self.road_tolerance['sat'] * 1.2,
|
177 |
+
'val': self.road_tolerance['val']
|
178 |
}
|
179 |
road_mask |= self.gpu_color_distance_hsv(hsv_pixels.T, ref_hsv, temp_tolerance)
|
180 |
|
181 |
for ref_hsv in self.water_colors_hsv:
|
182 |
water_mask |= self.gpu_color_distance_hsv(hsv_pixels.T, ref_hsv, self.water_tolerance)
|
183 |
|
184 |
+
# Roof detection with specific color matching
|
185 |
+
for ref_hsv in self.roof_colors_hsv:
|
186 |
+
roof_mask |= self.gpu_color_distance_hsv(hsv_pixels.T, ref_hsv, self.roof_tolerance)
|
187 |
+
|
188 |
+
# Normalize HSV values
|
189 |
h, s, v = hsv_pixels.T
|
190 |
h = h * 2 # Convert to 0-360 range
|
191 |
s = s / 255
|
|
|
194 |
# Enhanced vegetation detection
|
195 |
vegetation_mask = ((h >= 40) & (h <= 150) & (s >= 0.15))
|
196 |
|
197 |
+
# Refined terrain detection to avoid roof confusion
|
198 |
+
terrain_mask = (
|
199 |
+
((h >= 15) & (h <= 35) & (s >= 0.15) & (s <= 0.6)) | # Main terrain colors
|
200 |
+
((h >= 25) & (h <= 40) & (s >= 0.1) & (v >= 0.5)) # Lighter terrain
|
201 |
+
) & ~roof_mask # Explicitly exclude roof areas
|
202 |
|
203 |
# Apply brightness-based corrections for roads
|
204 |
+
gray_mask = (s <= 0.2) & (v >= 0.4) & (v <= 0.85)
|
205 |
+
road_mask |= gray_mask & ~(shadow_mask | water_mask | vegetation_mask | terrain_mask | roof_mask)
|
206 |
|
207 |
+
# Enhanced shadow detection
|
208 |
+
dark_mask = (v <= 0.3)
|
209 |
+
shadow_mask |= dark_mask & ~(water_mask | road_mask | roof_mask)
|
210 |
|
211 |
# Building mask (everything that's not another category)
|
212 |
+
building_mask = ~(shadow_mask | water_mask | road_mask | vegetation_mask | terrain_mask | roof_mask)
|
213 |
|
214 |
# Apply masks to create output
|
215 |
output_flat = output.reshape(-1, 3)
|
|
|
218 |
output_flat[road_mask] = self.colors['gray']
|
219 |
output_flat[vegetation_mask] = self.colors['green']
|
220 |
output_flat[terrain_mask] = self.colors['brown']
|
221 |
+
output_flat[roof_mask] = self.colors['salmon']
|
222 |
output_flat[building_mask] = self.colors['white']
|
223 |
|
224 |
segmented = output.reshape(height, width, 3)
|
225 |
|
226 |
+
# Enhanced cleanup with roof consideration
|
227 |
kernel = cp.ones((3, 3), dtype=bool)
|
228 |
kernel[1, 1] = False
|
229 |
|
230 |
+
# Two-pass cleanup
|
231 |
for _ in range(2):
|
232 |
for color_name, color_value in self.colors.items():
|
233 |
if cp.array_equal(color_value, self.colors['white']):
|
234 |
continue
|
235 |
|
|
|
236 |
color_mask = cp.all(segmented == color_value, axis=2)
|
237 |
dilated = binary_dilation(color_mask, structure=kernel)
|
238 |
|
|
|
239 |
building_pixels = cp.all(segmented == self.colors['white'], axis=2)
|
240 |
neighbor_count = binary_dilation(color_mask, structure=kernel).astype(int)
|
241 |
|
242 |
+
# Special handling for roofs - they should be more granular
|
243 |
+
if cp.array_equal(color_value, self.colors['salmon']):
|
244 |
+
surrounded = (neighbor_count >= 4) & building_pixels # Less aggressive for roofs
|
245 |
+
else:
|
246 |
+
surrounded = (neighbor_count >= 5) & building_pixels
|
247 |
|
|
|
248 |
segmented[surrounded] = color_value
|
249 |
|
250 |
return segmented
|
251 |
+
|
252 |
def estimate_heights_gpu(self, img, segmented):
|
253 |
+
"""GPU-accelerated height estimation with roof consideration"""
|
254 |
gpu_segmented = cp.asarray(segmented)
|
255 |
+
buildings_mask = cp.logical_or(
|
256 |
+
cp.all(gpu_segmented == self.colors['white'], axis=2),
|
257 |
+
cp.all(gpu_segmented == self.colors['salmon'], axis=2)
|
258 |
+
)
|
259 |
shadows_mask = cp.all(gpu_segmented == self.colors['black'], axis=2)
|
260 |
|
261 |
# Connected components labeling on GPU
|
262 |
labeled_array, num_features = cp_label(buildings_mask)
|
263 |
|
264 |
# Calculate areas using GPU
|
265 |
+
areas = cp.bincount(labeled_array.ravel())[1:]
|
266 |
max_area = cp.max(areas) if len(areas) > 0 else 1
|
267 |
|
268 |
height_map = cp.zeros_like(labeled_array, dtype=cp.float32)
|
269 |
|
270 |
+
# Process each building/roof
|
271 |
for label in range(1, num_features + 1):
|
272 |
building_mask = (labeled_array == label)
|
273 |
if not cp.any(building_mask):
|
|
|
276 |
area = areas[label-1]
|
277 |
size_factor = 0.3 + 0.7 * (area / max_area)
|
278 |
|
279 |
+
# Check if this is a roof (salmon color)
|
280 |
+
is_roof = cp.any(cp.all(gpu_segmented[building_mask] == self.colors['salmon'], axis=1))
|
281 |
+
|
282 |
+
# Adjust height for roofs (typically smaller residential buildings)
|
283 |
+
if is_roof:
|
284 |
+
size_factor *= 0.8 # Slightly lower height for residential buildings
|
285 |
+
|
286 |
# Calculate shadow influence
|
287 |
dilated = binary_dilation(building_mask, structure=cp.ones((5,5)))
|
288 |
shadow_ratio = cp.sum(dilated & shadows_mask) / cp.sum(dilated)
|
289 |
shadow_factor = 0.2 + 0.8 * shadow_ratio
|
290 |
|
|
|
291 |
final_height = size_factor * shadow_factor
|
292 |
height_map[building_mask] = final_height
|
293 |
|