""" Fallback detection module for when the main YOLO model fails due to torchvision or other dependency issues. This provides a simple detection mechanism without dependencies on PyTorch or torchvision. """ import logging import numpy as np import os import tempfile from typing import Dict, List, Optional, Tuple, Union import uuid # Initialize logger logger = logging.getLogger(__name__) # Try to import OpenCV, but don't fail if not available try: import cv2 HAS_CV2 = True except ImportError: HAS_CV2 = False logger.warning("OpenCV (cv2) not available in fallback_detection module") # Basic color detection thresholds # These are simple HSV thresholds for detecting common pollution colors COLOR_THRESHOLDS = { "oil_spill": { "lower": np.array([0, 0, 0]), "upper": np.array([180, 255, 80]), "label": "Potential Oil Spill", "confidence": 0.6 }, "plastic_bright": { "lower": np.array([0, 50, 180]), "upper": np.array([30, 255, 255]), "label": "Potential Plastic Debris", "confidence": 0.7 }, "foam_pollution": { "lower": np.array([0, 0, 200]), "upper": np.array([180, 30, 255]), "label": "Potential Foam/Chemical Pollution", "confidence": 0.65 }, # Enhanced plastic bottle detection thresholds "plastic_bottles_clear": { "lower": np.array([0, 0, 140]), "upper": np.array([180, 60, 255]), "label": "plastic bottle", # Updated label to match YOLO naming "confidence": 0.80 }, "plastic_bottles_blue": { "lower": np.array([90, 40, 100]), "upper": np.array([130, 255, 255]), "label": "plastic bottle", "confidence": 0.75 }, "plastic_bottles_green": { "lower": np.array([35, 40, 100]), "upper": np.array([85, 255, 255]), "label": "plastic bottle", "confidence": 0.75 }, "plastic_bottles_white": { "lower": np.array([0, 0, 180]), "upper": np.array([180, 30, 255]), "label": "plastic bottle", "confidence": 0.75 }, "plastic_bottles_cap": { "lower": np.array([100, 100, 100]), "upper": np.array([140, 255, 255]), "label": "plastic bottle cap", "confidence": 0.85 }, "blue_plastic": { "lower": np.array([90, 50, 50]), "upper": np.array([130, 255, 255]), "label": "plastic waste", # Updated label for consistency "confidence": 0.6 }, "green_plastic": { "lower": np.array([35, 50, 50]), "upper": np.array([85, 255, 255]), "label": "plastic waste", "confidence": 0.6 }, "white_plastic": { "lower": np.array([0, 0, 190]), "upper": np.array([180, 30, 255]), "label": "plastic waste", "confidence": 0.6 } } def analyze_texture_for_pollution(img): """ Analyze image texture to detect unnatural patterns that could be debris. Uses edge detection and morphological operations to find potential plastic debris. Enhanced for better plastic bottle detection. Args: img: OpenCV image in BGR format Returns: List of bounding boxes for potential debris based on texture """ try: # Convert to grayscale gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Apply Gaussian blur to reduce noise blurred = cv2.GaussianBlur(gray, (5, 5), 0) # Apply Canny edge detection edges = cv2.Canny(blurred, 50, 150) # Dilate edges to connect nearby edges kernel = np.ones((3, 3), np.uint8) dilated_edges = cv2.dilate(edges, kernel, iterations=2) # Find contours in the edge map contours, _ = cv2.findContours(dilated_edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # Also do a more specific search for bottle-shaped objects # Convert to HSV for color filtering hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # Create a combined mask for common bottle colors bottle_mask = np.zeros_like(gray) # Clear/translucent plastic clear_mask = cv2.inRange(hsv, np.array([0, 0, 140]), np.array([180, 60, 255])) bottle_mask = cv2.bitwise_or(bottle_mask, clear_mask) # Blue plastic blue_mask = cv2.inRange(hsv, np.array([90, 40, 100]), np.array([130, 255, 255])) bottle_mask = cv2.bitwise_or(bottle_mask, blue_mask) # Apply morphological operations to clean up the mask bottle_mask = cv2.morphologyEx(bottle_mask, cv2.MORPH_CLOSE, kernel) bottle_mask = cv2.morphologyEx(bottle_mask, cv2.MORPH_OPEN, kernel) # Find contours in the bottle mask bottle_contours, _ = cv2.findContours(bottle_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # Filter contours by various criteria to find unnatural patterns debris_regions = [] # Process regular edge contours for contour in contours: # Calculate area and perimeter area = cv2.contourArea(contour) perimeter = cv2.arcLength(contour, True) # Skip very small contours if area < 100: continue # Calculate shape metrics if perimeter > 0: circularity = 4 * np.pi * area / (perimeter * perimeter) # Unnatural objects tend to have specific circularity ranges # (not too circular, not too irregular) if 0.2 < circularity < 0.8: x, y, w, h = cv2.boundingRect(contour) # Calculate aspect ratio aspect_ratio = float(w) / h if h > 0 else 0 # Most natural objects don't have extreme aspect ratios if 0.2 < aspect_ratio < 5: # Get ROI and check for texture uniformity roi = gray[y:y+h, x:x+w] if roi.size > 0: # Calculate standard deviation of pixel values std_dev = np.std(roi) # Man-made objects often have uniform textures if std_dev < 40: debris_regions.append({ "bbox": [x, y, x+w, y+h], "confidence": 0.55, "class": "Potential Debris (Texture)" }) # Process bottle-specific contours with higher confidence for contour in bottle_contours: area = cv2.contourArea(contour) if area < 200: # Higher threshold for bottles continue perimeter = cv2.arcLength(contour, True) if perimeter <= 0: continue # Get bounding rectangle x, y, w, h = cv2.boundingRect(contour) # Calculate aspect ratio - bottles typically have aspect ratio between 0.2 and 0.7 aspect_ratio = float(w) / h if h > 0 else 0 # Bottle detection criteria - bottles are usually taller than wide if 0.2 < aspect_ratio < 0.7 and h > 50: # This is likely to be a bottle based on shape bottle_confidence = 0.70 # Get ROI for additional checks roi_hsv = hsv[y:y+h, x:x+w] if roi_hsv.size > 0: # Check for uniformity in color which is common in bottles h_std = np.std(roi_hsv[:,:,0]) s_std = np.std(roi_hsv[:,:,1]) # Bottles often have uniform hue and saturation if h_std < 30 and s_std < 60: bottle_confidence = 0.85 # Higher confidence for uniform color debris_regions.append({ "bbox": [x, y, x+w, y+h], "confidence": bottle_confidence, "class": "Plastic Bottle" }) return debris_regions except Exception as e: logger.error(f"Texture analysis failed: {str(e)}") return [] def fallback_detect_objects(image_path: str) -> Dict: """ Perform a simple color-based detection when ML detection fails. Uses basic computer vision techniques to detect potential pollution. Args: image_path: Path to the image file Returns: Dict with detections in the same format as the main detection function """ if not HAS_CV2: logger.warning("OpenCV not available for fallback detection") return {"detections": [], "detection_count": 0, "annotated_image_url": None} try: # Read the image img = cv2.imread(image_path) if img is None: logger.error(f"Failed to read image at {image_path} in fallback detection") return {"detections": [], "detection_count": 0, "annotated_image_url": None} # Convert to HSV for better color detection hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # Make a copy for annotation annotated = img.copy() # Initialize detections detections = [] # First check if the image contains water has_water = detect_water_body(image_path) logger.info(f"Water detection result: {'water detected' if has_water else 'no significant water detected'}") # Run texture-based detection for potential debris texture_detections = analyze_texture_for_pollution(img) detections.extend(texture_detections) logger.info(f"Texture analysis found {len(texture_detections)} potential debris objects") # Detect potential pollution based on color profiles for pollution_type, thresholds in COLOR_THRESHOLDS.items(): # Create mask using HSV thresholds mask = cv2.inRange(hsv, thresholds["lower"], thresholds["upper"]) # Apply some morphological operations to clean up the mask kernel = np.ones((5, 5), np.uint8) mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # Find contours contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # Filter out small contours, but with a smaller threshold for plastic debris if "plastic" in pollution_type: # More sensitive threshold for plastic items (0.5% of image) min_area = img.shape[0] * img.shape[1] * 0.005 else: # Standard threshold for other pollution (1% of image) min_area = img.shape[0] * img.shape[1] * 0.01 filtered_contours = [cnt for cnt in contours if cv2.contourArea(cnt) > min_area] # Process filtered contours for contour in filtered_contours: # Get bounding box x, y, w, h = cv2.boundingRect(contour) # Add to detections detections.append({ "class": thresholds["label"], "confidence": thresholds["confidence"], "bbox": [x, y, x + w, y + h] }) # Draw on the annotated image cv2.rectangle(annotated, (x, y), (x + w, y + h), (0, 255, 0), 2) # Add label label = f"{thresholds['label']}: {thresholds['confidence']:.2f}" cv2.putText(annotated, label, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) # Save the annotated image annotated_image_path = f"{image_path}_fallback_annotated.jpg" cv2.imwrite(annotated_image_path, annotated) # Return the results - would normally upload image in real implementation return { "detections": detections, "detection_count": len(detections), "annotated_image_path": annotated_image_path, "method": "fallback_color_detection" } except Exception as e: logger.error(f"Fallback detection failed: {str(e)}") return {"detections": [], "detection_count": 0, "annotated_image_url": None} def detect_water_body(image_path: str) -> bool: """ Simple detection to check if an image contains a large water body. This helps validate if the image is related to marine environment. Args: image_path: Path to the image file Returns: True if a significant water body is detected """ if not HAS_CV2: return True # Assume yes if we can't check try: # Read image img = cv2.imread(image_path) if img is None: return False # Convert to HSV hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # Water detection thresholds (blue/green tones) lower_water = np.array([90, 50, 50]) # Blue/green hues upper_water = np.array([150, 255, 255]) # Create mask mask = cv2.inRange(hsv, lower_water, upper_water) # Calculate percentage of water-like pixels water_percentage = np.sum(mask > 0) / (mask.shape[0] * mask.shape[1]) # Return True if water covers at least 30% of image return water_percentage > 0.3 except Exception as e: logger.error(f"Water detection failed: {str(e)}") return True # Assume yes if detection fails