seifbenayed commited on
Commit
514a0e2
·
1 Parent(s): 85f8ca4
Files changed (2) hide show
  1. app.py +464 -462
  2. requirements.txt +0 -1
app.py CHANGED
@@ -2,6 +2,7 @@
2
  import importlib.util
3
  import os
4
  import sys
 
5
 
6
  # Check if detectron2 is installed
7
  if importlib.util.find_spec("detectron2") is None:
@@ -12,12 +13,13 @@ if importlib.util.find_spec("detectron2") is None:
12
  print("Installation complete!")
13
 
14
  # -*- coding: utf-8 -*-
 
 
15
  #!/usr/bin/env python3
16
  # -*- coding: utf-8 -*-
17
  import os
18
  import sys
19
  import time
20
- import cv2
21
  import torch
22
  import numpy as np
23
  import gradio as gr
@@ -26,270 +28,226 @@ from torchvision import transforms
26
 
27
  # Add current directory to path
28
  if not os.getcwd() in sys.path:
29
- sys.path.append(os.getcwd())
30
 
31
  # Detectron2 imports - wrapped in try-except to make them optional
32
  try:
33
- from detectron2.engine import DefaultPredictor
34
- from detectron2.config import get_cfg
35
- from detectron2.utils.visualizer import Visualizer, ColorMode
36
- from detectron2 import model_zoo
37
- DETECTRON2_AVAILABLE = True
38
  except ImportError:
39
- print("Warning: Detectron2 is not installed. Damage detection will not be available.")
40
- DETECTRON2_AVAILABLE = False
41
 
42
  # Check for custom path for models
43
  try:
44
- from configs.get_config import load_config
45
- from models import *
46
- MODELS_IMPORTED = True
47
  except ImportError:
48
- print("Warning: Custom models couldn't be imported. Only damage detection will work.")
49
- MODELS_IMPORTED = False
50
 
51
  def setup_device(device_str):
52
- """Set up the computation device based on user input and availability"""
53
- if device_str == 'auto':
54
- if torch.cuda.is_available():
 
 
 
 
 
 
55
  return torch.device('cuda:0')
56
- elif hasattr(torch, 'backends') and hasattr(torch.backends, 'mps') and torch.backends.mps.is_available():
57
  return torch.device('mps')
58
  else:
 
59
  return torch.device('cpu')
60
- elif device_str == 'cuda' and torch.cuda.is_available():
61
- return torch.device('cuda:0')
62
- elif device_str == 'mps' and hasattr(torch, 'backends') and hasattr(torch.backends, 'mps') and torch.backends.mps.is_available():
63
- return torch.device('mps')
64
- else:
65
- print(f"Warning: Device {device_str} not available, using CPU instead.")
66
- return torch.device('cpu')
67
 
68
  def setup_damage_detector(model_path, threshold=0.7):
69
- """Set up the damage detection model using Detectron2"""
70
- if not DETECTRON2_AVAILABLE:
71
- print("Detectron2 is not installed. Cannot set up damage detector.")
72
- return None, None
 
 
 
 
 
 
 
 
 
 
73
 
74
- if model_path is None or not os.path.exists(model_path):
75
- print("No damage model specified or file not found. Skipping damage detection.")
76
- return None, None
 
77
 
78
- cfg = get_cfg()
79
- cfg.merge_from_file(model_zoo.get_config_file("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml"))
80
- cfg.MODEL.WEIGHTS = model_path
81
- cfg.MODEL.ROI_HEADS.NUM_CLASSES = 1 # Only one class (damage)
82
- cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = threshold
83
-
84
- # Explicitly set to use CPU if on Mac (MPS)
85
- if torch.backends.mps.is_available():
86
- cfg.MODEL.DEVICE = "cpu"
87
- print("Mac MPS detected - forcing Detectron2 to use CPU")
88
-
89
- try:
90
- predictor = DefaultPredictor(cfg)
91
- return predictor, cfg
92
- except Exception as e:
93
- print(f"Error setting up damage detector: {e}")
94
- return None, cfg
95
 
96
  def load_deepfake_model(model_path, cfg_path, device):
97
- """Load the deepfake detection model"""
98
- if not MODELS_IMPORTED:
99
- print("Custom models module not imported. Cannot load deepfake model.")
100
- return None, None
101
-
102
- if model_path is None or not os.path.exists(model_path):
103
- print("No deepfake model specified or file not found. Skipping deepfake detection.")
104
- return None, None
105
-
106
- if cfg_path is None or not os.path.exists(cfg_path):
107
- print("No deepfake config specified or file not found. Skipping deepfake detection.")
108
- return None, None
109
-
110
- try:
111
- # Load config
112
- cfg = load_config(cfg_path)
113
-
114
- # Build model
115
- model = build_model(cfg.MODEL, MODELS)
116
-
117
- # Load weights
118
- print(f"Loading deepfake model from: {model_path}")
119
- checkpoint = torch.load(model_path, map_location='cpu')
120
-
121
- if isinstance(checkpoint, dict) and 'state_dict' in checkpoint:
122
- model.load_state_dict(checkpoint['state_dict'])
123
- else:
124
- model.load_state_dict(checkpoint)
125
 
126
- # Move model to device and set to evaluation mode
127
- model = model.to(device)
128
- if hasattr(cfg.MODEL, 'precision') and cfg.MODEL.precision == 'fp64':
129
- model = model.to(torch.float64)
130
- model.eval()
131
-
132
- return model, cfg
133
- except Exception as e:
134
- print(f"Error loading deepfake model: {e}")
135
- import traceback
136
- traceback.print_exc()
137
- return None, None
138
-
139
- def preprocess_for_deepfake(image, cfg, device):
140
- """Preprocess an image for deepfake detection"""
141
- try:
142
- # Convert to RGB if needed
143
- if len(image.shape) == 3 and image.shape[2] == 3:
144
- if image.dtype != np.uint8:
145
- image = (image * 255).astype(np.uint8)
146
- rgb_img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
147
- else:
148
- rgb_img = image
149
 
