Spaces:
Sleeping
Sleeping
root16285 commited on
Commit ·
ebfcb4b
1
Parent(s): 1f3d9ce
Fix: Use ultralytics YOLO API instead of DetectMultiBackend to avoid export module dependency
Browse files- webapp/backend/main.py +50 -72
webapp/backend/main.py
CHANGED
|
@@ -24,24 +24,16 @@ from fastapi.responses import JSONResponse, HTMLResponse, FileResponse
|
|
| 24 |
from fastapi.staticfiles import StaticFiles
|
| 25 |
from PIL import Image
|
| 26 |
|
| 27 |
-
# YOLOv5 imports -
|
| 28 |
import sys
|
| 29 |
|
| 30 |
-
#
|
| 31 |
-
YOLOV5_ROOT = Path(__file__).resolve().parents[2]
|
| 32 |
-
if str(YOLOV5_ROOT) not in sys.path:
|
| 33 |
-
sys.path.insert(0, str(YOLOV5_ROOT))
|
| 34 |
-
|
| 35 |
-
# Import YOLOv5 modules
|
| 36 |
try:
|
| 37 |
-
from
|
| 38 |
-
from utils.general import non_max_suppression, scale_boxes, check_img_size
|
| 39 |
-
from utils.torch_utils import select_device
|
| 40 |
-
from utils.augmentations import letterbox
|
| 41 |
YOLOV5_AVAILABLE = True
|
| 42 |
-
print("✅
|
| 43 |
except Exception as e:
|
| 44 |
-
print(f"❌ Erreur d'import
|
| 45 |
YOLOV5_AVAILABLE = False
|
| 46 |
|
| 47 |
# Initialize FastAPI app
|
|
@@ -188,34 +180,34 @@ manager = ConnectionManager()
|
|
| 188 |
|
| 189 |
|
| 190 |
def load_model(model_name: str = "yolov5s"):
|
| 191 |
-
"""Load YOLOv5 model
|
| 192 |
global device
|
| 193 |
|
| 194 |
if not YOLOV5_AVAILABLE:
|
| 195 |
-
raise Exception("
|
| 196 |
|
| 197 |
# Initialize device on first load
|
| 198 |
if device is None:
|
| 199 |
-
device =
|
| 200 |
print(f"📊 Device sélectionné: {device}")
|
| 201 |
|
| 202 |
if model_name not in models_cache:
|
| 203 |
try:
|
| 204 |
print(f"🔄 Chargement du modèle {model_name}...")
|
| 205 |
|
| 206 |
-
#
|
| 207 |
-
model_path =
|
| 208 |
-
|
| 209 |
-
if not model_path.exists():
|
| 210 |
-
print(f"⚠️ {model_path} non trouvé, utilisation de yolov5s.pt")
|
| 211 |
-
model_path = YOLOV5_ROOT / "yolov5s.pt"
|
| 212 |
-
|
| 213 |
-
if not model_path.exists():
|
| 214 |
-
raise FileNotFoundError(f"Modèle non trouvé: {model_path}. Téléchargez-le depuis https://github.com/ultralytics/yolov5/releases")
|
| 215 |
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
|
| 220 |
models_cache[model_name] = model
|
| 221 |
print(f"✅ Modèle {model_name} chargé avec succès!")
|
|
@@ -234,60 +226,40 @@ def load_model(model_name: str = "yolov5s"):
|
|
| 234 |
|
| 235 |
|
| 236 |
def process_image(image: Image.Image, model_name: str = "yolov5s", conf_threshold: float = 0.25):
|
| 237 |
-
"""Process image and return detections"""
|
| 238 |
start_time = time.time()
|
| 239 |
|
| 240 |
try:
|
| 241 |
# Load model
|
| 242 |
model = load_model(model_name)
|
| 243 |
|
| 244 |
-
#
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
# Handle different image formats
|
| 248 |
-
if img.ndim == 2: # Grayscale
|
| 249 |
-
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
|
| 250 |
-
elif img.shape[2] == 4: # RGBA
|
| 251 |
-
img = cv2.cvtColor(img, cv2.COLOR_RGBA2RGB)
|
| 252 |
-
|
| 253 |
-
img_bgr = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
|
| 254 |
-
|
| 255 |
-
# Resize and letterbox
|
| 256 |
-
img_size = 640
|
| 257 |
-
h0, w0 = img_bgr.shape[:2]
|
| 258 |
-
r = img_size / max(h0, w0)
|
| 259 |
-
if r != 1:
|
| 260 |
-
img_bgr = cv2.resize(img_bgr, (int(w0 * r), int(h0 * r)), interpolation=cv2.INTER_LINEAR)
|
| 261 |
-
|
| 262 |
-
# Letterbox
|
| 263 |
-
img_letter, ratio, (dw, dh) = letterbox(img_bgr, img_size, auto=False, scaleup=True)
|
| 264 |
-
|
| 265 |
-
# Convert to tensor
|
| 266 |
-
img_tensor = torch.from_numpy(img_letter).to(device)
|
| 267 |
-
img_tensor = img_tensor.permute(2, 0, 1).float() / 255.0
|
| 268 |
-
if img_tensor.ndimension() == 3:
|
| 269 |
-
img_tensor = img_tensor.unsqueeze(0)
|
| 270 |
-
|
| 271 |
-
# Inference
|
| 272 |
-
with torch.no_grad():
|
| 273 |
-
pred = model(img_tensor)
|
| 274 |
|
| 275 |
-
#
|
| 276 |
-
|
| 277 |
|
| 278 |
-
# Process
|
| 279 |
detections = []
|
| 280 |
-
img_display =
|
| 281 |
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
|
|
|
|
|
|
| 285 |
|
| 286 |
-
for
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
class_name = CLASS_NAMES_FR.get(class_name_en, class_name_en)
|
| 292 |
|
| 293 |
detections.append({
|
|
@@ -297,7 +269,7 @@ def process_image(image: Image.Image, model_name: str = "yolov5s", conf_threshol
|
|
| 297 |
"class_id": class_id
|
| 298 |
})
|
| 299 |
|
| 300 |
-
# Draw on image
|
| 301 |
cv2.rectangle(img_display, (x1, y1), (x2, y2), (0, 255, 0), 2)
|
| 302 |
label = f"{class_name} {confidence:.2f}"
|
| 303 |
(label_w, label_h), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 2)
|
|
@@ -332,6 +304,12 @@ def process_image(image: Image.Image, model_name: str = "yolov5s", conf_threshol
|
|
| 332 |
"processing_time": time.time() - start_time,
|
| 333 |
"timestamp": datetime.now().isoformat()
|
| 334 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
|
| 336 |
|
| 337 |
def update_statistics(detections: List[dict], processing_time: float):
|
|
|
|
| 24 |
from fastapi.staticfiles import StaticFiles
|
| 25 |
from PIL import Image
|
| 26 |
|
| 27 |
+
# YOLOv5 imports - Use ultralytics package (more reliable for deployment)
|
| 28 |
import sys
|
| 29 |
|
| 30 |
+
# Import YOLOv5 via ultralytics package
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
try:
|
| 32 |
+
from ultralytics import YOLO
|
|
|
|
|
|
|
|
|
|
| 33 |
YOLOV5_AVAILABLE = True
|
| 34 |
+
print("✅ Ultralytics YOLO importé avec succès")
|
| 35 |
except Exception as e:
|
| 36 |
+
print(f"❌ Erreur d'import Ultralytics: {e}")
|
| 37 |
YOLOV5_AVAILABLE = False
|
| 38 |
|
| 39 |
# Initialize FastAPI app
|
|
|
|
| 180 |
|
| 181 |
|
| 182 |
def load_model(model_name: str = "yolov5s"):
|
| 183 |
+
"""Load YOLOv5 model using ultralytics"""
|
| 184 |
global device
|
| 185 |
|
| 186 |
if not YOLOV5_AVAILABLE:
|
| 187 |
+
raise Exception("Ultralytics YOLO non disponible - vérifiez l'installation")
|
| 188 |
|
| 189 |
# Initialize device on first load
|
| 190 |
if device is None:
|
| 191 |
+
device = 'cuda' if torch.cuda.is_available() else 'cpu'
|
| 192 |
print(f"📊 Device sélectionné: {device}")
|
| 193 |
|
| 194 |
if model_name not in models_cache:
|
| 195 |
try:
|
| 196 |
print(f"🔄 Chargement du modèle {model_name}...")
|
| 197 |
|
| 198 |
+
# Try to load from local file first
|
| 199 |
+
model_path = f"/app/{model_name}.pt"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
|
| 201 |
+
if not os.path.exists(model_path):
|
| 202 |
+
# Try in root directory
|
| 203 |
+
model_path = f"{model_name}.pt"
|
| 204 |
+
|
| 205 |
+
if not os.path.exists(model_path):
|
| 206 |
+
# Download from ultralytics hub
|
| 207 |
+
print(f"⚠️ {model_path} non trouvé, téléchargement depuis ultralytics...")
|
| 208 |
+
model = YOLO(f"{model_name}.pt")
|
| 209 |
+
else:
|
| 210 |
+
model = YOLO(model_path)
|
| 211 |
|
| 212 |
models_cache[model_name] = model
|
| 213 |
print(f"✅ Modèle {model_name} chargé avec succès!")
|
|
|
|
| 226 |
|
| 227 |
|
| 228 |
def process_image(image: Image.Image, model_name: str = "yolov5s", conf_threshold: float = 0.25):
|
| 229 |
+
"""Process image and return detections using ultralytics API"""
|
| 230 |
start_time = time.time()
|
| 231 |
|
| 232 |
try:
|
| 233 |
# Load model
|
| 234 |
model = load_model(model_name)
|
| 235 |
|
| 236 |
+
# Convert PIL to numpy array
|
| 237 |
+
img_array = np.array(image)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
|
| 239 |
+
# Run inference using ultralytics API
|
| 240 |
+
results = model(img_array, conf=conf_threshold, iou=0.45, verbose=False)
|
| 241 |
|
| 242 |
+
# Process results
|
| 243 |
detections = []
|
| 244 |
+
img_display = img_array.copy()
|
| 245 |
|
| 246 |
+
# Get the first result (single image)
|
| 247 |
+
result = results[0]
|
| 248 |
+
|
| 249 |
+
if result.boxes is not None and len(result.boxes) > 0:
|
| 250 |
+
boxes = result.boxes
|
| 251 |
|
| 252 |
+
for i in range(len(boxes)):
|
| 253 |
+
# Get box coordinates (xyxy format)
|
| 254 |
+
box = boxes.xyxy[i].cpu().numpy()
|
| 255 |
+
x1, y1, x2, y2 = map(int, box)
|
| 256 |
+
|
| 257 |
+
# Get confidence and class
|
| 258 |
+
confidence = float(boxes.conf[i].cpu().numpy())
|
| 259 |
+
class_id = int(boxes.cls[i].cpu().numpy())
|
| 260 |
+
|
| 261 |
+
# Get class name
|
| 262 |
+
class_name_en = result.names[class_id]
|
| 263 |
class_name = CLASS_NAMES_FR.get(class_name_en, class_name_en)
|
| 264 |
|
| 265 |
detections.append({
|
|
|
|
| 269 |
"class_id": class_id
|
| 270 |
})
|
| 271 |
|
| 272 |
+
# Draw on image
|
| 273 |
cv2.rectangle(img_display, (x1, y1), (x2, y2), (0, 255, 0), 2)
|
| 274 |
label = f"{class_name} {confidence:.2f}"
|
| 275 |
(label_w, label_h), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 2)
|
|
|
|
| 304 |
"processing_time": time.time() - start_time,
|
| 305 |
"timestamp": datetime.now().isoformat()
|
| 306 |
}
|
| 307 |
+
return {
|
| 308 |
+
"detections": [],
|
| 309 |
+
"image": image,
|
| 310 |
+
"processing_time": time.time() - start_time,
|
| 311 |
+
"timestamp": datetime.now().isoformat()
|
| 312 |
+
}
|
| 313 |
|
| 314 |
|
| 315 |
def update_statistics(detections: List[dict], processing_time: float):
|