File size: 8,507 Bytes
24a683e |
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 |
# utils.py - Helper functions for insect detection demo
import cv2
import numpy as np
import matplotlib.pyplot as plt
from typing import List, Dict, Tuple, Optional
from ultralytics import YOLO
def perform_detection(
yolo_model: YOLO,
frame: np.ndarray,
conf_threshold: float=0.5
) -> Optional[List[Dict]]:
"""
Runs the YOLO model inference on a single frame.
"""
if frame is None:
print("Error: Input frame is None in perform_detection.")
return None
try:
# Perform inference using the model
results = yolo_model.predict(source=frame, conf=conf_threshold, verbose=False)
return results
except Exception as e:
print(f"Error during model prediction: {e}")
return None
def create_motion_mask(frame, threshold=25):
"""
Creates a simple motion mask from an image.
For the demo, we'll use a basic thresholding approach.
"""
# Convert to grayscale
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# Apply Gaussian blur to reduce noise
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# Apply adaptive thresholding
thresh = cv2.adaptiveThreshold(
blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV, 11, threshold
)
# Apply morphological operations to clean up the mask
kernel = np.ones((3, 3), np.uint8)
mask = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=2)
return mask
def postprocess_results(
results: Optional[List[Dict]],
model_class_names: Dict[int, str],
mask: Optional[np.ndarray] = None
) -> List[Dict]:
"""
Extracts information from detection results.
If a mask is provided, only keeps detections that overlap with the mask.
"""
detections_list = []
if results is None or not results:
return detections_list
try:
boxes = results[0].boxes
except (IndexError, AttributeError) as e:
print(f"Warning: Could not access boxes in results: {e}")
return detections_list
for box in boxes:
try:
# Extract bounding box coordinates (xyxy format)
xyxy = box.xyxy[0].cpu().numpy().astype(int)
x1, y1, x2, y2 = xyxy
# If we have a mask, check if this detection overlaps with it
if mask is not None:
# Check if the center of the bounding box is within the mask
center_x = (x1 + x2) // 2
center_y = (y1 + y2) // 2
# Also check if a significant portion of the box overlaps with the mask
# First make sure we stay within mask boundaries
y1_safe = max(0, min(y1, mask.shape[0]-1))
y2_safe = max(0, min(y2, mask.shape[0]-1))
x1_safe = max(0, min(x1, mask.shape[1]-1))
x2_safe = max(0, min(x2, mask.shape[1]-1))
# Extract the region of the mask corresponding to the bounding box
box_region = mask[y1_safe:y2_safe, x1_safe:x2_safe]
# Calculate overlap
if box_region.size > 0:
mask_coverage = np.sum(box_region > 0) / box_region.size
else:
mask_coverage = 0
# Skip this detection if it doesn't overlap with the mask
if not (0 <= center_y < mask.shape[0] and 0 <= center_x < mask.shape[1] and
(mask[center_y, center_x] > 0 or mask_coverage > 0.5)):
continue
# Extract confidence score
conf = float(box.conf[0].cpu().numpy())
# Extract class ID and map to class name
cls_id = int(box.cls[0].cpu().numpy())
class_name = model_class_names.get(cls_id, f"Unknown Class {cls_id}")
# Store detection info
detections_list.append({
'class_name': class_name,
'confidence': conf,
'bbox_xyxy': [x1, y1, x2, y2]
})
except Exception as e:
print(f"Error processing a detection box: {e}")
continue
return detections_list
def draw_detections(
frame: np.ndarray,
detections: List[Dict],
mask: Optional[np.ndarray] = None
) -> np.ndarray:
"""
Draws bounding boxes and labels on the frame.
If mask is provided, overlays it on the frame.
"""
output_frame = frame.copy()
# If we have a mask, overlay it with transparency
if mask is not None and mask.shape[0] > 0 and mask.shape[1] > 0:
# Create a colored mask for overlay
mask_overlay = np.zeros_like(output_frame)
mask_overlay[mask > 0] = [0, 100, 0] # Green tint for mask regions
# Blend mask with the frame
output_frame = cv2.addWeighted(output_frame, 0.7, mask_overlay, 0.3, 0)
# Draw bounding boxes
color = (0, 255, 0) # Green color for bounding box
font_scale = 1.2
font = cv2.FONT_HERSHEY_SIMPLEX
for detection in detections:
try:
x1, y1, x2, y2 = detection['bbox_xyxy']
class_name = detection['class_name']
conf = detection['confidence']
# Draw Bounding Box
cv2.rectangle(output_frame, (x1, y1), (x2, y2), color, 5)
# Prepare and Draw Label
label = f"{class_name}: {conf:.2f}"
# Calculate text size for background
(label_width, label_height), baseline = cv2.getTextSize(label, font, font_scale, 3)
label_ymin = max(y1, label_height + 10)
# Draw background for text
cv2.rectangle(output_frame,
(x1, label_ymin - label_height - 10),
(x1 + label_width, label_ymin - baseline),
color,
cv2.FILLED)
# Add text
cv2.putText(output_frame,
label,
(x1, label_ymin - 5),
font,
font_scale,
(255, 255, 255), # White color
3)
except Exception as e:
continue
return output_frame
def load_yolo_model(model_path):
"""
Loads the YOLO model from the specified path.
"""
print("Loading the YOLO model...")
try:
model = YOLO(model_path)
class_names = model.names
print(f"Model loaded with {len(class_names)} classes!")
return model, class_names
except Exception as e:
print(f"Error loading model: {e}")
return None, None
def load_image(image_path):
"""
Loads an image from the specified path.
"""
print(f"Opening image: {image_path}")
image = cv2.imread(image_path)
if image is None:
print(f"Error: Could not read image file '{image_path}'.")
return image
def load_or_create_mask(image, mask_path=None):
"""
Either loads a mask from disk or creates a new one from the image.
"""
if mask_path and os.path.exists(mask_path):
print(f"Loading mask: {mask_path}")
mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE)
else:
print("Creating mask from image...")
mask = create_motion_mask(image)
return mask
def display_results(output_frame, detections, mask=None):
"""
Displays detection results and saves the output image.
"""
# Display results in console
print("\n--- Insects Detected ---")
if detections:
for i, obj in enumerate(detections, 1):
print(f"{i}. {obj['class_name']} (confidence: {obj['confidence']:.2f})")
else:
print("No insects detected.")
# Save mask if it exists
if mask is not None:
cv2.imwrite("motion_mask.png", mask)
print("Motion mask saved to: motion_mask.png")
# Convert from BGR to RGB for display
output_rgb = cv2.cvtColor(output_frame, cv2.COLOR_BGR2RGB)
# Display the image
plt.figure(figsize=(10, 8))
plt.imshow(output_rgb)
plt.title("Insect Detection Results")
plt.axis('off')
plt.show()
# Save result
result_path = "detection_result.jpg"
cv2.imwrite(result_path, output_frame)
print(f"Result saved to: {result_path}")
import os # Added for file path operations |