Update app.py
Browse files
app.py
CHANGED
@@ -15,21 +15,30 @@ app.secret_key = 'your_secret_key' # Replace with a secure secret key
|
|
15 |
#########################################
|
16 |
|
17 |
# --- Roboflow Box Detection Model ---
|
18 |
-
API_KEY = "wLjPoPYaLmrqCIOFA0RH"
|
19 |
-
PROJECT_ID = "base-model-box-r4suo-8lkk1-6dbqh"
|
20 |
-
VERSION_NUMBER = "2"
|
21 |
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
|
|
|
|
|
|
|
|
|
|
27 |
|
28 |
# --- YOLOv5 Pretrained Model for Persons & Cars ---
|
29 |
-
|
30 |
-
yolov5_model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True)
|
31 |
-
#
|
32 |
-
YOLO_FILTER_CLASSES = {"person", "car"}
|
|
|
|
|
|
|
|
|
33 |
|
34 |
#########################################
|
35 |
# 2. Helper Functions
|
@@ -63,55 +72,68 @@ def custom_nms(preds, iou_threshold=0.3):
|
|
63 |
return filtered_preds
|
64 |
|
65 |
def process_image(image_path):
|
66 |
-
|
67 |
-
Process the uploaded image using both detection pipelines:
|
68 |
-
(a) Box detection via Roboflow (with measurement using an ArUco marker).
|
69 |
-
(b) YOLOv5 detection for persons and cars.
|
70 |
-
Returns the annotated image and a list of detection info dictionaries.
|
71 |
-
"""
|
72 |
image = cv2.imread(image_path)
|
73 |
if image is None:
|
|
|
74 |
return None, "Could not read the image."
|
|
|
75 |
img_height, img_width = image.shape[:2]
|
76 |
-
|
77 |
detection_info = [] # List to hold all detection results for display
|
78 |
|
79 |
# --- (a) Roboflow Box Detection & Measurement ---
|
80 |
-
|
81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
processed_preds = []
|
83 |
for prediction in predictions:
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
|
|
|
|
|
|
|
|
|
|
99 |
box_detections = custom_nms(processed_preds, iou_threshold=0.3)
|
100 |
-
|
101 |
# Detect ArUco marker for measurement (only applicable for boxes)
|
102 |
marker_real_width_cm = 10.0 # The marker is 10cm x 10cm
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
|
|
|
|
|
|
|
|
115 |
conversion_factor = None
|
116 |
|
117 |
# Draw box detections and record measurement info (only for boxes)
|
@@ -144,32 +166,38 @@ def process_image(image_path):
|
|
144 |
(text_width, text_height), baseline = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
|
145 |
cv2.rectangle(image, (x1, y1 - text_height - baseline - 5), (x1 + text_width, y1 - 5), (0, 255, 0), -1)
|
146 |
cv2.putText(image, text, (x1, y1 - 5 - baseline), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)
|
147 |
-
|
148 |
# --- (b) YOLOv5 for Persons & Cars ---
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
173 |
# --- Build Top Summary Text ---
|
174 |
detection_counts = Counter(det["class"] for det in detection_info)
|
175 |
if detection_counts:
|
@@ -177,7 +205,7 @@ def process_image(image_path):
|
|
177 |
(info_width, info_height), info_baseline = cv2.getTextSize(top_text, cv2.FONT_HERSHEY_SIMPLEX, 1, 2)
|
178 |
cv2.rectangle(image, (5, 5), (5 + info_width, 5 + info_height + info_baseline), (0, 255, 0), -1)
|
179 |
cv2.putText(image, top_text, (5, 5 + info_height), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2)
|
180 |
-
|
181 |
return image, detection_info
|
182 |
|
183 |
#########################################
|
@@ -197,14 +225,22 @@ def index():
|
|
197 |
flash('No selected file')
|
198 |
return redirect(request.url)
|
199 |
upload_path = "uploaded.jpg"
|
200 |
-
|
|
|
|
|
|
|
|
|
|
|
201 |
processed_image, detection_info = process_image(upload_path)
|
202 |
if processed_image is None:
|
203 |
-
flash("Error
|
204 |
else:
|
205 |
retval, buffer = cv2.imencode('.jpg', processed_image)
|
206 |
image_data = base64.b64encode(buffer).decode('utf-8')
|
207 |
-
|
|
|
|
|
|
|
208 |
return render_template_string('''
|
209 |
<!doctype html>
|
210 |
<html>
|
@@ -334,4 +370,5 @@ def index():
|
|
334 |
#########################################
|
335 |
|
336 |
if __name__ == '__main__':
|
|
|
337 |
app.run(host="0.0.0.0", port=7860)
|
|
|
15 |
#########################################
|
16 |
|
17 |
# --- Roboflow Box Detection Model ---
|
18 |
+
API_KEY = "wLjPoPYaLmrqCIOFA0RH" # Your Roboflow API key
|
19 |
+
PROJECT_ID = "base-model-box-r4suo-8lkk1-6dbqh" # Your Roboflow project ID
|
20 |
+
VERSION_NUMBER = "2" # Your trained model version number
|
21 |
|
22 |
+
try:
|
23 |
+
rf = roboflow.Roboflow(api_key=API_KEY)
|
24 |
+
workspace = rf.workspace()
|
25 |
+
project = workspace.project(PROJECT_ID)
|
26 |
+
version = project.version(VERSION_NUMBER)
|
27 |
+
box_model = version.model # This model is trained for detecting boxes
|
28 |
+
print("Roboflow model loaded successfully.")
|
29 |
+
except Exception as e:
|
30 |
+
print("Error initializing Roboflow model:", e)
|
31 |
+
box_model = None
|
32 |
|
33 |
# --- YOLOv5 Pretrained Model for Persons & Cars ---
|
34 |
+
try:
|
35 |
+
yolov5_model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True)
|
36 |
+
# Filter YOLO detections to only include persons and cars.
|
37 |
+
YOLO_FILTER_CLASSES = {"person", "car"}
|
38 |
+
print("YOLOv5 model loaded successfully.")
|
39 |
+
except Exception as e:
|
40 |
+
print("Error loading YOLOv5 model:", e)
|
41 |
+
yolov5_model = None
|
42 |
|
43 |
#########################################
|
44 |
# 2. Helper Functions
|
|
|
72 |
return filtered_preds
|
73 |
|
74 |
def process_image(image_path):
|
75 |
+
# Load image
|
|
|
|
|
|
|
|
|
|
|
76 |
image = cv2.imread(image_path)
|
77 |
if image is None:
|
78 |
+
print("DEBUG: cv2.imread failed to load the image from", image_path)
|
79 |
return None, "Could not read the image."
|
80 |
+
|
81 |
img_height, img_width = image.shape[:2]
|
|
|
82 |
detection_info = [] # List to hold all detection results for display
|
83 |
|
84 |
# --- (a) Roboflow Box Detection & Measurement ---
|
85 |
+
if box_model is None:
|
86 |
+
print("DEBUG: Roboflow model is not initialized.")
|
87 |
+
return None, "Roboflow model is not available."
|
88 |
+
try:
|
89 |
+
results = box_model.predict(image_path, confidence=50, overlap=30).json()
|
90 |
+
predictions = results.get("predictions", [])
|
91 |
+
except Exception as e:
|
92 |
+
print("DEBUG: Error during Roboflow prediction:", e)
|
93 |
+
return None, "Error during Roboflow prediction."
|
94 |
+
|
95 |
processed_preds = []
|
96 |
for prediction in predictions:
|
97 |
+
try:
|
98 |
+
x, y, width, height = prediction["x"], prediction["y"], prediction["width"], prediction["height"]
|
99 |
+
x1 = int(round(x - width / 2))
|
100 |
+
y1 = int(round(y - height / 2))
|
101 |
+
x2 = int(round(x + width / 2))
|
102 |
+
y2 = int(round(y + height / 2))
|
103 |
+
# Clamp coordinates to image dimensions
|
104 |
+
x1 = max(0, min(x1, img_width - 1))
|
105 |
+
y1 = max(0, min(y1, img_height - 1))
|
106 |
+
x2 = max(0, min(x2, img_width - 1))
|
107 |
+
y2 = max(0, min(y2, img_height - 1))
|
108 |
+
processed_preds.append({
|
109 |
+
"box": (x1, y1, x2, y2),
|
110 |
+
"class": prediction["class"],
|
111 |
+
"confidence": prediction["confidence"]
|
112 |
+
})
|
113 |
+
except Exception as e:
|
114 |
+
print("DEBUG: Error processing a prediction:", e)
|
115 |
+
continue
|
116 |
+
|
117 |
box_detections = custom_nms(processed_preds, iou_threshold=0.3)
|
118 |
+
|
119 |
# Detect ArUco marker for measurement (only applicable for boxes)
|
120 |
marker_real_width_cm = 10.0 # The marker is 10cm x 10cm
|
121 |
+
try:
|
122 |
+
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
123 |
+
aruco_dict = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_6X6_250)
|
124 |
+
aruco_params = cv2.aruco.DetectorParameters()
|
125 |
+
corners, ids, _ = cv2.aruco.detectMarkers(gray, aruco_dict, parameters=aruco_params)
|
126 |
+
if ids is not None and len(corners) > 0:
|
127 |
+
marker_corners = corners[0].reshape((4, 2))
|
128 |
+
cv2.aruco.drawDetectedMarkers(image, corners, ids)
|
129 |
+
marker_width_pixels = np.linalg.norm(marker_corners[0] - marker_corners[1])
|
130 |
+
marker_height_pixels = np.linalg.norm(marker_corners[1] - marker_corners[2])
|
131 |
+
marker_pixel_size = (marker_width_pixels + marker_height_pixels) / 2.0
|
132 |
+
conversion_factor = marker_real_width_cm / marker_pixel_size
|
133 |
+
else:
|
134 |
+
conversion_factor = None
|
135 |
+
except Exception as e:
|
136 |
+
print("DEBUG: Error during ArUco detection:", e)
|
137 |
conversion_factor = None
|
138 |
|
139 |
# Draw box detections and record measurement info (only for boxes)
|
|
|
166 |
(text_width, text_height), baseline = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
|
167 |
cv2.rectangle(image, (x1, y1 - text_height - baseline - 5), (x1 + text_width, y1 - 5), (0, 255, 0), -1)
|
168 |
cv2.putText(image, text, (x1, y1 - 5 - baseline), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)
|
169 |
+
|
170 |
# --- (b) YOLOv5 for Persons & Cars ---
|
171 |
+
if yolov5_model is None:
|
172 |
+
print("DEBUG: YOLOv5 model is not initialized.")
|
173 |
+
else:
|
174 |
+
try:
|
175 |
+
img_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
176 |
+
yolo_results = yolov5_model(img_rgb)
|
177 |
+
df = yolo_results.pandas().xyxy[0]
|
178 |
+
for _, row in df.iterrows():
|
179 |
+
if row['name'] in YOLO_FILTER_CLASSES:
|
180 |
+
xmin = int(row['xmin'])
|
181 |
+
ymin = int(row['ymin'])
|
182 |
+
xmax = int(row['xmax'])
|
183 |
+
ymax = int(row['ymax'])
|
184 |
+
conf = row['confidence']
|
185 |
+
label = row['name']
|
186 |
+
cv2.rectangle(image, (xmin, ymin), (xmax, ymax), (255, 0, 0), 2)
|
187 |
+
text = f"{label} ({conf:.2f})"
|
188 |
+
(text_width, text_height), baseline = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
|
189 |
+
cv2.rectangle(image, (xmin, ymin - text_height - baseline - 5), (xmin + text_width, ymin - 5), (255, 0, 0), -1)
|
190 |
+
cv2.putText(image, text, (xmin, ymin - 5 - baseline), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)
|
191 |
+
detection_info.append({
|
192 |
+
"class": label,
|
193 |
+
"confidence": f"{conf:.2f}",
|
194 |
+
"width_cm": "N/A",
|
195 |
+
"height_cm": "N/A"
|
196 |
+
})
|
197 |
+
except Exception as e:
|
198 |
+
print("DEBUG: Error during YOLOv5 inference:", e)
|
199 |
+
return None, "Error during YOLOv5 inference."
|
200 |
+
|
201 |
# --- Build Top Summary Text ---
|
202 |
detection_counts = Counter(det["class"] for det in detection_info)
|
203 |
if detection_counts:
|
|
|
205 |
(info_width, info_height), info_baseline = cv2.getTextSize(top_text, cv2.FONT_HERSHEY_SIMPLEX, 1, 2)
|
206 |
cv2.rectangle(image, (5, 5), (5 + info_width, 5 + info_height + info_baseline), (0, 255, 0), -1)
|
207 |
cv2.putText(image, top_text, (5, 5 + info_height), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2)
|
208 |
+
|
209 |
return image, detection_info
|
210 |
|
211 |
#########################################
|
|
|
225 |
flash('No selected file')
|
226 |
return redirect(request.url)
|
227 |
upload_path = "uploaded.jpg"
|
228 |
+
try:
|
229 |
+
file.save(upload_path)
|
230 |
+
except Exception as e:
|
231 |
+
print("DEBUG: Error saving uploaded file:", e)
|
232 |
+
flash("Error saving uploaded file.")
|
233 |
+
return redirect(request.url)
|
234 |
processed_image, detection_info = process_image(upload_path)
|
235 |
if processed_image is None:
|
236 |
+
flash("Error Processing Image: " + detection_info)
|
237 |
else:
|
238 |
retval, buffer = cv2.imencode('.jpg', processed_image)
|
239 |
image_data = base64.b64encode(buffer).decode('utf-8')
|
240 |
+
try:
|
241 |
+
os.remove(upload_path)
|
242 |
+
except Exception as e:
|
243 |
+
print("DEBUG: Error removing uploaded file:", e)
|
244 |
return render_template_string('''
|
245 |
<!doctype html>
|
246 |
<html>
|
|
|
370 |
#########################################
|
371 |
|
372 |
if __name__ == '__main__':
|
373 |
+
# Ensure the app runs on 0.0.0.0 and port 7860 for Hugging Face Spaces.
|
374 |
app.run(host="0.0.0.0", port=7860)
|