150
- # Resize
151
- img_resized = cv2.resize(rgb_img, (cfg.DATASET.IMAGE_SIZE[0], cfg.DATASET.IMAGE_SIZE[1]))
152
-
153
- # Convert to PIL and apply transforms
154
- transform = transforms.Compose([
155
- transforms.ToTensor(),
156
- transforms.Normalize(
157
- mean=cfg.DATASET.TRANSFORM.normalize.mean,
158
- std=cfg.DATASET.TRANSFORM.normalize.std
159
- )
160
- ])
161
-
162
- img_tensor = transform(Image.fromarray(img_resized)).unsqueeze(0) # Add batch dimension
163
- img_tensor = img_tensor.to(device)
164
-
165
- # Convert to correct precision
166
- if hasattr(cfg.MODEL, 'precision') and cfg.MODEL.precision == 'fp64':
167
- img_tensor = img_tensor.to(torch.float64)
168
 
169
- return img_tensor
170
- except Exception as e:
171
- print(f"Error preprocessing image for deepfake detection: {e}")
172
- import traceback
173
- traceback.print_exc()
174
- return None
175
-
176
- def detect_damage(img, damage_detector):
177
- """Detect damage in an image"""
178
- try:
179
- if img is None:
180
- raise ValueError("Invalid image")
181
 
182
- # If no damage detector available, return the whole image as region
183
- if damage_detector is None:
184
- print("No damage detector available. Using whole image as region.")
185
- h, w = img.shape[:2]
186
- damage_regions = [{
187
- "box": (0, 0, w, h),
188
- "score": 1.0,
189
- "mask": None
190
- }]
191
- return img, None, damage_regions
192
 
193
- # Run inference
194
- outputs = damage_detector(img)
195
-
196
- # Get damage regions
197
- instances = outputs["instances"].to("cpu")
198
- boxes = instances.pred_boxes.tensor.numpy() if instances.has("pred_boxes") else []
199
- scores = instances.scores.numpy() if instances.has("scores") else []
200
- masks = instances.pred_masks.numpy() if instances.has("pred_masks") else []
201
-
202
- damage_regions = []
203
- for i in range(len(boxes)):
204
- x1, y1, x2, y2 = map(int, boxes[i])
205
- damage_regions.append({
206
- "box": (x1, y1, x2, y2),
207
- "score": float(scores[i]),
208
- "mask": masks[i] if len(masks) > i else None
209
- })
210
-
211
- if not damage_regions:
212
- print("No damage detected. Using whole image.")
213
- h, w = img.shape[:2]
214
- damage_regions = [{
215
- "box": (0, 0, w, h),
216
- "score": 1.0,
217
- "mask": None
218
- }]
219
 
220
- return img, outputs, damage_regions
221
- except Exception as e:
222
- print(f"Error detecting damage: {e}")
223
- # If error occurs, return the whole image as region
224
- if 'img' in locals() and img is not None:
225
- h, w = img.shape[:2]
226
- damage_regions = [{
227
- "box": (0, 0, w, h),
228
- "score": 1.0,
229
- "mask": None
230
- }]
231
- return img, None, damage_regions
232
- return None, None, []
233
-
234
- def check_deepfake(image, damage_regions, deepfake_model, deepfake_cfg, device, threshold=0.5):
235
- """Check if damage regions are deepfakes"""
236
- results = []
237
-
238
- if deepfake_model is None:
239
- print("No deepfake model available. Skipping deepfake detection.")
240
- return []
241
 
242
- try:
243
- # If no damage regions, check the entire image
244
- if not damage_regions:
245
- img_tensor = preprocess_for_deepfake(image, deepfake_cfg, device)
246
- if img_tensor is None:
247
- return []
 
 
 
 
248
 
249
- # Run inference
250
- with torch.no_grad():
251
- outputs = deepfake_model(img_tensor)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
 
253
- # Extract outputs
254
- if isinstance(outputs, list):
255
- outputs = outputs[0]
 
 
 
 
 
 
 
 
 
256
 
257
- if isinstance(outputs, dict) and 'cls' in outputs:
258
- cls_outputs = outputs['cls']
259
- cls_prob = cls_outputs.sigmoid().cpu().numpy()
260
- else:
261
- # Assuming the output is directly the classification probability
262
- cls_prob = outputs.sigmoid().cpu().numpy() if hasattr(outputs, 'sigmoid') else outputs.cpu().numpy()
 
 
 
 
263
 
264
- if cls_prob.size > 0:
265
- is_fake = cls_prob[0][0] > threshold if cls_prob.ndim > 1 else cls_prob[0] > threshold
266
- confidence = cls_prob[0][0] if cls_prob.ndim > 1 else cls_prob[0]
267
-
268
- results.append({
269
- "region": "full_image",
270
- "deepfake_prob": float(confidence),
271
- "is_fake": bool(is_fake)
272
- })
273
 
274
- return results
275
-
276
- # Process each damage region
277
- for i, region in enumerate(damage_regions):
278
- x1, y1, x2, y2 = region["box"]
279
- # Ensure coordinates are within image bounds
280
- x1, y1 = max(0, x1), max(0, y1)
281
- x2, y2 = min(image.shape[1], x2), min(image.shape[0], y2)
 
 
 
 
 
 
282
 
283
- # Extract region and check if it's a deepfake
284
- if x2 > x1 and y2 > y1:
285
- # Get ROI
286
- roi = image[y1:y2, x1:x2]
 
 
 
 
287
 
288
- # Preprocess
289
- img_tensor = preprocess_for_deepfake(roi, deepfake_cfg, device)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  if img_tensor is None:
291
- continue
292
-
293
  # Run inference
294
  with torch.no_grad():
295
  outputs = deepfake_model(img_tensor)
