Intern-4
commited on
Commit
·
fc25810
1
Parent(s):
c02898e
exiftool installation
Browse files- apt.txt +1 -0
- gradio_with_map.py +57 -90
apt.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
libimage-exiftool-perl
|
gradio_with_map.py
CHANGED
@@ -3,13 +3,13 @@ import gradio as gr
|
|
3 |
import PIL.Image as Image
|
4 |
import numpy as np
|
5 |
import cv2
|
6 |
-
from ultralytics import
|
|
|
7 |
from geopy.distance import geodesic
|
8 |
import folium
|
9 |
import base64
|
10 |
import supervision as sv
|
11 |
import os
|
12 |
-
from PIL.ExifTags import TAGS, GPSTAGS
|
13 |
|
14 |
# Constants for image dimensions
|
15 |
IMAGE_WIDTH = 4000
|
@@ -19,9 +19,13 @@ IMAGE_HEIGHT = 3000
|
|
19 |
model = YOLOv10("weights/best.pt")
|
20 |
|
21 |
# Define the directory for saving uploaded images
|
22 |
-
UPLOAD_DIR = 'uploads' # Or any other directory within your project
|
23 |
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
24 |
|
|
|
|
|
|
|
|
|
25 |
|
26 |
# Function to calculate ground distance from pixel distance
|
27 |
def calculate_ground_distance(altitude, fov_deg, image_dimension, pixel_distance):
|
@@ -29,14 +33,12 @@ def calculate_ground_distance(altitude, fov_deg, image_dimension, pixel_distance
|
|
29 |
ground_distance = (2 * altitude * np.tan(fov_rad / 2)) * (pixel_distance / image_dimension)
|
30 |
return ground_distance
|
31 |
|
32 |
-
|
33 |
# Function to get GPS coordinates from offsets
|
34 |
def get_gps_coordinates(lat, lon, north_offset, east_offset):
|
35 |
new_location = geodesic(meters=north_offset).destination((lat, lon), 0)
|
36 |
new_location = geodesic(meters=east_offset).destination(new_location, 90)
|
37 |
return new_location.latitude, new_location.longitude
|
38 |
|
39 |
-
|
40 |
def extract_xmp_metadata(xmp_data):
|
41 |
# Parse the XMP data as an XML tree
|
42 |
root = ET.fromstring(xmp_data)
|
@@ -57,65 +59,10 @@ def extract_xmp_metadata(xmp_data):
|
|
57 |
|
58 |
return relative_altitude, gimbal_yaw_degree, gimbal_pitch_degree
|
59 |
|
60 |
-
|
61 |
-
def extract_gps_info(exif_data):
|
62 |
-
"""Extract GPS information from the EXIF data."""
|
63 |
-
gps_info = {}
|
64 |
-
for tag, value in exif_data.items():
|
65 |
-
decoded = TAGS.get(tag, tag)
|
66 |
-
if decoded == 'GPSInfo':
|
67 |
-
for t in value:
|
68 |
-
sub_decoded = GPSTAGS.get(t, t)
|
69 |
-
gps_info[sub_decoded] = value[t]
|
70 |
-
return gps_info
|
71 |
-
|
72 |
-
def extract_fov_info(exif_data):
|
73 |
-
"""Extract FOV information from the EXIF data."""
|
74 |
-
fov_info = {}
|
75 |
-
|
76 |
-
for tag, value in exif_data.items():
|
77 |
-
decoded = TAGS.get(tag, tag)
|
78 |
-
|
79 |
-
# Check for known FOV-related tags
|
80 |
-
if decoded == 'FocalLength': # Example tag for focal length
|
81 |
-
focal_length = value
|
82 |
-
fov_info['FocalLength'] = focal_length
|
83 |
-
|
84 |
-
if decoded == 'FocalLengthIn35mmFilm': # Another example
|
85 |
-
focal_length_35mm = value
|
86 |
-
fov_info['FocalLengthIn35mmFilm'] = focal_length_35mm
|
87 |
-
|
88 |
-
# Add additional checks for custom FOV-related tags if known
|
89 |
-
# if decoded == 'YourCameraSpecificFOVTag':
|
90 |
-
# fov_degree = value
|
91 |
-
# fov_info['FOV'] = fov_degree
|
92 |
-
|
93 |
-
# Calculate FOV if needed (e.g., using focal length and sensor size)
|
94 |
-
if 'FocalLength' in fov_info:
|
95 |
-
# Example calculation for FOV using a known formula:
|
96 |
-
# This example uses a hypothetical sensor size; adjust as needed.
|
97 |
-
sensor_width = 36.0 # mm (example for full-frame sensor)
|
98 |
-
focal_length = fov_info['FocalLength'][0] / fov_info['FocalLength'][1]
|
99 |
-
|
100 |
-
# Calculate horizontal FOV in degrees
|
101 |
-
fov_info['FOV'] = 2 * np.arctan(sensor_width / (2 * focal_length)) * (180 / np.pi)
|
102 |
-
|
103 |
-
return fov_info
|
104 |
-
|
105 |
-
|
106 |
-
def get_decimal_from_dms(dms, ref):
|
107 |
-
"""Convert DMS coordinates to decimal format."""
|
108 |
-
degrees, minutes, seconds = dms
|
109 |
-
decimal = float(degrees) + float(minutes) / 60 + float(seconds) / 3600
|
110 |
-
if ref in ['S', 'W']:
|
111 |
-
decimal = -decimal
|
112 |
-
return decimal
|
113 |
-
|
114 |
-
|
115 |
def save_image_with_metadata(img, img_path):
|
116 |
# Convert PIL Image to a format that retains EXIF
|
117 |
img_format = img.format or 'JPEG'
|
118 |
-
|
119 |
# Save image to a temporary file to preserve metadata
|
120 |
img.save(img_path, format=img_format)
|
121 |
|
@@ -139,30 +86,56 @@ def predict_image(img, conf_threshold, iou_threshold):
|
|
139 |
print(f"Gimbal Pitch Degree: {gimbal_pitch_degree}")
|
140 |
else:
|
141 |
print("XMP data not found in the image.")
|
142 |
-
|
143 |
# Extract EXIF data
|
144 |
exif_data = img.info.get("exif")
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
gps_latitude = get_decimal_from_dms(gps_info['GPSLatitude'], gps_info['GPSLatitudeRef'])
|
152 |
-
gps_longitude = get_decimal_from_dms(gps_info['GPSLongitude'], gps_info['GPSLongitudeRef'])
|
153 |
-
|
154 |
-
CAMERA_GPS = (gps_latitude, gps_longitude)
|
155 |
-
print("Camera GPS:", CAMERA_GPS)
|
156 |
-
else:
|
157 |
-
print("EXIF data not found in the image.")
|
158 |
-
return None, "EXIF data not found."
|
159 |
|
160 |
# Save the image with metadata
|
161 |
-
|
162 |
-
|
|
|
|
|
|
|
163 |
# Convert PIL Image to OpenCV image
|
164 |
img_cv2 = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
|
165 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
166 |
# Perform prediction
|
167 |
results = model.predict(
|
168 |
source=img_cv2,
|
@@ -174,7 +147,7 @@ def predict_image(img, conf_threshold, iou_threshold):
|
|
174 |
)
|
175 |
|
176 |
detections = sv.Detections.from_ultralytics(results[0])
|
177 |
-
|
178 |
# Annotate and display image
|
179 |
for r in results:
|
180 |
im_array = r.plot()
|
@@ -184,7 +157,7 @@ def predict_image(img, conf_threshold, iou_threshold):
|
|
184 |
building_locations = []
|
185 |
for i, box in enumerate(detections.xyxy): # Correct way to iterate through boxes
|
186 |
# Extract bounding box coordinates and class
|
187 |
-
#
|
188 |
x_min, y_min, x_max, y_max = box # Access the first (and only) box
|
189 |
class_id = int(detections.class_id[i]) # Get class ID as an integer
|
190 |
|
@@ -194,17 +167,11 @@ def predict_image(img, conf_threshold, iou_threshold):
|
|
194 |
pixel_distance_x = x_center - IMAGE_WIDTH / 2
|
195 |
pixel_distance_y = IMAGE_HEIGHT / 2 - y_center
|
196 |
|
197 |
-
RELATIVE_ALTITUDE = float(relative_altitude)
|
198 |
-
FOV_HORIZONTAL = float(extract_fov_info(exif_data)[['FOV']])
|
199 |
-
FOV_VERTICAL = FOV_HORIZONTAL * (IMAGE_HEIGHT / IMAGE_WIDTH)
|
200 |
-
|
201 |
ground_distance_x = calculate_ground_distance(RELATIVE_ALTITUDE, FOV_HORIZONTAL, IMAGE_WIDTH, pixel_distance_x)
|
202 |
ground_distance_y = calculate_ground_distance(RELATIVE_ALTITUDE, FOV_VERTICAL, IMAGE_HEIGHT, pixel_distance_y)
|
203 |
|
204 |
-
east_offset = ground_distance_x * np.cos(
|
205 |
-
|
206 |
-
north_offset = ground_distance_x * np.sin(np.radians(gimbal_yaw_degree)) + ground_distance_y * np.cos(
|
207 |
-
np.radians(gimbal_yaw_degree))
|
208 |
|
209 |
building_lat, building_lon = get_gps_coordinates(CAMERA_GPS[0], CAMERA_GPS[1], north_offset, east_offset)
|
210 |
building_locations.append((building_lat, building_lon, class_id))
|
@@ -223,7 +190,7 @@ def predict_image(img, conf_threshold, iou_threshold):
|
|
223 |
|
224 |
folium.Marker(
|
225 |
location=(building_lat, building_lon),
|
226 |
-
popup=f'Building {i
|
227 |
icon=folium.Icon(color='red' if class_id == 1 else 'green', icon='home')
|
228 |
).add_to(m)
|
229 |
|
@@ -231,7 +198,7 @@ def predict_image(img, conf_threshold, iou_threshold):
|
|
231 |
m.save('temp_map.html')
|
232 |
with open('temp_map.html', 'r') as f:
|
233 |
folium_map_html = f.read()
|
234 |
-
|
235 |
encoded_html = base64.b64encode(folium_map_html.encode()).decode('utf-8')
|
236 |
data_url = f"data:text/html;base64,{encoded_html}"
|
237 |
|
|
|
3 |
import PIL.Image as Image
|
4 |
import numpy as np
|
5 |
import cv2
|
6 |
+
from ultralytics import YOLOv10
|
7 |
+
from exiftool import ExifToolHelper
|
8 |
from geopy.distance import geodesic
|
9 |
import folium
|
10 |
import base64
|
11 |
import supervision as sv
|
12 |
import os
|
|
|
13 |
|
14 |
# Constants for image dimensions
|
15 |
IMAGE_WIDTH = 4000
|
|
|
19 |
model = YOLOv10("weights/best.pt")
|
20 |
|
21 |
# Define the directory for saving uploaded images
|
22 |
+
UPLOAD_DIR = './uploads/' # Or any other directory within your project
|
23 |
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
24 |
|
25 |
+
# Debugging: Check exiftool path
|
26 |
+
exiftool_path = os.popen("which exiftool").read().strip()
|
27 |
+
print(f"ExifTool path: {exiftool_path}")
|
28 |
+
|
29 |
|
30 |
# Function to calculate ground distance from pixel distance
|
31 |
def calculate_ground_distance(altitude, fov_deg, image_dimension, pixel_distance):
|
|
|
33 |
ground_distance = (2 * altitude * np.tan(fov_rad / 2)) * (pixel_distance / image_dimension)
|
34 |
return ground_distance
|
35 |
|
|
|
36 |
# Function to get GPS coordinates from offsets
|
37 |
def get_gps_coordinates(lat, lon, north_offset, east_offset):
|
38 |
new_location = geodesic(meters=north_offset).destination((lat, lon), 0)
|
39 |
new_location = geodesic(meters=east_offset).destination(new_location, 90)
|
40 |
return new_location.latitude, new_location.longitude
|
41 |
|
|
|
42 |
def extract_xmp_metadata(xmp_data):
|
43 |
# Parse the XMP data as an XML tree
|
44 |
root = ET.fromstring(xmp_data)
|
|
|
59 |
|
60 |
return relative_altitude, gimbal_yaw_degree, gimbal_pitch_degree
|
61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
def save_image_with_metadata(img, img_path):
|
63 |
# Convert PIL Image to a format that retains EXIF
|
64 |
img_format = img.format or 'JPEG'
|
65 |
+
|
66 |
# Save image to a temporary file to preserve metadata
|
67 |
img.save(img_path, format=img_format)
|
68 |
|
|
|
86 |
print(f"Gimbal Pitch Degree: {gimbal_pitch_degree}")
|
87 |
else:
|
88 |
print("XMP data not found in the image.")
|
89 |
+
|
90 |
# Extract EXIF data
|
91 |
exif_data = img.info.get("exif")
|
92 |
+
try:
|
93 |
+
xmp_data = img.info.get("xmp")
|
94 |
+
#print(xmp_data)
|
95 |
+
except:
|
96 |
+
print("error loading xmp data")
|
97 |
+
#print(exif_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
98 |
|
99 |
# Save the image with metadata
|
100 |
+
if exif_data:
|
101 |
+
img.save(img_path, exif=exif_data) # Save the image with its EXIF data
|
102 |
+
else:
|
103 |
+
img.save(img_path) # Save without EXIF data if not available
|
104 |
+
|
105 |
# Convert PIL Image to OpenCV image
|
106 |
img_cv2 = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
|
107 |
|
108 |
+
# Use ExifTool to extract metadata
|
109 |
+
metadata = {}
|
110 |
+
tag_list = [
|
111 |
+
"Composite:FOV",
|
112 |
+
"Composite:GPSLatitude",
|
113 |
+
"Composite:GPSLongitude",
|
114 |
+
"XMP:AbsoluteAltitude",
|
115 |
+
"XMP:RelativeAltitude",
|
116 |
+
"XMP:GimbalRollDegree",
|
117 |
+
"XMP:GimbalYawDegree",
|
118 |
+
"XMP:GimbalPitchDegree"
|
119 |
+
]
|
120 |
+
|
121 |
+
rel_path = img_path.lstrip("./")
|
122 |
+
#print(rel_path)
|
123 |
+
with ExifToolHelper() as et:
|
124 |
+
for d in et.get_metadata(rel_path):
|
125 |
+
metadata.update({k: v for k, v in d.items() if k in tag_list})
|
126 |
+
|
127 |
+
# Extract necessary metadata
|
128 |
+
CAMERA_GPS = (metadata["Composite:GPSLatitude"], metadata["Composite:GPSLongitude"])
|
129 |
+
RELATIVE_ALTITUDE = float(relative_altitude)
|
130 |
+
GIMBAL_YAW_DEGREE = float(gimbal_yaw_degree)
|
131 |
+
FOV_HORIZONTAL = float(metadata["Composite:FOV"])
|
132 |
+
FOV_VERTICAL = FOV_HORIZONTAL * (IMAGE_HEIGHT / IMAGE_WIDTH)
|
133 |
+
#GIMBAL_PITCH_DEGREE = float(gimbal_pitch_degree)
|
134 |
+
|
135 |
+
# Convert degrees to radians
|
136 |
+
yaw_rad = np.radians(GIMBAL_YAW_DEGREE)
|
137 |
+
#pitch_rad = np.radians(GIMBAL_PITCH_DEGREE)
|
138 |
+
|
139 |
# Perform prediction
|
140 |
results = model.predict(
|
141 |
source=img_cv2,
|
|
|
147 |
)
|
148 |
|
149 |
detections = sv.Detections.from_ultralytics(results[0])
|
150 |
+
|
151 |
# Annotate and display image
|
152 |
for r in results:
|
153 |
im_array = r.plot()
|
|
|
157 |
building_locations = []
|
158 |
for i, box in enumerate(detections.xyxy): # Correct way to iterate through boxes
|
159 |
# Extract bounding box coordinates and class
|
160 |
+
#print(box)
|
161 |
x_min, y_min, x_max, y_max = box # Access the first (and only) box
|
162 |
class_id = int(detections.class_id[i]) # Get class ID as an integer
|
163 |
|
|
|
167 |
pixel_distance_x = x_center - IMAGE_WIDTH / 2
|
168 |
pixel_distance_y = IMAGE_HEIGHT / 2 - y_center
|
169 |
|
|
|
|
|
|
|
|
|
170 |
ground_distance_x = calculate_ground_distance(RELATIVE_ALTITUDE, FOV_HORIZONTAL, IMAGE_WIDTH, pixel_distance_x)
|
171 |
ground_distance_y = calculate_ground_distance(RELATIVE_ALTITUDE, FOV_VERTICAL, IMAGE_HEIGHT, pixel_distance_y)
|
172 |
|
173 |
+
east_offset = ground_distance_x * np.cos(yaw_rad) - ground_distance_y * np.sin(yaw_rad)
|
174 |
+
north_offset = ground_distance_x * np.sin(yaw_rad) + ground_distance_y * np.cos(yaw_rad)
|
|
|
|
|
175 |
|
176 |
building_lat, building_lon = get_gps_coordinates(CAMERA_GPS[0], CAMERA_GPS[1], north_offset, east_offset)
|
177 |
building_locations.append((building_lat, building_lon, class_id))
|
|
|
190 |
|
191 |
folium.Marker(
|
192 |
location=(building_lat, building_lon),
|
193 |
+
popup=f'Building {i+1}: {building_status}',
|
194 |
icon=folium.Icon(color='red' if class_id == 1 else 'green', icon='home')
|
195 |
).add_to(m)
|
196 |
|
|
|
198 |
m.save('temp_map.html')
|
199 |
with open('temp_map.html', 'r') as f:
|
200 |
folium_map_html = f.read()
|
201 |
+
|
202 |
encoded_html = base64.b64encode(folium_map_html.encode()).decode('utf-8')
|
203 |
data_url = f"data:text/html;base64,{encoded_html}"
|
204 |
|