jbilcke-hf HF Staff commited on
Commit
8e5f6db
·
verified ·
1 Parent(s): 74087f2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +85 -35
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
- # Output colors (BGR for OpenCV)
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 improved road and shadow detection"""
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 # Equivalent to window_size=5 in segment.py
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
- # Improved color matching with adjusted tolerances
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, # Slightly increased tolerance
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, # Increased hue tolerance
142
- 'sat': self.road_tolerance['sat'] * 1.2, # Increased saturation tolerance
143
- 'val': self.road_tolerance['val'] # Keep original value tolerance
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
- # Normalize HSV values for vegetation and terrain detection
 
 
 
 
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
- # Enhanced terrain detection
160
- terrain_mask = ((h >= 10) & (h <= 30) & (s >= 0.15)) | \
161
- ((h >= 25) & (h <= 40) & (s >= 0.1) & (v <= 0.8)) # Added brown-gray detection
 
 
162
 
163
  # Apply brightness-based corrections for roads
164
- gray_mask = (s <= 0.2) & (v >= 0.4) & (v <= 0.85) # Detect grayish areas
165
- road_mask |= gray_mask & ~(shadow_mask | water_mask | vegetation_mask | terrain_mask)
166
 
167
- # Enhanced shadow detection using value component
168
- dark_mask = (v <= 0.3) # Detect very dark areas
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 isolated pixel cleanup using morphological operations
186
  kernel = cp.ones((3, 3), dtype=bool)
187
  kernel[1, 1] = False
188
 
189
- # Two-pass cleanup for better results
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
- # More aggressive cleanup for truly isolated pixels
204
- surrounded = (neighbor_count >= 5) & building_pixels # At least 5 neighbors of same color
 
 
 
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.all(gpu_segmented == self.colors['white'], axis=2)
 
 
 
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:] # Skip background
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