@@ -310,257 +268,301 @@ try:
310
  confidence = cls_prob[0][0] if cls_prob.ndim > 1 else cls_prob[0]
311
 
312
  results.append({
313
- "region_id": i,
314
- "box": (x1, y1, x2, y2),
315
  "deepfake_prob": float(confidence),
316
  "is_fake": bool(is_fake)
317
  })
318
-
319
- return results
320
- except Exception as e:
321
- print(f"Error in deepfake detection: {e}")
322
- import traceback
323
- traceback.print_exc()
324
- return []
325
-
326
- def visualize_results(image, damage_outputs, deepfake_results, damage_threshold):
327
- """Create visualization of damage detection and deepfake verification"""
328
- try:
329
- # Create a copy for visualization
330
- img_copy = image.copy()
331
-
332
- # Draw damage detection results
333
- if damage_outputs is not None and DETECTRON2_AVAILABLE:
334
- try:
335
- v = Visualizer(img_copy[:, :, ::-1], scale=1.0, instance_mode=ColorMode.IMAGE_BW)
336
- v = v.draw_instance_predictions(damage_outputs["instances"].to("cpu"))
337
- result_img = v.get_image()[:, :, ::-1]
338
 
339
- # Convert to a standard numpy array to ensure compatibility with OpenCV
340
- result_img = np.array(result_img, dtype=np.uint8)
341
- except Exception as e:
342
- print(f"Error visualizing damage detection: {e}")
343
- result_img = img_copy
344
- else:
345
- result_img = img_copy
346
-
347
- # Add deepfake detection results
348
- for result in deepfake_results:
349
- try:
350
- if "box" in result:
351
- x1, y1, x2, y2 = result["box"]
352
- fake_prob = result["deepfake_prob"]
353
- is_fake = result["is_fake"]
354
- region_id = result.get("region_id", 0)
355
-
356
- # Text for the region
357
- text = f"R{region_id}: {'FAKE' if is_fake else 'REAL'} ({fake_prob*100:.1f}%)"
358
-
359
- # Different colors for fake/real
360
- color = (0, 0, 255) if is_fake else (0, 255, 0) # Red for fake, green for real
361
-
362
- # Ensure we have a standard numpy array
363
- if not isinstance(result_img, np.ndarray):
364
- result_img = np.array(result_img, dtype=np.uint8)
365
-
366
- # Draw rectangle and text
367
- cv2.rectangle(result_img, (x1, y1), (x2, y2), color, 2)
368
- cv2.putText(result_img, text, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2)
369
- elif "region" in result and result["region"] == "full_image":
370
- fake_prob = result["deepfake_prob"]
371
- is_fake = result["is_fake"]
372
-
373
- # Text for the whole image
374
- text = f"Image: {'FAKE' if is_fake else 'REAL'} ({fake_prob*100:.1f}%)"
375
 
376
- # Different colors for fake/real
377
- color = (0, 0, 255) if is_fake else (0, 255, 0) # Red for fake, green for real
 
 
378
 
379
- # Ensure we have a standard numpy array
380
- if not isinstance(result_img, np.ndarray):
381
- result_img = np.array(result_img, dtype=np.uint8)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
 
383
- # Draw text
384
- cv2.putText(result_img, text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, color, 2)
385
- except Exception as e:
386
- print(f"Error drawing result {result}: {e}")
387
-
388
- return result_img
389
- except Exception as e:
390
- print(f"Error visualizing results: {e}")
391
- import traceback
392
- traceback.print_exc()
393
- return np.array(image, dtype=np.uint8) # Return the original image as a numpy array
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
394
 
395
  def process_image(input_image, damage_model_path, deepfake_model_path, deepfake_cfg_path,
396
- damage_threshold, deepfake_threshold, skip_damage, device_str):
397
- """Process an image through the car damage and deepfake detection pipeline"""
398
- progress_info = []
399
-
400
- # Convert Gradio image to numpy array
401
- if isinstance(input_image, dict) and "path" in input_image:
402
- img = cv2.imread(input_image["path"])
403
- elif isinstance(input_image, str):
404
- img = cv2.imread(input_image)
405
- elif isinstance(input_image, np.ndarray):
406
- # Make a copy to avoid modifying the original
407
- img = input_image.copy()
408
- # Convert from RGB to BGR (OpenCV format)
409
- if len(img.shape) == 3 and img.shape[2] == 3:
410
- img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
411
- else:
412
- return None, "Error: Unsupported image format"
413
-
414
- if img is None:
415
- return None, "Error: Could not read the image"
416
-
417
- # Progress update
418
- progress_info.append("Image loaded successfully")
419
-
420
- # Setup device
421
- device = setup_device(device_str)
422
- progress_info.append(f"Using device: {device}")
423
-
424
- # Initialize models
425
- damage_detector = None
426
- deepfake_model = None
427
- deepfake_cfg = None
428
-
429
- # Setup damage detector if not skipped
430
- if not skip_damage and damage_model_path:
431
- progress_info.append("Setting up damage detector...")
432
- damage_detector, detector_cfg = setup_damage_detector(damage_model_path, float(damage_threshold))
433
- if damage_detector is None and DETECTRON2_AVAILABLE:
434
- progress_info.append("Failed to initialize damage detector")
435
- else:
436
- progress_info.append("Damage detector initialized successfully")
437
-
438
- # Setup deepfake detector
439
- if deepfake_model_path and deepfake_cfg_path:
440
- progress_info.append("Setting up deepfake detector...")
441
- deepfake_model, deepfake_cfg = load_deepfake_model(deepfake_model_path, deepfake_cfg_path, device)
442
- if deepfake_model is None:
443
- progress_info.append("Failed to initialize deepfake detector")
444
  else:
