Sanjay / app /services /fallback_detection.py
TheDeepDas's picture
Yolo
6bbbfda
"""
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