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