445
- progress_info.append("Deepfake detector initialized successfully")
446
-
447
- # Ensure at least one detector is working
448
- if damage_detector is None and deepfake_model is None:
449
- return None, "Error: Neither damage nor deepfake detector is available"
450
-
451
- # Step 1: Detect damage or use whole image
452
- progress_info.append("Detecting damage regions...")
453
- start_time = time.time()
454
- img, damage_outputs, damage_regions = detect_damage(img, damage_detector)
455
- damage_time = time.time() - start_time
456
-
457
- if img is None:
458
- return None, "Error: Failed to process image"
459
-
460
- # Print damage detection results
461
- if damage_detector is not None and damage_regions:
462
- progress_info.append(f"Detected {len(damage_regions)} damage regions in {damage_time:.3f} seconds")
463
- else:
464
- progress_info.append("Using the whole image for analysis")
465
-
466
- # Step 2: Check if damage is deepfake
467
- deepfake_results = []
468
- if deepfake_model is not None:
469
- progress_info.append("Performing deepfake detection...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  start_time = time.time()
471
- deepfake_results = check_deepfake(
472
- img, damage_regions, deepfake_model, deepfake_cfg, device, float(deepfake_threshold)
473
- )
474
- deepfake_time = time.time() - start_time
475
 
476
- if deepfake_results:
477
- progress_info.append(f"Deepfake detection completed in {deepfake_time:.3f} seconds")
478
-
479
- # Generate report
480
- for result in deepfake_results:
481
- if "region_id" in result:
482
- region_id = result["region_id"]
483
- fake_prob = result["deepfake_prob"]
484
- is_fake = result["is_fake"]
485
- progress_info.append(f"Region {region_id}: {'FAKE' if is_fake else 'REAL'} (Probability: {fake_prob*100:.2f}%)")
486
- elif "region" in result and result["region"] == "full_image":
487
- fake_prob = result["deepfake_prob"]
488
- is_fake = result["is_fake"]
489
- progress_info.append(f"Whole image: {'FAKE' if is_fake else 'REAL'} (Probability: {fake_prob*100:.2f}%)")
490
  else:
491
- progress_info.append("No deepfake detection results")
492
-
493
- # Step 3: Visualize final results
494
- progress_info.append("Generating visualization...")
495
- result_img = visualize_results(img, damage_outputs, deepfake_results, float(damage_threshold))
496
-
497
- # Convert back to RGB for Gradio
498
- if len(result_img.shape) == 3 and result_img.shape[2] == 3:
499
- result_img = cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB)
500
-
501
- progress_info.append("Processing complete!")
502
-
503
- return result_img, "\n".join(progress_info)
504
-
505
- def create_gradio_interface():
506
- with gr.Blocks(title="Car Damage & Deepfake Detection") as app:
507
- gr.Markdown("# Car Damage Detection & Deepfake Verification")
508
- gr.Markdown("Upload an image to detect car damage and check if it's a deepfake")
509
 
510
- with gr.Tab("Basic Interface"):
511
- with gr.Row():
512
- with gr.Column(scale=1):
513
- input_image = gr.Image(type="numpy", label="Input Image")
514
-
515
- # Simple controls
516
- skip_damage = gr.Checkbox(label="Skip Damage Detection", value=False)
517
- damage_threshold = gr.Slider(minimum=0.1, maximum=1.0, value=0.7, step=0.05,
518
- label="Damage Detection Threshold")
519
- deepfake_threshold = gr.Slider(minimum=0.1, maximum=1.0, value=0.5, step=0.05,
520
- label="Deepfake Detection Threshold")
521
- device = gr.Dropdown(choices=["auto", "cuda", "cpu", "mps"], value="auto",
522
- label="Computation Device")
523
-
524
- process_btn = gr.Button("Process Image", variant="primary")
525
-
526
- with gr.Column(scale=1):
527
- output_image = gr.Image(type="numpy", label="Result")
528
- output_text = gr.Textbox(label="Detection Results", lines=10)
529
 
530
- with gr.Tab("Advanced Settings"):
531
- with gr.Row():
532
- with gr.Column():
533
- damage_model_path = gr.Textbox(label="Damage Model Path",
534
- placeholder="Path to damage detection model (.pth)")
535
- deepfake_model_path = gr.Textbox(label="Deepfake Model Path",
536
- placeholder="Path to deepfake detection model (.pth)")
537
- deepfake_cfg_path = gr.Textbox(label="Deepfake Config Path",
538
- placeholder="Path to deepfake model config (.yaml)")
 
 
 
 
 
 
 
 
 
 
 
539
 
540
- # Connect the process function
541
- process_btn.click(
542
- fn=process_image,
543
- inputs=[
544
- input_image,
545
- damage_model_path,
546
- deepfake_model_path,
547
- deepfake_cfg_path,
548
- damage_threshold,
549
- deepfake_threshold,
550
- skip_damage,
551
- device
552
- ],
553
- outputs=[output_image, output_text]
554
- )
555
 
556
- # Examples
557
- gr.Markdown("## Examples")
558
- gr.Markdown("Note: Examples will only work if you have the appropriate models installed.")
 
 
 
 
 
559
 
560
- return app
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
561
 
562
- # Create and launch the app
563
- app = create_gradio_interface()
564
 
565
  # For local testing and Hugging Face Spaces, with debugging enabled
566
  if __name__ == "__main__":
 
2
  import importlib.util
3
  import os
4
  import sys
5
+ import cv2
6
 
7
  # Check if detectron2 is installed
8
  if importlib.util.find_spec("detectron2") is None:
 
13
  print("Installation complete!")
14
 
15
  # -*- coding: utf-8 -*-
16
+
17
+
18
  #!/usr/bin/env python3
19
  # -*- coding: utf-8 -*-
20
  import os
21
  import sys
22
  import time
 
23
  import torch
24
  import numpy as np
25
  import gradio as gr
 
28
 
29
  # Add current directory to path
30
  if not os.getcwd() in sys.path:
31
+ sys.path.append(os.getcwd())
32
 
