DariusGiannoli
fix: 11 bugs β€” confusion matrix, multi-class localization, dedup RCE/NMS, validation guards
a2b92f9
import cv2
import numpy as np
import time
import joblib
from pathlib import Path
from src.config import MODEL_PATHS
class ORBDetector:
"""
The Ancestor: ORB (Oriented FAST and Rotated BRIEF).
ROBUST VERSION: Tuned for smooth/synthetic data.
"""
def __init__(self):
print("πŸ›οΈ Initializing Classical ORB Detector (Aggressive Mode)...")
# KEY CHANGE: fastThreshold=0 ensures we detect even faint corners
# nfeatures=2000 allows us to capture more context
self.orb = cv2.ORB_create(
nfeatures=500,
scaleFactor=1.2,
nlevels=8,
edgeThreshold=25, # Reduced from 31 to allow features near edges
fastThreshold=10 # Reduced from 20 to 0 (Max Sensitivity)
)
self.bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
self.reference_descriptors = None
self.model_path = MODEL_PATHS.get('orb_ref')
self.load_reference()
def load_reference(self):
if self.model_path and Path(self.model_path).exists():
self.reference_descriptors = joblib.load(self.model_path)
print(f"βœ… Loaded ORB Reference from {self.model_path}")
else:
print(f"⚠️ Reference not found. Run training/train_orb.py")
def train(self, images, labels):
print(f"πŸ›οΈ Training ORB on {len(images)} images...")
best_num_features = 0
best_descriptors = None
bird_images = [img for img, lbl in zip(images, labels) if lbl == 'bird']
if not bird_images:
print("❌ No bird images found.")
return
print(f" -> Scanning {len(bird_images)} bird images with High Sensitivity...")
for i, img in enumerate(bird_images):
if img is None: continue
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# CLAHE: Enhance contrast to help ORB see details in smooth images
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
gray = clahe.apply(gray)
kp, des = self.orb.detectAndCompute(gray, None)
count = 0 if des is None else len(des)
# print(f" [Img {i}] Features: {count}") # Uncomment if you need to debug
if des is not None and count > best_num_features:
best_num_features = count
best_descriptors = des
if best_descriptors is None:
raise RuntimeError(
"ORB training failed: no features detected. "
"Images may be too smooth, too small, or solid colors.")
else:
self.reference_descriptors = best_descriptors
if self.model_path:
joblib.dump(self.reference_descriptors, self.model_path)
print(f"πŸ’Ύ Success! Saved Reference with {best_num_features} features.")
def predict(self, image):
if self.reference_descriptors is None:
return "Untrained", 0.0, 0.0
t0 = time.perf_counter()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Apply same contrast enhancement
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
gray = clahe.apply(gray)
kp, des = self.orb.detectAndCompute(gray, None)
if des is None:
return "background", 0.0, (time.perf_counter() - t0) * 1000
matches = self.bf.match(self.reference_descriptors, des)
matches = sorted(matches, key=lambda x: x.distance)
# Relaxed matching for smooth images
good_matches = [m for m in matches if m.distance < 70]
score = len(good_matches)
# Lower threshold for detection
label = "bird" if score > 5 else "background"
confidence = min(score / 10.0, 1.0)
t1 = time.perf_counter()
return label, confidence, (t1 - t0) * 1000