diff --git a/__pycache__/classifier.cpython-313.pyc b/__pycache__/classifier.cpython-313.pyc new file mode 100644 index 0000000..0885b41 Binary files /dev/null and b/__pycache__/classifier.cpython-313.pyc differ diff --git a/app.py b/app.py new file mode 100644 index 0000000..ea006f0 --- /dev/null +++ b/app.py @@ -0,0 +1,83 @@ +from pathlib import Path +import zipfile +import streamlit as st +from PIL import Image +from classifier import WasteClassifier + +# --- MODEL KONFİGÜRASYONLARI (Notebook Analiz Sonuçları) --- +APP_DIR = Path(__file__).resolve().parent +MODEL_CANDIDATES = [ + APP_DIR / "model" / "akilli_kutu_model.keras", + APP_DIR / "akilli_kutu_model.keras", +] + +def resolve_model_path() -> Path: + for candidate in MODEL_CANDIDATES: + if candidate.is_file() and zipfile.is_zipfile(candidate): + return candidate + for candidate in MODEL_CANDIDATES: + if candidate.is_file(): + return candidate + return MODEL_CANDIDATES[0] + +MODEL_PATH = resolve_model_path() + +# Klasörlerin alfabetik dizilimine sadık kalınmış Türkçe karşılıklar: +# ['cardboard', 'glass', 'metal', 'paper', 'plastic', 'trash'] +CLASSES = ["Karton", "Cam", "Metal", "Kağıt", "Plastik", "Genel Çöp"] +TARGET_SIZE = (224, 224) + +# Her etkileşimde modelin sıfırdan yüklenip sistemi dondurmaması için cache yapısı kullanıyoruz +@st.cache_resource +def load_cached_classifier(): + return WasteClassifier(model_path=str(MODEL_PATH), class_names=CLASSES, target_size=TARGET_SIZE) + + +def main(): + st.set_page_config(page_title="AI Atık Sınıflandırma", page_icon="♻️", layout="centered") + + st.title("♻️ Akıllı Hazne Atık Sınıflandırma Sistemi") + st.markdown("Eğitilen **MobileNetV2** modelini kullanarak atık türünü gerçek zamanlı tespit edin.") + st.write("---") + + + try: + classifier = load_cached_classifier() + except Exception as e: + st.error(f"Model yüklenirken kritik hata: {e}") + st.info("Lütfen model dosyasının doğru dizinde ve doğru adda olduğundan emin olun.") + return + + # Dosya Yükleme Alanı + uploaded_file = st.file_uploader("Siyah arka planda çekilmiş atık fotoğrafını yükleyin...", type=["jpg", "jpeg", "png"]) + + if uploaded_file is not None: + # Görseli RAM'e alıyoruz + image = Image.open(uploaded_file) + + + col1, col2 = st.columns(2) + + with col1: + st.image(image, caption="Yüklenen Nesne", use_container_width=True) + + with col2: + st.subheader("Model Analizi") + + with st.spinner("Model tahmin yürütüyor..."): + try: + # Tahmin motorunu çalıştır + prediction, confidence = classifier.predict(image) + + # Sonuç Ekranı + st.success(f"**Tespit Edilen Sınıf:** {prediction}") + st.metric(label="Güven Skoru", value=f"% {confidence:.2f}") + + # Kullanıcı yönlendirme kalkanı + st.info(f"Yapay zeka bu nesnenin **{prediction}** kutusuna atılmasını öneriyor.") + + except Exception as e: + st.error(f"Tahmin işlemi esnasında bir hata oluştu: {e}") + +if __name__ == "__main__": + main() diff --git a/classifier.py b/classifier.py new file mode 100644 index 0000000..fe47214 --- /dev/null +++ b/classifier.py @@ -0,0 +1,78 @@ +import numpy as np +from abc import ABC, abstractmethod +from pathlib import Path +import zipfile +from PIL import Image +import tensorflow as tf + + + +class BaseClassifier(ABC): + def __init__(self, class_names: list[str]) -> None: + self.class_names = class_names + self.model: tf.keras.Model | None = None + + @abstractmethod + def load_model(self, model_path: str) -> None: + pass + + @abstractmethod + def _preprocess_image(self, image: Image.Image) -> np.ndarray: + pass + + def predict(self, image: Image.Image) -> tuple[str, float]: + if self.model is None: + raise ValueError("Model henüz yüklenmedi.") + + processed_img = self._preprocess_image(image) + predictions = self.model.predict(processed_img) + + best_class_idx = np.argmax(predictions[0]) + + predicted_class = self.class_names[best_class_idx] + confidence = float(predictions[0][best_class_idx]) * 100 + + return predicted_class, confidence + + +class WasteClassifier(BaseClassifier): + """MobileNetV2 tabanlı çöp sınıflandırma modelinin yönetim sınıfı.""" + + def __init__(self, model_path: str, class_names: list[str], target_size: tuple[int, int] = (224, 224)): + super().__init__(class_names=class_names) + self.model_path = model_path + self.target_size = target_size + + + self.load_model(self.model_path) + + def load_model(self, model_path: str) -> None: + model_file = Path(model_path) + if not model_file.is_file(): + raise FileNotFoundError(f"Model dosyası bulunamadı: {model_file}") + if not zipfile.is_zipfile(model_file): + raise ValueError(f"Model dosyası geçersiz .keras arşivi: {model_file}") + + try: + self.model = tf.keras.models.load_model(str(model_file)) + except Exception as e: + raise RuntimeError(f"Model yüklenirken hata oluştu: {e}") + + + # Görüntü ön işleme adımları + def _preprocess_image(self, image: Image.Image) -> np.ndarray: + """Görüntüyü MobileNetV2 girdisine ve notebook'taki ön işleme adımlarına hazırlar.""" + # Eğer görsel PNG formatında ve saydam arka planlıysa, RGB'ye dönüştürerek alpha kanalını eliyoruz + if image.mode != "RGB": + image = image.convert("RGB") + + # Görseli modelin eğitim boyutu olan 224x224'e ayarladık + image = image.resize(self.target_size) + img_array = tf.keras.preprocessing.image.img_to_array(image) + + # Notebook'taki Veri Önişleme (rescale=1./255) adımı ile eşleme + img_array = img_array / 255.0 + + # Batch boyutu ekleme: (224, 224, 3) -> (1, 224, 224, 3) + img_array = np.expand_dims(img_array, axis=0) + return img_array diff --git a/akilli_kutu_model.keras b/model/akilli_kutu_model.keras similarity index 72% rename from akilli_kutu_model.keras rename to model/akilli_kutu_model.keras index dc55649..545221c 100644 Binary files a/akilli_kutu_model.keras and b/model/akilli_kutu_model.keras differ