wheat-disease-detection / BACKEND /preprocessing.py
hllerdgn's picture
Backend restructuring and YOLOv8 removal
fa44bdc
Raw
History Blame Contribute Delete
11.3 kB
"""
PREPROCESSING.PY — Görüntü Ön İşleme Modülü
İçerik:
• CLAHE (Contrast Limited Adaptive Histogram Equalization)
• Bulanıklık / kalite filtresi (Laplacian variance)
• Boyutlandırma & normalizasyon
• Tarımsal görüntü için renk analizi
"""
import cv2
import numpy as np
from PIL import Image
from pathlib import Path
from dataclasses import dataclass, field
from typing import Optional, Tuple
# ============================================================================
# 📦 VERİ YAPILARI
# ============================================================================
@dataclass
class QualityReport:
"""Görüntü kalite analizi sonucu."""
is_valid: bool
blur_score: float # Laplacian varyansı — yüksek = net
brightness: float # Ortalama parlaklık (0-255)
contrast: float # Standart sapma
has_green: bool # Bitki rengi var mı?
rejection_reason: Optional[str] = None
warnings: list = field(default_factory=list)
@dataclass
class PreprocessResult:
"""Ön işleme sonucu."""
image_pil: Image.Image # PyTorch modeline girecek PIL görüntüsü
image_cv2: np.ndarray # OpenCV formatı (detection için)
image_clahe: np.ndarray # CLAHE uygulanmış BGR
quality: QualityReport
original_size: Tuple[int, int] # (genişlik, yükseklik)
processed_size: Tuple[int, int]
# ============================================================================
# ⚙️ KALİBRASYON EŞİKLERİ
# ============================================================================
class QualityThresholds:
BLUR_MIN = 80.0 # Bu altı → bulanık
BLUR_WARN = 150.0 # Bu altı → uyarı
BRIGHTNESS_MIN = 30.0 # Çok karanlık
BRIGHTNESS_MAX = 230.0 # Çok parlak (overexposed)
CONTRAST_MIN = 15.0 # Düşük kontrast
GREEN_HUE_LOW = 25 # HSV yeşil alt sınır
GREEN_HUE_HIGH = 95 # HSV yeşil üst sınır
GREEN_MIN_RATIO = 0.05 # Görüntünün en az %5'i yeşil olmalı
# ============================================================================
# 🔧 ÖN İŞLEYİCİ
# ============================================================================
class ImagePreprocessor:
"""
Tarımsal görüntüler için ön işleme pipeline'ı.
Pipeline:
1. Görüntü yükleme & format dönüşümü
2. Kalite filtresi (blur, parlaklık, kontrast, yeşil oran)
3. CLAHE (kontrast iyileştirme)
4. Boyutlandırma
Kullanım:
preprocessor = ImagePreprocessor()
result = preprocessor.process("goruntu.jpg")
if result.quality.is_valid:
# pipeline'a geç
"""
def __init__(
self,
target_size: Tuple[int, int] = (640, 640), # YOLO için
clahe_clip: float = 3.0,
clahe_grid: Tuple[int, int] = (8, 8),
thresholds: QualityThresholds = None,
):
self.target_size = target_size
self.thresholds = thresholds or QualityThresholds()
# CLAHE nesnesi (LAB renk uzayının L kanalına uygulanır)
self.clahe = cv2.createCLAHE(
clipLimit = clahe_clip,
tileGridSize= clahe_grid,
)
# ── Ana Metod ─────────────────────────────────────────────────────────────
def process(
self,
source, # str yol | np.ndarray BGR | PIL.Image
skip_quality_check: bool = False,
) -> PreprocessResult:
"""
Görüntüyü yükler, kalitesini kontrol eder ve ön işler.
Args:
source : Dosya yolu, OpenCV array veya PIL Image
skip_quality_check : True → kalite filtresi atlanır
Returns:
PreprocessResult
"""
# 1. Yükle → BGR numpy
bgr = self._load_to_bgr(source)
original_size = (bgr.shape[1], bgr.shape[0]) # (W, H)
# 2. Kalite kontrolü
quality = self._check_quality(bgr) if not skip_quality_check \
else QualityReport(is_valid=True, blur_score=999,
brightness=128, contrast=50, has_green=True)
# 3. CLAHE uygula (geçersiz görüntülerde de uygula — API yanıtı için)
bgr_clahe = self._apply_clahe(bgr)
# 4. Boyutlandır (YOLO girdisi için)
bgr_resized = cv2.resize(bgr_clahe, self.target_size,
interpolation=cv2.INTER_LINEAR)
# 5. PIL'e çevir (Swin-T sınıflandırıcısı için)
pil_image = Image.fromarray(
cv2.cvtColor(bgr_resized, cv2.COLOR_BGR2RGB)
)
return PreprocessResult(
image_pil = pil_image,
image_cv2 = bgr_resized,
image_clahe = bgr_clahe,
quality = quality,
original_size = original_size,
processed_size = self.target_size,
)
# ── Görüntü Yükleme ───────────────────────────────────────────────────────
def _load_to_bgr(self, source) -> np.ndarray:
"""Her formatı BGR numpy array'e çevirir."""
if isinstance(source, np.ndarray):
# Zaten numpy — RGB mi BGR mi kontrol et
if source.ndim == 3 and source.shape[2] == 3:
return source # BGR kabul edilir
raise ValueError(f"Beklenmeyen numpy shape: {source.shape}")
if isinstance(source, Image.Image):
return cv2.cvtColor(np.array(source.convert("RGB")), cv2.COLOR_RGB2BGR)
# Dosya yolu
path = Path(source)
if not path.exists():
raise FileNotFoundError(f"Görüntü bulunamadı: {path}")
bgr = cv2.imread(str(path))
if bgr is None:
raise ValueError(f"Görüntü okunamadı: {path}")
return bgr
# ── CLAHE ─────────────────────────────────────────────────────────────────
def _apply_clahe(self, bgr: np.ndarray) -> np.ndarray:
"""
LAB renk uzayında yalnızca L (parlaklık) kanalına CLAHE uygular.
Renk bilgisini (hastalık rengi!) bozmaz.
"""
lab = cv2.cvtColor(bgr, cv2.COLOR_BGR2LAB)
l, a, b = cv2.split(lab)
l_clahe = self.clahe.apply(l)
lab_clahe = cv2.merge([l_clahe, a, b])
return cv2.cvtColor(lab_clahe, cv2.COLOR_LAB2BGR)
# ── Kalite Kontrolü ───────────────────────────────────────────────────────
def _check_quality(self, bgr: np.ndarray) -> QualityReport:
t = self.thresholds
warnings = []
gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
# 1. Bulanıklık (Laplacian varyansı)
blur_score = float(cv2.Laplacian(gray, cv2.CV_64F).var())
if blur_score < t.BLUR_MIN:
return QualityReport(
is_valid=False, blur_score=blur_score,
brightness=0, contrast=0, has_green=False,
rejection_reason=f"Görüntü çok bulanık (skor: {blur_score:.1f} < {t.BLUR_MIN})",
)
if blur_score < t.BLUR_WARN:
warnings.append(f"⚠️ Düşük netlik (skor: {blur_score:.1f})")
# 2. Parlaklık
brightness = float(gray.mean())
if brightness < t.BRIGHTNESS_MIN:
return QualityReport(
is_valid=False, blur_score=blur_score,
brightness=brightness, contrast=0, has_green=False,
rejection_reason=f"Görüntü çok karanlık (parlaklık: {brightness:.1f})",
)
if brightness > t.BRIGHTNESS_MAX:
return QualityReport(
is_valid=False, blur_score=blur_score,
brightness=brightness, contrast=0, has_green=False,
rejection_reason=f"Görüntü aşırı parlak/overexposed ({brightness:.1f})",
)
# 3. Kontrast
contrast = float(gray.std())
if contrast < t.CONTRAST_MIN:
warnings.append(f"⚠️ Düşük kontrast (std: {contrast:.1f})")
# 4. Yeşil oran kontrolü (bitki görüntüsü mü?)
hsv = cv2.cvtColor(bgr, cv2.COLOR_BGR2HSV)
green_mask = cv2.inRange(
hsv,
np.array([t.GREEN_HUE_LOW, 30, 30]),
np.array([t.GREEN_HUE_HIGH, 255, 255]),
)
green_ratio = float(green_mask.sum() / 255) / (bgr.shape[0] * bgr.shape[1])
has_green = green_ratio >= t.GREEN_MIN_RATIO
if not has_green:
warnings.append(
f"⚠️ Düşük yeşil oran ({green_ratio*100:.1f}%) — "
"bitki görüntüsü olmayabilir"
)
return QualityReport(
is_valid = True,
blur_score = blur_score,
brightness = brightness,
contrast = contrast,
has_green = has_green,
warnings = warnings,
)
# ── Yardımcılar ───────────────────────────────────────────────────────────
def quality_to_dict(self, q: QualityReport) -> dict:
return {
"is_valid" : q.is_valid,
"blur_score" : round(q.blur_score, 2),
"brightness" : round(q.brightness, 2),
"contrast" : round(q.contrast, 2),
"has_green" : q.has_green,
"rejection_reason": q.rejection_reason,
"warnings" : q.warnings,
}
# ============================================================================
# 🧪 TEST
# ============================================================================
if __name__ == "__main__":
import sys
preprocessor = ImagePreprocessor(target_size=(640, 640))
# Sentetik test görüntüsü
dummy_bgr = np.random.randint(40, 200, (480, 640, 3), dtype=np.uint8)
# Biraz yeşil ekle
dummy_bgr[:, :, 1] = np.clip(dummy_bgr[:, :, 1] + 60, 0, 255)
result = preprocessor.process(dummy_bgr, skip_quality_check=False)
print(f"Geçerli mi : {result.quality.is_valid}")
print(f"Bulanıklık : {result.quality.blur_score:.2f}")
print(f"Parlaklık : {result.quality.brightness:.2f}")
print(f"Yeşil var mı : {result.quality.has_green}")
print(f"Orijinal boyut : {result.original_size}")
print(f"İşlenmiş boyut : {result.processed_size}")
print(f"PIL mode : {result.image_pil.mode} {result.image_pil.size}")
print(f"OpenCV shape : {result.image_cv2.shape}")
print(f"Uyarılar : {result.quality.warnings}")
print("✅ Preprocessing modülü çalışıyor.")