File size: 15,540 Bytes
257ebbe
 
 
 
 
 
 
 
 
 
 
 
06afbb1
940e55e
06afbb1
afbe51e
940e55e
257ebbe
940e55e
257ebbe
 
 
 
 
 
940e55e
257ebbe
 
 
 
 
 
 
06afbb1
940e55e
06afbb1
257ebbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
940e55e
 
 
257ebbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
940e55e
257ebbe
940e55e
 
257ebbe
 
 
 
06afbb1
940e55e
06afbb1
257ebbe
 
940e55e
 
 
 
 
257ebbe
 
 
940e55e
 
257ebbe
 
 
 
 
 
 
 
 
06afbb1
940e55e
06afbb1
257ebbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
06afbb1
940e55e
06afbb1
257ebbe
 
 
 
 
 
 
 
940e55e
 
257ebbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
940e55e
257ebbe
940e55e
 
 
257ebbe
 
 
 
940e55e
257ebbe
 
 
 
 
 
 
 
06afbb1
940e55e
06afbb1
63bac93
940e55e
 
06afbb1
be6f001
63bac93
940e55e
 
 
 
 
 
 
 
 
 
 
 
 
 
63bac93
 
 
 
 
 
 
 
06afbb1
e1161fe
06afbb1
63bac93
 
06afbb1
 
e1161fe
63bac93
06afbb1
63bac93
 
06afbb1
be6f001
63bac93
 
940e55e
 
06afbb1
 
63bac93
 
 
06afbb1
940e55e
63bac93
 
940e55e
63bac93
940e55e
06afbb1
63bac93
940e55e
63bac93
940e55e
63bac93
06afbb1
 
63bac93
 
06afbb1
 
940e55e
63bac93
 
06afbb1
 
63bac93
940e55e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63bac93
 
 
 
06afbb1
 
63bac93
 
940e55e
 
 
 
 
 
 
 
 
 
 
63bac93
 
 
78de67a
63bac93
 
 
 
06afbb1
071fc64
78de67a
257ebbe
63bac93
 
 
 
257ebbe
 
c1c00a9
071fc64
63bac93
 
 
c1c00a9
 
 
 
 
 
 
071fc64
78de67a
63bac93
 
071fc64
78de67a
257ebbe
06afbb1
940e55e
06afbb1
257ebbe
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# -*- 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()