33
  # Detectron2 imports - wrapped in try-except to make them optional
34
  try:
35
+ from detectron2.engine import DefaultPredictor
36
+ from detectron2.config import get_cfg
37
+ from detectron2.utils.visualizer import Visualizer, ColorMode
38
+ from detectron2 import model_zoo
39
+ DETECTRON2_AVAILABLE = True
40
  except ImportError:
41
+ print("Warning: Detectron2 is not installed. Damage detection will not be available.")
42
+ DETECTRON2_AVAILABLE = False
43
 
44
  # Check for custom path for models
45
  try:
46
+ from configs.get_config import load_config
47
+ from models import *
48
+ MODELS_IMPORTED = True
49
  except ImportError:
50
+ print("Warning: Custom models couldn't be imported. Only damage detection will work.")
51
+ MODELS_IMPORTED = False
52
 
53
  def setup_device(device_str):
54
+ """Set up the computation device based on user input and availability"""
55
+ if device_str == 'auto':
56
+ if torch.cuda.is_available():
57
+ return torch.device('cuda:0')
58
+ elif hasattr(torch, 'backends') and hasattr(torch.backends, 'mps') and torch.backends.mps.is_available():
59
+ return torch.device('mps')
60
+ else:
61
+ return torch.device('cpu')
62
+ elif device_str == 'cuda' and torch.cuda.is_available():
63
  return torch.device('cuda:0')
64
+ elif device_str == 'mps' and hasattr(torch, 'backends') and hasattr(torch.backends, 'mps') and torch.backends.mps.is_available():
65
  return torch.device('mps')
66
  else:
67
+ print(f"Warning: Device {device_str} not available, using CPU instead.")
68
  return torch.device('cpu')
 
 
 
 
 
 
 
69
 
70
  def setup_damage_detector(model_path, threshold=0.7):
71
+ """Set up the damage detection model using Detectron2"""
72
+ if not DETECTRON2_AVAILABLE:
73
+ print("Detectron2 is not installed. Cannot set up damage detector.")
74
+ return None, None
75
+
76
+ if model_path is None or not os.path.exists(model_path):
77
+ print("No damage model specified or file not found. Skipping damage detection.")
78
+ return None, None
79
+
80
+ cfg = get_cfg()
81
+ cfg.merge_from_file(model_zoo.get_config_file("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml"))
82
+ cfg.MODEL.WEIGHTS = model_path
83
+ cfg.MODEL.ROI_HEADS.NUM_CLASSES = 1 # Only one class (damage)
84
+ cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = threshold
85
 
86
+ # Explicitly set to use CPU if on Mac (MPS)
87
+ if torch.backends.mps.is_available():
88
+ cfg.MODEL.DEVICE = "cpu"
89
+ print("Mac MPS detected - forcing Detectron2 to use CPU")
90
 
91
+ try:
92
+ predictor = DefaultPredictor(cfg)
93
+ return predictor, cfg
94
+ except Exception as e:
95
+ print(f"Error setting up damage detector: {e}")
96
+ return None, cfg
 
 
 
 
 
 
 
 
 
 
 
97
 
98
  def load_deepfake_model(model_path, cfg_path, device):
99
+ """Load the deepfake detection model"""
100
+ if not MODELS_IMPORTED:
101
+ print("Custom models module not imported. Cannot load deepfake model.")
102
+ return None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
+ if model_path is None or not os.path.exists(model_path):
105
+ print("No deepfake model specified or file not found. Skipping deepfake detection.")
106
+ return None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
+ if cfg_path is None or not os.path.exists(cfg_path):
109
+ print("No deepfake config specified or file not found. Skipping deepfake detection.")
110
+ return None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
+ try:
113
+ # Load config
114
+ cfg = load_config(cfg_path)
 
 
 
 
 
 
 
 
 
115
 
116
+ # Build model
117
+ model = build_model(cfg.MODEL, MODELS)
 
 
 
 
 
 
 
 
118
 
119
+ # Load weights
120
+ print(f"Loading deepfake model from: {model_path}")
121
+ checkpoint = torch.load(model_path, map_location='cpu')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
+ if isinstance(checkpoint, dict) and 'state_dict' in checkpoint:
124
+ model.load_state_dict(checkpoint['state_dict'])
125
+ else:
126
+ model.load_state_dict(checkpoint)
127
+
128
+ # Move model to device and set to evaluation mode
129
+ model = model.to(device)
130
+ if hasattr(cfg.MODEL, 'precision') and cfg.MODEL.precision == 'fp64':
131
+ model = model.to(torch.float64)
132
+ model.eval()
133
+
134
+ return model, cfg
135
+ except Exception as e:
136
+ print(f"Error loading deepfake model: {e}")
137
+ import traceback
138
+ traceback.print_exc()
139
+ return None, None
 
 
 
 
140
 
141
+ def preprocess_for_deepfake(image, cfg, device):
142
+ """Preprocess an image for deepfake detection"""
143
+ try:
144
+ # Convert to RGB if needed
145
+ if len(image.shape) == 3 and image.shape[2] == 3:
146
+ if image.dtype != np.uint8:
147
+ image = (image * 255).astype(np.uint8)
148
+ rgb_img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
149
+ else:
150
+ rgb_img = image
151
 
152
+ # Resize
153
+ img_resized = cv2.resize(rgb_img, (cfg.DATASET.IMAGE_SIZE[0], cfg.DATASET.IMAGE_SIZE[1]))
154
+
155
+ # Convert to PIL and apply transforms
156
+ transform = transforms.Compose([
157
+ transforms.ToTensor(),
158
+ transforms.Normalize(
159
+ mean=cfg.DATASET.TRANSFORM.normalize.mean,
160
+ std=cfg.DATASET.TRANSFORM.normalize.std
161
+ )
162
+ ])
163
+
164
+ img_tensor = transform(Image.fromarray(img_resized)).unsqueeze(0) # Add batch dimension
165
+ img_tensor = img_tensor.to(device)
166
+
167
+ # Convert to correct precision
168
+ if hasattr(cfg.MODEL, 'precision') and cfg.MODEL.precision == 'fp64':
169
+ img_tensor = img_tensor.to(torch.float64)
170
 
