Spaces:
Sleeping
Sleeping
attempt pano image cropping
Browse files- app.py +130 -15
- requirements.txt +3 -0
app.py
CHANGED
@@ -1,4 +1,6 @@
|
|
1 |
import streamlit as st
|
|
|
|
|
2 |
import folium
|
3 |
from streamlit_folium import st_folium
|
4 |
import requests
|
@@ -32,6 +34,117 @@ if not MAPILLARY_ACCESS_TOKEN:
|
|
32 |
st.error("Mapillary access token not found. Please configure it in the Space secrets.")
|
33 |
st.stop()
|
34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
def get_bounding_box(lat, lon):
|
36 |
"""
|
37 |
Create a bounding box around a point that extends roughly 25 meters in each direction
|
@@ -54,9 +167,8 @@ def get_nearest_image(lat, lon):
|
|
54 |
"""
|
55 |
bbox = get_bounding_box(lat, lon)
|
56 |
params = {
|
57 |
-
'fields': 'id,thumb_1024_url',
|
58 |
'limit': 1,
|
59 |
-
'is_pano': False,
|
60 |
'bbox': f'{bbox[0]},{bbox[1]},{bbox[2]},{bbox[3]}'
|
61 |
}
|
62 |
|
@@ -69,11 +181,11 @@ def get_nearest_image(lat, lon):
|
|
69 |
)
|
70 |
response.raise_for_status()
|
71 |
data = response.json()
|
72 |
-
|
73 |
if 'data' in data and len(data['data']) > 0:
|
74 |
return data['data'][0]
|
75 |
return None
|
76 |
-
|
77 |
except requests.exceptions.RequestException as e:
|
78 |
st.error(f"Error fetching Mapillary data: {str(e)}")
|
79 |
return None
|
@@ -132,7 +244,7 @@ def load_knn():
|
|
132 |
|
133 |
def main():
|
134 |
st.title("Percept: Map Explorer")
|
135 |
-
|
136 |
try:
|
137 |
with st.spinner('Loading CLIP model... This may take a moment.'):
|
138 |
model, preprocess, tokenizer = load_model()
|
@@ -148,35 +260,38 @@ def main():
|
|
148 |
# Initialize the map centered on Amsterdam
|
149 |
amsterdam_coords = [52.3676, 4.9041]
|
150 |
m = folium.Map(location=amsterdam_coords, zoom_start=13)
|
151 |
-
|
152 |
# Add a marker for Amsterdam city center
|
153 |
folium.Marker(
|
154 |
amsterdam_coords,
|
155 |
popup="Amsterdam City Center",
|
156 |
icon=folium.Icon(color="red", icon="info-sign")
|
157 |
).add_to(m)
|
158 |
-
|
159 |
# Display the map and get clicked coordinates
|
160 |
map_data = st_folium(m, height=400, width=700)
|
161 |
-
|
162 |
# Check if a location was clicked
|
163 |
if map_data['last_clicked']:
|
164 |
lat = map_data['last_clicked']['lat']
|
165 |
lng = map_data['last_clicked']['lng']
|
166 |
-
|
167 |
st.write(f"Selected coordinates: {lat:.4f}, {lng:.4f}")
|
168 |
-
|
169 |
# Get nearest Mapillary image
|
170 |
with st.spinner('Fetching street view image...'):
|
171 |
image_data = get_nearest_image(lat, lng)
|
172 |
-
|
173 |
if image_data:
|
174 |
# Display the image
|
175 |
try:
|
176 |
-
|
177 |
-
|
|
|
|
|
|
|
178 |
st.image(image, caption="Street View", width=400)
|
179 |
-
|
180 |
# Add download button
|
181 |
st.download_button(
|
182 |
label="Download Image",
|
@@ -201,7 +316,7 @@ def main():
|
|
201 |
k = 40
|
202 |
for cat in categories:
|
203 |
st.write(cat, f'rating = {knn_get_score(knn, k, cat, vec):.1f}')
|
204 |
-
|
205 |
except Exception as e:
|
206 |
st.error(f"Error displaying image: {str(e)}")
|
207 |
else:
|
|
|
1 |
import streamlit as st
|
2 |
+
import cv2
|
3 |
+
from ultralytics import YOLO # For street detection
|
4 |
import folium
|
5 |
from streamlit_folium import st_folium
|
6 |
import requests
|
|
|
34 |
st.error("Mapillary access token not found. Please configure it in the Space secrets.")
|
35 |
st.stop()
|
36 |
|
37 |
+
def detect_and_crop_street(panorama_url, use_yolo=True):
|
38 |
+
"""
|
39 |
+
Detect streets in a panoramic image and return a cropped normal-sized image
|
40 |
+
Args:
|
41 |
+
panorama_url: URL of the panoramic image
|
42 |
+
use_yolo: Whether to use YOLOv8 (True) or simple edge detection (False)
|
43 |
+
Returns:
|
44 |
+
cropped_image: PIL Image containing the cropped street view
|
45 |
+
"""
|
46 |
+
# Download and convert image to CV2 format
|
47 |
+
response = requests.get(panorama_url)
|
48 |
+
img = Image.open(BytesIO(response.content))
|
49 |
+
cv_img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
|
50 |
+
|
51 |
+
if use_yolo:
|
52 |
+
# Load YOLOv8 model
|
53 |
+
model = YOLO('yolov8n.pt')
|
54 |
+
|
55 |
+
# Detect objects
|
56 |
+
results = model(cv_img)
|
57 |
+
|
58 |
+
# Look for road/street class (index 0 in COCO dataset)
|
59 |
+
street_boxes = []
|
60 |
+
for result in results:
|
61 |
+
for box, cls in zip(result.boxes.xyxy, result.boxes.cls):
|
62 |
+
if cls == 0: # road class
|
63 |
+
street_boxes.append(box.cpu().numpy())
|
64 |
+
|
65 |
+
if street_boxes:
|
66 |
+
# Take the largest street detection
|
67 |
+
largest_box = max(street_boxes, key=lambda box: (box[2]-box[0])*(box[3]-box[1]))
|
68 |
+
x1, y1, x2, y2 = map(int, largest_box)
|
69 |
+
|
70 |
+
# Add some padding
|
71 |
+
padding = 50
|
72 |
+
height, width = cv_img.shape[:2]
|
73 |
+
x1 = max(0, x1 - padding)
|
74 |
+
y1 = max(0, y1 - padding)
|
75 |
+
x2 = min(width, x2 + padding)
|
76 |
+
y2 = min(height, y2 + padding)
|
77 |
+
|
78 |
+
cropped = cv_img[y1:y2, x1:x2]
|
79 |
+
else:
|
80 |
+
# Fallback to edge detection if no streets found
|
81 |
+
cropped = edge_based_crop(cv_img)
|
82 |
+
else:
|
83 |
+
cropped = edge_based_crop(cv_img)
|
84 |
+
|
85 |
+
# Convert back to PIL Image
|
86 |
+
cropped_pil = Image.fromarray(cv2.cvtColor(cropped, cv2.COLOR_BGR2RGB))
|
87 |
+
|
88 |
+
# Resize to standard dimensions while maintaining aspect ratio
|
89 |
+
target_width = 1024
|
90 |
+
aspect_ratio = cropped.shape[1] / cropped.shape[0]
|
91 |
+
target_height = int(target_width / aspect_ratio)
|
92 |
+
cropped_pil = cropped_pil.resize((target_width, target_height), Image.Resampling.LANCZOS)
|
93 |
+
|
94 |
+
return cropped_pil
|
95 |
+
|
96 |
+
def edge_based_crop(cv_img):
|
97 |
+
"""
|
98 |
+
Use edge detection to find and crop around street areas
|
99 |
+
"""
|
100 |
+
# Convert to grayscale
|
101 |
+
gray = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
|
102 |
+
|
103 |
+
# Apply Gaussian blur
|
104 |
+
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
|
105 |
+
|
106 |
+
# Detect edges
|
107 |
+
edges = cv2.Canny(blurred, 50, 150)
|
108 |
+
|
109 |
+
# Find contours
|
110 |
+
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
111 |
+
|
112 |
+
if contours:
|
113 |
+
# Find the largest contour
|
114 |
+
largest_contour = max(contours, key=cv2.contourArea)
|
115 |
+
x, y, w, h = cv2.boundingRect(largest_contour)
|
116 |
+
|
117 |
+
# Add padding
|
118 |
+
padding = 50
|
119 |
+
height, width = cv_img.shape[:2]
|
120 |
+
x = max(0, x - padding)
|
121 |
+
y = max(0, y - padding)
|
122 |
+
w = min(width - x, w + 2*padding)
|
123 |
+
h = min(height - y, h + 2*padding)
|
124 |
+
|
125 |
+
return cv_img[y:y+h, x:x+w]
|
126 |
+
else:
|
127 |
+
# If no contours found, return center crop
|
128 |
+
height, width = cv_img.shape[:2]
|
129 |
+
center_x = width // 2
|
130 |
+
center_y = height // 2
|
131 |
+
crop_width = width // 3
|
132 |
+
crop_height = height // 3
|
133 |
+
return cv_img[center_y-crop_height//2:center_y+crop_height//2,
|
134 |
+
center_x-crop_width//2:center_x+crop_width//2]
|
135 |
+
|
136 |
+
# Example usage in your Streamlit app:
|
137 |
+
def process_panorama(panorama_url):
|
138 |
+
"""
|
139 |
+
Process a panoramic image to get a street-centered crop
|
140 |
+
"""
|
141 |
+
try:
|
142 |
+
cropped_image = detect_and_crop_street(panorama_url)
|
143 |
+
return cropped_image
|
144 |
+
except Exception as e:
|
145 |
+
st.error(f"Error processing panorama: {str(e)}")
|
146 |
+
return None
|
147 |
+
|
148 |
def get_bounding_box(lat, lon):
|
149 |
"""
|
150 |
Create a bounding box around a point that extends roughly 25 meters in each direction
|
|
|
167 |
"""
|
168 |
bbox = get_bounding_box(lat, lon)
|
169 |
params = {
|
170 |
+
'fields': 'id,thumb_1024_url,is_pano',
|
171 |
'limit': 1,
|
|
|
172 |
'bbox': f'{bbox[0]},{bbox[1]},{bbox[2]},{bbox[3]}'
|
173 |
}
|
174 |
|
|
|
181 |
)
|
182 |
response.raise_for_status()
|
183 |
data = response.json()
|
184 |
+
|
185 |
if 'data' in data and len(data['data']) > 0:
|
186 |
return data['data'][0]
|
187 |
return None
|
188 |
+
|
189 |
except requests.exceptions.RequestException as e:
|
190 |
st.error(f"Error fetching Mapillary data: {str(e)}")
|
191 |
return None
|
|
|
244 |
|
245 |
def main():
|
246 |
st.title("Percept: Map Explorer")
|
247 |
+
|
248 |
try:
|
249 |
with st.spinner('Loading CLIP model... This may take a moment.'):
|
250 |
model, preprocess, tokenizer = load_model()
|
|
|
260 |
# Initialize the map centered on Amsterdam
|
261 |
amsterdam_coords = [52.3676, 4.9041]
|
262 |
m = folium.Map(location=amsterdam_coords, zoom_start=13)
|
263 |
+
|
264 |
# Add a marker for Amsterdam city center
|
265 |
folium.Marker(
|
266 |
amsterdam_coords,
|
267 |
popup="Amsterdam City Center",
|
268 |
icon=folium.Icon(color="red", icon="info-sign")
|
269 |
).add_to(m)
|
270 |
+
|
271 |
# Display the map and get clicked coordinates
|
272 |
map_data = st_folium(m, height=400, width=700)
|
273 |
+
|
274 |
# Check if a location was clicked
|
275 |
if map_data['last_clicked']:
|
276 |
lat = map_data['last_clicked']['lat']
|
277 |
lng = map_data['last_clicked']['lng']
|
278 |
+
|
279 |
st.write(f"Selected coordinates: {lat:.4f}, {lng:.4f}")
|
280 |
+
|
281 |
# Get nearest Mapillary image
|
282 |
with st.spinner('Fetching street view image...'):
|
283 |
image_data = get_nearest_image(lat, lng)
|
284 |
+
|
285 |
if image_data:
|
286 |
# Display the image
|
287 |
try:
|
288 |
+
if image_data['is_pano']:
|
289 |
+
image = process_panorama(image_data['thumb_1024_url'])
|
290 |
+
else:
|
291 |
+
response = requests.get(image_data['thumb_1024_url'])
|
292 |
+
image = Image.open(BytesIO(response.content))
|
293 |
st.image(image, caption="Street View", width=400)
|
294 |
+
|
295 |
# Add download button
|
296 |
st.download_button(
|
297 |
label="Download Image",
|
|
|
316 |
k = 40
|
317 |
for cat in categories:
|
318 |
st.write(cat, f'rating = {knn_get_score(knn, k, cat, vec):.1f}')
|
319 |
+
|
320 |
except Exception as e:
|
321 |
st.error(f"Error displaying image: {str(e)}")
|
322 |
else:
|
requirements.txt
CHANGED
@@ -5,3 +5,6 @@ Pillow
|
|
5 |
requests
|
6 |
open_clip_torch
|
7 |
scikit-learn
|
|
|
|
|
|
|
|
5 |
requests
|
6 |
open_clip_torch
|
7 |
scikit-learn
|
8 |
+
opencv-python-headless
|
9 |
+
ultralytics
|
10 |
+
numpy
|