Spaces:
Sleeping
Sleeping
| """ | |
| 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 |