171
+ return img_tensor
172
+ except Exception as e:
173
+ print(f"Error preprocessing image for deepfake detection: {e}")
174
+ import traceback
175
+ traceback.print_exc()
176
+ return None
177
+
178
+ def detect_damage(img, damage_detector):
179
+ """Detect damage in an image"""
180
+ try:
181
+ if img is None:
182
+ raise ValueError("Invalid image")
183
 
184
+ # If no damage detector available, return the whole image as region
185
+ if damage_detector is None:
186
+ print("No damage detector available. Using whole image as region.")
187
+ h, w = img.shape[:2]
188
+ damage_regions = [{
189
+ "box": (0, 0, w, h),
190
+ "score": 1.0,
191
+ "mask": None
192
+ }]
193
+ return img, None, damage_regions
194
 
195
+ # Run inference
196
+ outputs = damage_detector(img)
 
 
 
 
 
 
 
197
 
198
+ # Get damage regions
199
+ instances = outputs["instances"].to("cpu")
200
+ boxes = instances.pred_boxes.tensor.numpy() if instances.has("pred_boxes") else []
201
+ scores = instances.scores.numpy() if instances.has("scores") else []
202
+ masks = instances.pred_masks.numpy() if instances.has("pred_masks") else []
203
+
204
+ damage_regions = []
205
+ for i in range(len(boxes)):
206
+ x1, y1, x2, y2 = map(int, boxes[i])
207
+ damage_regions.append({
208
+ "box": (x1, y1, x2, y2),
209
+ "score": float(scores[i]),
210
+ "mask": masks[i] if len(masks) > i else None
211
+ })
212
 
213
+ if not damage_regions:
214
+ print("No damage detected. Using whole image.")
215
+ h, w = img.shape[:2]
216
+ damage_regions = [{
217
+ "box": (0, 0, w, h),
218
+ "score": 1.0,
219
+ "mask": None
220
+ }]
221
 
222
+ return img, outputs, damage_regions
223
+ except Exception as e:
224
+ print(f"Error detecting damage: {e}")
225
+ # If error occurs, return the whole image as region
226
+ if 'img' in locals() and img is not None:
227
+ h, w = img.shape[:2]
228
+ damage_regions = [{
229
+ "box": (0, 0, w, h),
230
+ "score": 1.0,
231
+ "mask": None
232
+ }]
233
+ return img, None, damage_regions
234
+ return None, None, []
235
+
236
+ def check_deepfake(image, damage_regions, deepfake_model, deepfake_cfg, device, threshold=0.5):
237
+ """Check if damage regions are deepfakes"""
238
+ results = []
239
+
240
+ if deepfake_model is None:
241
+ print("No deepfake model available. Skipping deepfake detection.")
242
+ return []
243
+
244
+ try:
245
+ # If no damage regions, check the entire image
246
+ if not damage_regions:
247
+ img_tensor = preprocess_for_deepfake(image, deepfake_cfg, device)
248
  if img_tensor is None:
249
+ return []
250
+
251
  # Run inference
252
  with torch.no_grad():
253
  outputs = deepfake_model(img_tensor)
 
268
  confidence = cls_prob[0][0] if cls_prob.ndim > 1 else cls_prob[0]
269
 
270
  results.append({
271
+ "region": "full_image",
 
272
  "deepfake_prob": float(confidence),
273
  "is_fake": bool(is_fake)
274
  })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
 
276
+ return results
277
+
278
+ # Process each damage region
279
+ for i, region in enumerate(damage_regions):
280
+ x1, y1, x2, y2 = region["box"]
281
+ # Ensure coordinates are within image bounds
282
+ x1, y1 = max(0, x1), max(0, y1)
283
+ x2, y2 = min(image.shape[1], x2), min(image.shape[0], y2)
284
+
285
+ # Extract region and check if it's a deepfake
286
+ if x2 > x1 and y2 > y1:
287
+ # Get ROI
288
+ roi = image[y1:y2, x1:x2]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
 
290
+ # Preprocess
291
+ img_tensor = preprocess_for_deepfake(roi, deepfake_cfg, device)
292
+ if img_tensor is None:
293
+ continue
294
 
295
+ # Run inference
296
+ with torch.no_grad():
297
+ outputs = deepfake_model(img_tensor)
298
+
299
+ # Extract outputs
300
+ if isinstance(outputs, list):
301
+ outputs = outputs[0]
302
+
303
+ if isinstance(outputs, dict) and 'cls' in outputs:
304
+ cls_outputs = outputs['cls']
305
+ cls_prob = cls_outputs.sigmoid().cpu().numpy()
306
+ else:
307
+ # Assuming the output is directly the classification probability
308
+ cls_prob = outputs.sigmoid().cpu().numpy() if hasattr(outputs, 'sigmoid') else outputs.cpu().numpy()
309
+
310
+ if cls_prob.size > 0:
311
+ is_fake = cls_prob[0][0] > threshold if cls_prob.ndim > 1 else cls_prob[0] > threshold
312
+ confidence = cls_prob[0][0] if cls_prob.ndim > 1 else cls_prob[0]
313
+
314
+ results.append({
315
+ "region_id": i,
316
+ "box": (x1, y1, x2, y2),
317
+ "deepfake_prob": float(confidence),
318
+ "is_fake": bool(is_fake)
319
+ })
320
+
321
+ return results
322
+ except Exception as e:
323
+ print(f"Error in deepfake detection: {e}")
324
+ import traceback
325
+ traceback.print_exc()
326
+ return []
327
+
328
+ def visualize_results(image, damage_outputs, deepfake_results, damage_threshold):
329
+ """Create visualization of damage detection and deepfake verification"""
330
+ try:
331
+ # Create a copy for visualization
332
+ img_copy = image.copy()
333
+
334
+ # Draw damage detection results
335
+ if damage_outputs is not None and DETECTRON2_AVAILABLE:
336
+ try:
337
+ v = Visualizer(img_copy[:, :, ::-1], scale=1.0, instance_mode=ColorMode.IMAGE_BW)
338
+ v = v.draw_instance_predictions(damage_outputs["instances"].to("cpu"))
339
+ result_img = v.get_image()[:, :, ::-1]
340
 
