Spaces:
Sleeping
Sleeping
File size: 14,140 Bytes
6bbbfda |
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 |
"""
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 |