Spaces:
Sleeping
Sleeping
DynamicPacific
commited on
Commit
·
67cd09b
1
Parent(s):
dbdc03c
Improve tree extraction with contour-based detection
Browse files- Replace grid-based approach with contour detection
- Use adaptive NDVI thresholding based on image statistics
- Add morphological operations to clean up tree masks
- Implement area-based filtering (0.1% to 10% of image)
- Add adaptive confidence scoring
- Include area and confidence metadata in features
- Add OpenCV dependency for contour processing
- Results in more accurate tree shapes and better area coverage
- requirements.txt +1 -0
- utils/advanced_extraction.py +79 -42
requirements.txt
CHANGED
@@ -10,3 +10,4 @@ fiona>=1.9.0
|
|
10 |
matplotlib>=3.7.0
|
11 |
pandas>=2.0.0
|
12 |
scipy>=1.11.0
|
|
|
|
10 |
matplotlib>=3.7.0
|
11 |
pandas>=2.0.0
|
12 |
scipy>=1.11.0
|
13 |
+
opencv-python>=4.8.0
|
utils/advanced_extraction.py
CHANGED
@@ -3,24 +3,35 @@ import logging
|
|
3 |
import numpy as np
|
4 |
import rasterio
|
5 |
from rasterio.warp import transform_bounds
|
|
|
|
|
6 |
|
7 |
def extract_features_from_geotiff(image_path, output_folder, feature_type="trees"):
|
8 |
-
"""
|
9 |
try:
|
10 |
logging.info(f"Extracting {feature_type} from {image_path}")
|
11 |
|
12 |
with rasterio.open(image_path) as src:
|
13 |
-
#
|
14 |
if src.count >= 3:
|
15 |
red = src.read(1).astype(float)
|
16 |
green = src.read(2).astype(float)
|
17 |
nir = src.read(4).astype(float) if src.count >= 4 else green
|
18 |
|
|
|
19 |
ndvi = np.divide(nir - red, nir + red + 1e-10)
|
20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
else:
|
22 |
band = src.read(1)
|
23 |
-
|
|
|
|
|
24 |
|
25 |
# Get bounds
|
26 |
bounds = src.bounds
|
@@ -32,48 +43,74 @@ def extract_features_from_geotiff(image_path, output_folder, feature_type="trees
|
|
32 |
else:
|
33 |
west, south, east, north = -74.1, 40.6, -73.9, 40.8
|
34 |
|
35 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
features = []
|
37 |
height, width = mask.shape
|
38 |
-
|
|
|
39 |
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
}
|
74 |
-
|
75 |
-
|
76 |
-
|
|
|
|
|
77 |
|
78 |
return {
|
79 |
"type": "FeatureCollection",
|
|
|
3 |
import numpy as np
|
4 |
import rasterio
|
5 |
from rasterio.warp import transform_bounds
|
6 |
+
import cv2
|
7 |
+
from scipy import ndimage
|
8 |
|
9 |
def extract_features_from_geotiff(image_path, output_folder, feature_type="trees"):
|
10 |
+
"""Improved feature extraction with contour-based tree detection."""
|
11 |
try:
|
12 |
logging.info(f"Extracting {feature_type} from {image_path}")
|
13 |
|
14 |
with rasterio.open(image_path) as src:
|
15 |
+
# Enhanced NDVI calculation
|
16 |
if src.count >= 3:
|
17 |
red = src.read(1).astype(float)
|
18 |
green = src.read(2).astype(float)
|
19 |
nir = src.read(4).astype(float) if src.count >= 4 else green
|
20 |
|
21 |
+
# Improved NDVI calculation
|
22 |
ndvi = np.divide(nir - red, nir + red + 1e-10)
|
23 |
+
|
24 |
+
# Adaptive thresholding based on image statistics
|
25 |
+
ndvi_mean = np.mean(ndvi)
|
26 |
+
ndvi_std = np.std(ndvi)
|
27 |
+
threshold = max(0.3, ndvi_mean + 0.5 * ndvi_std) # More adaptive threshold
|
28 |
+
|
29 |
+
mask = ndvi > threshold
|
30 |
else:
|
31 |
band = src.read(1)
|
32 |
+
# Adaptive threshold for single-band images
|
33 |
+
threshold = np.percentile(band, 70) # Increased from 60
|
34 |
+
mask = band > threshold
|
35 |
|
36 |
# Get bounds
|
37 |
bounds = src.bounds
|
|
|
43 |
else:
|
44 |
west, south, east, north = -74.1, 40.6, -73.9, 40.8
|
45 |
|
46 |
+
# Convert to uint8 for OpenCV processing
|
47 |
+
mask_uint8 = (mask * 255).astype(np.uint8)
|
48 |
+
|
49 |
+
# Morphological operations to clean up the mask
|
50 |
+
kernel = np.ones((3, 3), np.uint8)
|
51 |
+
mask_cleaned = cv2.morphologyEx(mask_uint8, cv2.MORPH_CLOSE, kernel)
|
52 |
+
mask_cleaned = cv2.morphologyEx(mask_cleaned, cv2.MORPH_OPEN, kernel)
|
53 |
+
|
54 |
+
# Find contours instead of using grid
|
55 |
+
contours, _ = cv2.findContours(mask_cleaned, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
56 |
+
|
57 |
+
# Filter contours by area and create features
|
58 |
features = []
|
59 |
height, width = mask.shape
|
60 |
+
min_area = (height * width) * 0.001 # Minimum 0.1% of image area
|
61 |
+
max_area = (height * width) * 0.1 # Maximum 10% of image area
|
62 |
|
63 |
+
for i, contour in enumerate(contours):
|
64 |
+
area = cv2.contourArea(contour)
|
65 |
+
|
66 |
+
# Filter by area
|
67 |
+
if area < min_area or area > max_area:
|
68 |
+
continue
|
69 |
+
|
70 |
+
# Simplify contour for better polygon shape
|
71 |
+
epsilon = 0.02 * cv2.arcLength(contour, True)
|
72 |
+
approx = cv2.approxPolyDP(contour, epsilon, True)
|
73 |
+
|
74 |
+
# Convert contour points to geographic coordinates
|
75 |
+
polygon_coords = []
|
76 |
+
for point in approx:
|
77 |
+
x, y = point[0]
|
78 |
+
|
79 |
+
# Convert pixel coordinates to geographic coordinates
|
80 |
+
x_ratio = x / width
|
81 |
+
y_ratio = y / height
|
82 |
+
|
83 |
+
lon = west + x_ratio * (east - west)
|
84 |
+
lat = north - y_ratio * (north - south)
|
85 |
+
|
86 |
+
polygon_coords.append([lon, lat])
|
87 |
+
|
88 |
+
# Close the polygon
|
89 |
+
if len(polygon_coords) > 2:
|
90 |
+
polygon_coords.append(polygon_coords[0])
|
91 |
+
|
92 |
+
# Calculate confidence based on area and shape
|
93 |
+
area_ratio = area / (height * width)
|
94 |
+
confidence = min(0.95, 0.5 + area_ratio * 10) # Adaptive confidence
|
95 |
+
|
96 |
+
feature = {
|
97 |
+
"type": "Feature",
|
98 |
+
"id": i,
|
99 |
+
"properties": {
|
100 |
+
"feature_type": feature_type,
|
101 |
+
"confidence": round(confidence, 2),
|
102 |
+
"area_pixels": int(area),
|
103 |
+
"area_ratio": round(area_ratio, 4)
|
104 |
+
},
|
105 |
+
"geometry": {
|
106 |
+
"type": "Polygon",
|
107 |
+
"coordinates": [polygon_coords]
|
108 |
}
|
109 |
+
}
|
110 |
+
|
111 |
+
features.append(feature)
|
112 |
+
|
113 |
+
logging.info(f"Extracted {len(features)} tree features")
|
114 |
|
115 |
return {
|
116 |
"type": "FeatureCollection",
|