341
+ # Convert to a standard numpy array to ensure compatibility with OpenCV
342
+ result_img = np.array(result_img, dtype=np.uint8)
343
+ except Exception as e:
344
+ print(f"Error visualizing damage detection: {e}")
345
+ result_img = img_copy
346
+ else:
347
+ result_img = img_copy
348
+
349
+ # Add deepfake detection results
350
+ for result in deepfake_results:
351
+ try:
352
+ if "box" in result:
353
+ x1, y1, x2, y2 = result["box"]
354
+ fake_prob = result["deepfake_prob"]
355
+ is_fake = result["is_fake"]
356
+ region_id = result.get("region_id", 0)
357
+
358
+ # Text for the region
359
+ text = f"R{region_id}: {'FAKE' if is_fake else 'REAL'} ({fake_prob*100:.1f}%)"
360
+
361
+ # Different colors for fake/real
362
+ color = (0, 0, 255) if is_fake else (0, 255, 0) # Red for fake, green for real
363
+
364
+ # Ensure we have a standard numpy array
365
+ if not isinstance(result_img, np.ndarray):
366
+ result_img = np.array(result_img, dtype=np.uint8)
367
+
368
+ # Draw rectangle and text
369
+ cv2.rectangle(result_img, (x1, y1), (x2, y2), color, 2)
370
+ cv2.putText(result_img, text, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2)
371
+ elif "region" in result and result["region"] == "full_image":
372
+ fake_prob = result["deepfake_prob"]
373
+ is_fake = result["is_fake"]
374
+
375
+ # Text for the whole image
376
+ text = f"Image: {'FAKE' if is_fake else 'REAL'} ({fake_prob*100:.1f}%)"
377
+
378
+ # Different colors for fake/real
379
+ color = (0, 0, 255) if is_fake else (0, 255, 0) # Red for fake, green for real
380
+
381
+ # Ensure we have a standard numpy array
382
+ if not isinstance(result_img, np.ndarray):
383
+ result_img = np.array(result_img, dtype=np.uint8)
384
+
385
+ # Draw text
386
+ cv2.putText(result_img, text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, color, 2)
387
+ except Exception as e:
388
+ print(f"Error drawing result {result}: {e}")
389
+
390
+ return result_img
391
+ except Exception as e:
392
+ print(f"Error visualizing results: {e}")
393
+ import traceback
394
+ traceback.print_exc()
395
+ return np.array(image, dtype=np.uint8) # Return the original image as a numpy array
396
 
397
  def process_image(input_image, damage_model_path, deepfake_model_path, deepfake_cfg_path,
398
+ damage_threshold, deepfake_threshold, skip_damage, device_str):
399
+ """Process an image through the car damage and deepfake detection pipeline"""
400
+ progress_info = []
401
+
402
+ # Convert Gradio image to numpy array
403
+ if isinstance(input_image, dict) and "path" in input_image:
404
+ img = cv2.imread(input_image["path"])
405
+ elif isinstance(input_image, str):
406
+ img = cv2.imread(input_image)
407
+ elif isinstance(input_image, np.ndarray):
408
+ # Make a copy to avoid modifying the original
409
+ img = input_image.copy()
410
+ # Convert from RGB to BGR (OpenCV format)
411
+ if len(img.shape) == 3 and img.shape[2] == 3:
412
+ img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
  else:
414
+ return None, "Error: Unsupported image format"
415
+
416
+ if img is None:
417
+ return None, "Error: Could not read the image"
418
+
419
+ # Progress update
420
+ progress_info.append("Image loaded successfully")
421
+
422
+ # Setup device
423
+ device = setup_device(device_str)
424
+ progress_info.append(f"Using device: {device}")
425
+
426
+ # Initialize models
427
+ damage_detector = None
428
+ deepfake_model = None
429
+ deepfake_cfg = None
430
+
431
+ # Setup damage detector if not skipped
432
+ if not skip_damage and damage_model_path:
433
+ progress_info.append("Setting up damage detector...")
434
+ damage_detector, detector_cfg = setup_damage_detector(damage_model_path, float(damage_threshold))
435
+ if damage_detector is None and DETECTRON2_AVAILABLE:
436
+ progress_info.append("Failed to initialize damage detector")
437
+ else:
438
+ progress_info.append("Damage detector initialized successfully")
439
+
440
+ # Setup deepfake detector
441
+ if deepfake_model_path and deepfake_cfg_path:
442
+ progress_info.append("Setting up deepfake detector...")
443
+ deepfake_model, deepfake_cfg = load_deepfake_model(deepfake_model_path, deepfake_cfg_path, device)
444
+ if deepfake_model is None:
445
+ progress_info.append("Failed to initialize deepfake detector")
446
+ else:
447
+ progress_info.append("Deepfake detector initialized successfully")
448
+
449
+ # Ensure at least one detector is working
450
+ if damage_detector is None and deepfake_model is None:
451
+ return None, "Error: Neither damage nor deepfake detector is available"
452
+
453
+ # Step 1: Detect damage or use whole image
454
+ progress_info.append("Detecting damage regions...")
455
  start_time = time.time()
456
+ img, damage_outputs, damage_regions = detect_damage(img, damage_detector)
457
+ damage_time = time.time() - start_time
 
 
458
 
