Spaces:
Running
Running
| # -*- coding: utf-8 -*- | |
| import gradio as gr | |
| import cv2 | |
| import numpy as np | |
| import torch | |
| from PIL import Image | |
| from ultralytics import YOLO | |
| from transformers import BlipProcessor, BlipForConditionalGeneration | |
| from huggingface_hub import hf_hub_download | |
| import warnings | |
| warnings.filterwarnings('ignore') | |
| # ============================================================ | |
| # 1. LOAD MODELS (CHANGE USERNAME BELOW) | |
| # ============================================================ | |
| print("Loading YOLOv11 model from Hugging Face Hub...") | |
| # ⚠️ IMPORTANT: Replace "YOUR_USERNAME" with your actual Hugging Face username | |
| model_path = hf_hub_download( | |
| repo_id="SkyGuardAI/drone-detection-yolov11", # <--- CHANGE THIS | |
| filename="best.pt" | |
| ) | |
| model = YOLO(model_path) | |
| print("YOLO model loaded successfully.") | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| print(f"Using device: {device}") | |
| print("Loading BLIP model...") | |
| blip_processor = BlipProcessor.from_pretrained("Salesforce/blip-image-captioning-base") | |
| blip_model = BlipForConditionalGeneration.from_pretrained("Salesforce/blip-image-captioning-base").to(device) | |
| blip_model.eval() | |
| print("BLIP model loaded successfully.") | |
| # ============================================================ | |
| # 2. HEATMAP FUNCTIONS (unchanged) | |
| # ============================================================ | |
| layer_outputs = {} | |
| def hook_fn(module, input, output): | |
| layer_outputs['feature_map'] = output.detach() | |
| def get_best_layer(model): | |
| best_layer = None | |
| best_depth = 0 | |
| pytorch_model = model.model if hasattr(model, 'model') else model | |
| for name, layer in pytorch_model.named_modules(): | |
| if isinstance(layer, torch.nn.Conv2d): | |
| depth = name.count('.') | |
| if depth > best_depth: | |
| best_depth = depth | |
| best_layer = layer | |
| return best_layer | |
| def generate_heatmap(model, image): | |
| try: | |
| layer_outputs.clear() | |
| if isinstance(image, Image.Image): | |
| image = np.array(image) | |
| img_resized = cv2.resize(image, (640, 640)) | |
| pytorch_model = model.model if hasattr(model, 'model') else model | |
| target_layer = get_best_layer(pytorch_model) | |
| if target_layer is None: | |
| return None | |
| hook = target_layer.register_forward_hook(hook_fn) | |
| results = model(img_resized) | |
| hook.remove() | |
| if 'feature_map' not in layer_outputs: | |
| return None | |
| feature_map = layer_outputs['feature_map'] | |
| if feature_map.dim() == 4: | |
| heatmap = feature_map[0].mean(dim=0).cpu().numpy() | |
| else: | |
| heatmap = feature_map.cpu().numpy() | |
| heatmap = cv2.GaussianBlur(heatmap, (5, 5), 0) | |
| min_val = heatmap.min() | |
| max_val = heatmap.max() | |
| if max_val - min_val > 1e-8: | |
| heatmap = (heatmap - min_val) / (max_val - min_val) | |
| else: | |
| heatmap = np.zeros_like(heatmap) | |
| heatmap = cv2.resize(heatmap, (640, 640)) | |
| threshold = np.percentile(heatmap, 70) | |
| heatmap = np.where(heatmap > threshold, heatmap, 0) | |
| if heatmap.max() > 0: | |
| heatmap = heatmap / heatmap.max() | |
| heatmap_colored = cv2.applyColorMap((heatmap * 255).astype(np.uint8), cv2.COLORMAP_JET) | |
| heatmap_colored = cv2.cvtColor(heatmap_colored, cv2.COLOR_BGR2RGB) | |
| overlay = cv2.addWeighted(img_resized, 0.6, heatmap_colored, 0.4, 0) | |
| return overlay | |
| except Exception as e: | |
| print(f"Heatmap error: {e}") | |
| return None | |
| # ============================================================ | |
| # 3. DYNAMIC CAPTION (BLIP) | |
| # ============================================================ | |
| def generate_dynamic_caption(image): | |
| try: | |
| if isinstance(image, np.ndarray): | |
| image = Image.fromarray(image).convert('RGB') | |
| elif isinstance(image, Image.Image): | |
| image = image.convert('RGB') | |
| inputs = blip_processor(image, return_tensors="pt").to(device) | |
| with torch.no_grad(): | |
| out = blip_model.generate( | |
| **inputs, | |
| max_length=60, | |
| num_beams=5, | |
| temperature=0.7, | |
| repetition_penalty=1.2 | |
| ) | |
| caption = blip_processor.decode(out[0], skip_special_tokens=True) | |
| return caption | |
| except Exception as e: | |
| print(f"Caption error: {e}") | |
| return "AI model is analyzing the scene." | |
| # ============================================================ | |
| # 4. XAI REPORT | |
| # ============================================================ | |
| def build_xai_report(is_drone, confidence, drone_count, processing_time, image_caption): | |
| if is_drone: | |
| drone_text = "a drone" if drone_count == 1 else f"{drone_count} drones" | |
| if confidence >= 0.8: | |
| confidence_level = "VERY HIGH" | |
| confidence_assessment = "excellent" | |
| elif confidence >= 0.6: | |
| confidence_level = "HIGH" | |
| confidence_assessment = "good" | |
| elif confidence >= 0.5: | |
| confidence_level = "MODERATE" | |
| confidence_assessment = "acceptable" | |
| else: | |
| confidence_level = "LOW" | |
| confidence_assessment = "limited" | |
| report = f""" | |
| ================================================================================ | |
| XAI DRONE DETECTION REPORT | |
| ================================================================================ | |
| [DYNAMIC IMAGE ANALYSIS] | |
| {image_caption} | |
| [DETECTION RESULTS] | |
| • Status: CONFIRMED | |
| • Confidence: {confidence:.1%} (Level: {confidence_level}) | |
| • Drones Detected: {drone_count} | |
| • Processing Time: {processing_time:.0f} milliseconds | |
| • Model: YOLOv11 | |
| • XAI Method: Convolutional Feature Map Extraction | |
| [XAI HEATMAP INTERPRETATION] | |
| The heatmap shows red regions where the neural network focused its attention. | |
| Strong red activation on {drone_text} confirms the model successfully learned | |
| discriminative features for drone detection. | |
| The model demonstrates {confidence_assessment} confidence, as evidenced by | |
| the concentrated activation pattern in the heatmap. | |
| [TECHNICAL DETAILS] | |
| • Heatmap: Extracted from deepest convolutional layer of YOLOv11 | |
| • Color Code: Red = High activation (model focus) | Blue = Low activation | |
| • Processing: Gaussian blur (5x5 kernel) | |
| • Threshold: Top 30% activation retained | |
| [XAI CONCLUSION] | |
| The YOLOv11 model has successfully detected {drone_text} in this image with | |
| {confidence_assessment} confidence. The heatmap confirms correct feature | |
| learning as the neural network focused on the drone's location. | |
| """ | |
| else: | |
| report = f""" | |
| ================================================================================ | |
| XAI DRONE DETECTION REPORT | |
| ================================================================================ | |
| [DYNAMIC IMAGE ANALYSIS] | |
| {image_caption} | |
| [DETECTION RESULTS] | |
| • Status: NOT CONFIRMED | |
| • Highest Confidence: {confidence:.1%} | |
| • Processing Time: {processing_time:.0f} milliseconds | |
| • Model: YOLOv11 | |
| • XAI Method: Convolutional Feature Map Extraction | |
| [XAI HEATMAP INTERPRETATION] | |
| The heatmap shows scattered or unfocused activation patterns without strong | |
| concentration on any specific region. This indicates the model did not identify | |
| strong drone-like features in this image. | |
| [POSSIBLE REASONS] | |
| • No drone is present in the image | |
| • Drone is too small or too far from the camera | |
| • Poor lighting conditions reduce feature visibility | |
| • Image blur or motion blur affects detection quality | |
| • Drone is partially occluded by other objects | |
| [RECOMMENDATIONS] | |
| • Ensure adequate lighting in the scene | |
| • Position the drone closer to the camera | |
| • Use higher resolution images without motion blur | |
| • Avoid cluttered backgrounds that may confuse the model | |
| [TECHNICAL NOTE] | |
| The heatmap was extracted from the deepest convolutional layer of YOLOv11. | |
| Scattered activation pattern confirms absence of strong drone-like features. | |
| """ | |
| return report | |
| # ============================================================ | |
| # 5. MAIN PIPELINE FUNCTION | |
| # ============================================================ | |
| def drone_detection_pipeline(input_image): | |
| try: | |
| if isinstance(input_image, Image.Image): | |
| img = np.array(input_image) | |
| else: | |
| img = input_image.copy() | |
| original_h, original_w = img.shape[:2] | |
| results = model(img) | |
| heatmap_overlay = generate_heatmap(model, img) | |
| is_drone = False | |
| confidence = 0.0 | |
| drone_count = 0 | |
| if results and len(results) > 0 and hasattr(results[0], 'boxes'): | |
| boxes_data = results[0].boxes | |
| if boxes_data and boxes_data.data is not None: | |
| data = boxes_data.data.cpu().numpy() | |
| for det in data: | |
| if len(det) >= 6: | |
| conf = float(det[4]) | |
| cls = int(det[5]) | |
| class_name = model.names[cls] | |
| if class_name.lower() == 'drone' and conf >= 0.3: | |
| drone_count += 1 | |
| confidence = max(confidence, conf) | |
| is_drone = drone_count > 0 | |
| result_img = results[0].plot() if len(results) > 0 else img | |
| result_img_rgb = cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB) | |
| caption = generate_dynamic_caption(img) | |
| processing_time_ms = 0 | |
| report = build_xai_report(is_drone, confidence, drone_count, processing_time_ms, caption) | |
| if heatmap_overlay is not None: | |
| heatmap_resized = cv2.resize(heatmap_overlay, (original_w, original_h)) | |
| else: | |
| heatmap_resized = np.zeros_like(result_img_rgb) | |
| return result_img_rgb, heatmap_resized, report | |
| except Exception as e: | |
| error_msg = f"An error occurred during processing: {str(e)}" | |
| print(error_msg) | |
| blank = np.zeros((480, 640, 3), dtype=np.uint8) | |
| return blank, blank, error_msg | |
| # ============================================================ | |
| # 6. INTERFACE - DARK BLUE ONLY, NO PURPLE/VIOLET | |
| # ============================================================ | |
| custom_css = """ | |
| /* Force white background */ | |
| .gradio-container, body, .gradio-container .main, .gradio-container .gradio-container { | |
| background: #ffffff !important; | |
| font-family: 'Segoe UI', 'Roboto', sans-serif; | |
| } | |
| /* Override any purple/violet colors from Gradio default theme */ | |
| :root { | |
| --primary-50: #0a2b4e !important; | |
| --primary-100: #1e4a76 !important; | |
| --primary-200: #2c5282 !important; | |
| --primary-300: #1e3a8a !important; | |
| --primary-400: #0a2b4e !important; | |
| --primary-500: #0a2b4e !important; | |
| --primary-600: #0a2b4e !important; | |
| --primary-700: #0a2b4e !important; | |
| --primary-800: #0a2b4e !important; | |
| --primary-900: #0a2b4e !important; | |
| } | |
| /* Header - dark blue */ | |
| .skyguard-header { | |
| text-align: center; | |
| padding: 1.5rem 0 0.5rem 0; | |
| margin-bottom: 1.5rem; | |
| } | |
| .skyguard-header h1 { | |
| font-size: 3rem; | |
| font-weight: 800; | |
| letter-spacing: 3px; | |
| color: #0a2b4e !important; | |
| margin-bottom: 0.2rem; | |
| } | |
| .skyguard-header .tagline { | |
| font-size: 1.2rem; | |
| font-weight: 600; | |
| color: #1e4a76 !important; | |
| letter-spacing: 1px; | |
| margin-top: 0.2rem; | |
| } | |
| .skyguard-header .sub { | |
| font-size: 0.9rem; | |
| color: #2c5282 !important; | |
| margin-top: 0.5rem; | |
| } | |
| /* Primary button - solid dark blue, no gradient, no purple */ | |
| .gr-button-primary, button.gr-button-primary, .gr-button.primary { | |
| background: #0a2b4e !important; | |
| border: none !important; | |
| color: white !important; | |
| font-weight: 700 !important; | |
| border-radius: 30px !important; | |
| padding: 10px 28px !important; | |
| transition: all 0.2s ease !important; | |
| text-transform: uppercase !important; | |
| letter-spacing: 1px; | |
| box-shadow: none !important; | |
| } | |
| .gr-button-primary:hover, button.gr-button-primary:hover, .gr-button.primary:hover { | |
| background: #1e4a76 !important; | |
| transform: scale(1.02); | |
| box-shadow: 0 2px 8px rgba(10,43,78,0.2) !important; | |
| } | |
| /* Tabs - remove purple */ | |
| .gr-tabs .tab-nav button { | |
| background-color: #eef2f5 !important; | |
| color: #0a2b4e !important; | |
| font-weight: 600 !important; | |
| border-radius: 8px 8px 0 0 !important; | |
| padding: 8px 20px !important; | |
| margin-right: 4px; | |
| border: none !important; | |
| } | |
| .gr-tabs .tab-nav button.selected { | |
| background-color: #0a2b4e !important; | |
| color: white !important; | |
| } | |
| /* Input and output boxes */ | |
| .gr-box, .gr-form, .gr-input, .gr-panel { | |
| background-color: #f9fafb !important; | |
| border: 1px solid #d1d5db !important; | |
| border-radius: 12px !important; | |
| } | |
| /* Labels */ | |
| label, .gr-form label { | |
| color: #1e293b !important; | |
| font-weight: 500 !important; | |
| } | |
| /* Markdown text */ | |
| .gr-markdown p, .gr-markdown { | |
| color: #1e293b !important; | |
| } | |
| /* Footer */ | |
| .skyguard-footer { | |
| text-align: center; | |
| margin-top: 30px; | |
| font-size: 12px; | |
| color: #6c757d; | |
| border-top: 1px solid #e2e8f0; | |
| padding-top: 15px; | |
| } | |
| /* Additional overrides for any purple elements */ | |
| .gr-button, .gr-button-lg, .gr-button-sm, button { | |
| --tw-ring-color: #0a2b4e !important; | |
| } | |
| .prose a, a { | |
| color: #1e4a76 !important; | |
| } | |
| input:focus, textarea:focus, select:focus { | |
| border-color: #0a2b4e !important; | |
| ring-color: #0a2b4e !important; | |
| } | |
| """ | |
| with gr.Blocks(title="SkyGuard - Drone Detection System", theme=gr.themes.Soft(), css=custom_css) as demo: | |
| gr.HTML(""" | |
| <div class="skyguard-header"> | |
| <h1>SKYGUARD</h1> | |
| <div class="tagline">SEE EVERY THREAT BEFORE IT LANDS</div> | |
| <div class="sub">YOLOv11 Detection | Explainable AI (XAI) | BLIP Captioning</div> | |
| <div class="sub" style="font-size:0.75rem; color:#3b6e9e;">Mohamed Khider University of Biskra - Computer Science | Biskra, Algeria</div> | |
| </div> | |
| """) | |
| with gr.Row(equal_height=False): | |
| with gr.Column(scale=1, min_width=300): | |
| input_image = gr.Image(label="Upload Aerial Image", type="pil") | |
| analyze_btn = gr.Button("Analyze Threat", variant="primary") | |
| with gr.Column(scale=2): | |
| with gr.Tabs(): | |
| with gr.TabItem("Detection"): | |
| output_image = gr.Image(label="Bounding Boxes") | |
| with gr.TabItem("Heatmap (XAI)"): | |
| heatmap_image = gr.Image(label="Neural Activation Heatmap") | |
| with gr.TabItem("Intelligence Report"): | |
| report_text = gr.Markdown(label="XAI Analysis Report") | |
| analyze_btn.click( | |
| fn=drone_detection_pipeline, | |
| inputs=[input_image], | |
| outputs=[output_image, heatmap_image, report_text] | |
| ) | |
| gr.HTML(""" | |
| <div class="skyguard-footer"> | |
| SkyGuard System | Real-time Drone Detection | Powered by Hugging Face Spaces | |
| </div> | |
| """) | |
| # ============================================================ | |
| # 7. RUN APP | |
| # ============================================================ | |
| if __name__ == "__main__": | |
| demo.launch() |