459
+ if img is None:
460
+ return None, "Error: Failed to process image"
461
+
462
+ # Print damage detection results
463
+ if damage_detector is not None and damage_regions:
464
+ progress_info.append(f"Detected {len(damage_regions)} damage regions in {damage_time:.3f} seconds")
 
 
 
 
 
 
 
 
465
  else:
466
+ progress_info.append("Using the whole image for analysis")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
 
468
+ # Step 2: Check if damage is deepfake
469
+ deepfake_results = []
470
+ if deepfake_model is not None:
471
+ progress_info.append("Performing deepfake detection...")
472
+ start_time = time.time()
473
+ deepfake_results = check_deepfake(
474
+ img, damage_regions, deepfake_model, deepfake_cfg, device, float(deepfake_threshold)
475
+ )
476
+ deepfake_time = time.time() - start_time
 
 
 
 
 
 
 
 
 
 
477
 
478
+ if deepfake_results:
479
+ progress_info.append(f"Deepfake detection completed in {deepfake_time:.3f} seconds")
480
+
481
+ # Generate report
482
+ for result in deepfake_results:
483
+ if "region_id" in result:
484
+ region_id = result["region_id"]
485
+ fake_prob = result["deepfake_prob"]
486
+ is_fake = result["is_fake"]
487
+ progress_info.append(f"Region {region_id}: {'FAKE' if is_fake else 'REAL'} (Probability: {fake_prob*100:.2f}%)")
488
+ elif "region" in result and result["region"] == "full_image":
489
+ fake_prob = result["deepfake_prob"]
490
+ is_fake = result["is_fake"]
491
+ progress_info.append(f"Whole image: {'FAKE' if is_fake else 'REAL'} (Probability: {fake_prob*100:.2f}%)")
492
+ else:
493
+ progress_info.append("No deepfake detection results")
494
+
495
+ # Step 3: Visualize final results
496
+ progress_info.append("Generating visualization...")
497
+ result_img = visualize_results(img, damage_outputs, deepfake_results, float(damage_threshold))
498
 
499
+ # Convert back to RGB for Gradio
500
+ if len(result_img.shape) == 3 and result_img.shape[2] == 3:
501
+ result_img = cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB)
 
 
 
 
 
 
 
 
 
 
 
 
502
 
503
+ progress_info.append("Processing complete!")
504
+
505
+ return result_img, "\n".join(progress_info)
506
+
507
+ def create_gradio_interface():
508
+ with gr.Blocks(title="Car Damage & Deepfake Detection") as app:
509
+ gr.Markdown("# Car Damage Detection & Deepfake Verification")
510
+ gr.Markdown("Upload an image to detect car damage and check if it's a deepfake")
511
 
512
+ with gr.Tab("Basic Interface"):
513
+ with gr.Row():
514
+ with gr.Column(scale=1):
515
+ input_image = gr.Image(type="numpy", label="Input Image")
516
+
517
+ # Simple controls
518
+ skip_damage = gr.Checkbox(label="Skip Damage Detection", value=False)
519
+ damage_threshold = gr.Slider(minimum=0.1, maximum=1.0, value=0.7, step=0.05,
520
+ label="Damage Detection Threshold")
521
+ deepfake_threshold = gr.Slider(minimum=0.1, maximum=1.0, value=0.5, step=0.05,
522
+ label="Deepfake Detection Threshold")
523
+ device = gr.Dropdown(choices=["auto", "cuda", "cpu", "mps"], value="auto",
524
+ label="Computation Device")
525
+
526
+ process_btn = gr.Button("Process Image", variant="primary")
527
+
528
+ with gr.Column(scale=1):
529
+ output_image = gr.Image(type="numpy", label="Result")
530
+ output_text = gr.Textbox(label="Detection Results", lines=10)
531
+
532
+ with gr.Tab("Advanced Settings"):
533
+ with gr.Row():
534
+ with gr.Column():
535
+ damage_model_path = gr.Textbox(label="Damage Model Path",
536
+ placeholder="Path to damage detection model (.pth)")
537
+ deepfake_model_path = gr.Textbox(label="Deepfake Model Path",
538
+ placeholder="Path to deepfake detection model (.pth)")
539
+ deepfake_cfg_path = gr.Textbox(label="Deepfake Config Path",
540
+ placeholder="Path to deepfake model config (.yaml)")
541
+
542
+ # Connect the process function
543
+ process_btn.click(
544
+ fn=process_image,
545
+ inputs=[
546
+ input_image,
547
+ damage_model_path,
548
+ deepfake_model_path,
549
+ deepfake_cfg_path,
550
+ damage_threshold,
551
+ deepfake_threshold,
552
+ skip_damage,
553
+ device
554
+ ],
555
+ outputs=[output_image, output_text]
556
+ )
557
+
558
+ # Examples
559
+ gr.Markdown("## Examples")
560
+ gr.Markdown("Note: Examples will only work if you have the appropriate models installed.")
561
+
562
+ return app
563
 
564
+ # Create and launch the app
565
+ app = create_gradio_interface()
566
 
567
  # For local testing and Hugging Face Spaces, with debugging enabled
568
  if __name__ == "__main__":
requirements.txt CHANGED
@@ -5,7 +5,6 @@ numpy>=1.24.0
5
  Pillow>=10.0.0
6
  gradio>=3.50.0
7
  python-box
8
- opencv-python
9
  fvcore>=0.1.5.post20221221; platform_system!="Darwin"
10
  iopath>=0.1.9; platform_system!="Darwin"
11
  pycocotools>=2.0.6; platform_system!="Darwin"
 
5
  Pillow>=10.0.0
6
  gradio>=3.50.0
7
  python-box
 
8
  fvcore>=0.1.5.post20221221; platform_system!="Darwin"
9
  iopath>=0.1.9; platform_system!="Darwin"
10
  pycocotools>=2.0.6; platform_system!="Darwin"