Mif_E31230483_KlasifikasiTomat/main.py

305 lines
9.7 KiB
Python

import cv2
import numpy as np
import os
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns
import joblib
from sklearn.preprocessing import LabelEncoder
def extract_color_histogram(image, bins=(8, 8, 8)):
"""
Ekstraksi fitur Color Histogram HSV dari gambar
Parameters:
image: gambar dalam format BGR (OpenCV)
bins: jumlah bin untuk setiap channel HSV
Returns:
features: array fitur histogram yang telah dinormalisasi
"""
# Konversi dari BGR ke HSV
image_hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# Hitung dan normalisasi histogram untuk setiap channel HSV
features = []
for i in range(3):
hist = cv2.calcHist([image_hsv], [i], None, [bins[i]], [0, 256])
hist = cv2.normalize(hist, hist).flatten()
features.append(hist)
# Gabungkan semua histogram
features = np.concatenate(features)
return features
def plot_confusion_matrix(y_true, y_pred, class_names, save_path='confusion_matrix.png'):
"""
Menampilkan confusion matrix dalam bentuk heatmap dan menyimpannya sebagai file PNG
Parameters:
y_true: label aktual
y_pred: label prediksi
class_names: list nama kelas
save_path: path untuk menyimpan file gambar
"""
# Hitung confusion matrix
cm = confusion_matrix(y_true, y_pred)
# Buat figure
plt.figure(figsize=(10, 8))
# Buat heatmap dengan seaborn
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=class_names, yticklabels=class_names,
cbar_kws={'label': 'Jumlah Sampel'},
square=True,
linewidths=0.5,
annot_kws={'size': 12, 'weight': 'bold'})
# Set title dan labels
plt.title('Confusion Matrix - Klasifikasi Tingkat Kematangan Tomat',
fontsize=16, fontweight='bold', pad=20)
plt.xlabel('Kelas Prediksi', fontsize=12, fontweight='bold')
plt.ylabel('Kelas Aktual', fontsize=12, fontweight='bold')
# Rotate labels untuk better readability
plt.xticks(rotation=45, ha='right')
plt.yticks(rotation=0)
# Add text summary
total_samples = len(y_true)
accuracy = np.mean(y_true == y_pred) * 100
plt.figtext(0.5, 0.02, f'Total Sampel: {total_samples} | Akurasi: {accuracy:.2f}%',
ha='center', fontsize=11, style='italic')
# Adjust layout
plt.tight_layout()
# Save sebagai PNG dengan high quality
plt.savefig(save_path, dpi=300, bbox_inches='tight', facecolor='white')
# Tampilkan plot
plt.show()
# Print summary
print(f"\nConfusion Matrix telah disimpan sebagai: {save_path}")
print(f"Ukuran gambar: 300 DPI")
return cm
def load_dataset(dataset_path):
"""
Membaca seluruh gambar dari folder dataset dan melakukan ekstraksi fitur
Parameters:
dataset_path: path ke folder dataset
Returns:
features: array fitur dari semua gambar
labels: array label dari semua gambar
class_names: nama kelas
"""
features = []
labels = []
class_names = []
# Dapatkan semua folder kelas
classes = [d for d in os.listdir(dataset_path)
if os.path.isdir(os.path.join(dataset_path, d))]
classes.sort() # Urutkan untuk konsistensi
print(f"Ditemukan {len(classes)} kelas: {classes}")
for class_idx, class_name in enumerate(classes):
class_path = os.path.join(dataset_path, class_name)
class_names.append(class_name)
# Dapatkan semua file gambar dalam folder kelas
image_files = [f for f in os.listdir(class_path)
if f.lower().endswith(('.png', '.jpg', '.jpeg'))]
print(f"Memproses kelas '{class_name}': {len(image_files)} gambar")
for image_file in image_files:
image_path = os.path.join(class_path, image_file)
try:
# Baca gambar
image = cv2.imread(image_path)
if image is None:
print(f"Warning: Tidak dapat membaca gambar {image_path}")
continue
# Ekstraksi fitur
feature = extract_color_histogram(image)
features.append(feature)
labels.append(class_idx)
except Exception as e:
print(f"Error memproses {image_path}: {e}")
continue
return np.array(features), np.array(labels), class_names
def main():
"""
Fungsi utama untuk menjalankan proses klasifikasi tingkat kematangan tomat
"""
print("=" * 60)
print("KLASIFIKASI TINGKAT KEMATANGAN TOMAT")
print("Menggunakan Random Forest dan Color Histogram HSV")
print("=" * 60)
# Path ke dataset
dataset_path = "."
# Cek apakah folder dataset ada
if not os.path.exists(dataset_path):
print(f"Error: Folder '{dataset_path}' tidak ditemukan!")
print("Pastikan folder dataset ada dengan struktur:")
print("./")
print(" matang/")
print(" mentah/")
print(" setengah_matang/")
return
# Load dataset dan ekstraksi fitur
print("\n1. MEMBACA DATASET DAN EKSTRAKSI FITUR")
print("-" * 40)
features, labels, class_names = load_dataset(dataset_path)
print(f"\nTotal gambar yang berhasil diproses: {len(features)}")
print(f"Dimensi fitur per gambar: {features.shape[1]}")
# Split dataset menjadi training dan testing (80:20)
print("\n2. SPLIT DATASET")
print("-" * 40)
X_train, X_test, y_train, y_test = train_test_split(
features, labels,
test_size=0.2,
random_state=42,
stratify=labels
)
# Tampilkan jumlah data training dan testing
print(f"Jumlah data training: {len(X_train)}")
print(f"Jumlah data testing: {len(X_test)}")
print(f"Rasio training:testing = {len(X_train)/(len(X_train)+len(X_test)):.1f}:{len(X_test)/(len(X_train)+len(X_test)):.1f}")
# Tampilkan distribusi kelas
print("\nDistribusi kelas pada data training:")
for i, class_name in enumerate(class_names):
count = np.sum(y_train == i)
print(f" {class_name}: {count} gambar")
print("\nDistribusi kelas pada data testing:")
for i, class_name in enumerate(class_names):
count = np.sum(y_test == i)
print(f" {class_name}: {count} gambar")
# Training model Random Forest
print("\n3. TRAINING MODEL RANDOM FOREST")
print("-" * 40)
# Inisialisasi model
rf_model = RandomForestClassifier(
n_estimators=100,
random_state=42,
max_depth=10
)
# Training model
rf_model.fit(X_train, y_train)
print("Model Random Forest telah selesai training!")
# Evaluasi model
print("\n4. EVALUASI MODEL")
print("-" * 40)
# Prediksi pada data testing
y_pred = rf_model.predict(X_test)
# Tampilkan classification report
print("\nClassification Report:")
print(classification_report(y_test, y_pred, target_names=class_names))
# Tampilkan confusion matrix dengan visualisasi yang lebih baik
print("\nConfusion Matrix:")
cm = plot_confusion_matrix(y_test, y_pred, class_names, 'confusion_matrix_tomat.png')
print(cm)
# Tampilkan akurasi
accuracy = np.mean(y_pred == y_test)
print(f"\nAkurasi model: {accuracy:.4f} ({accuracy*100:.2f}%)")
# Feature importance
print("\n5. FEATURE IMPORTANCE")
print("-" * 40)
# Dapatkan feature importance
importances = rf_model.feature_importances_
# Kelompokkan berdasarkan channel RGB
bins_per_channel = len(importances) // 3
r_importance = np.sum(importances[:bins_per_channel])
g_importance = np.sum(importances[bins_per_channel:2*bins_per_channel])
b_importance = np.sum(importances[2*bins_per_channel:])
print(f"Importance channel R: {r_importance:.4f} ({r_importance*100:.2f}%)")
print(f"Importance channel G: {g_importance:.4f} ({g_importance*100:.2f}%)")
print(f"Importance channel B: {b_importance:.4f} ({b_importance*100:.2f}%)")
# Simpan model
print("\n6. MENYIMPAN MODEL")
print("-" * 40)
# Buat folder models jika belum ada
models_folder = "models"
if not os.path.exists(models_folder):
os.makedirs(models_folder)
print(f"Folder '{models_folder}' dibuat")
# Buat label encoder
label_encoder = LabelEncoder()
label_encoder.fit(class_names)
# Simpan model
try:
model_file = os.path.join(models_folder, "tomat_classifier.pkl")
joblib.dump(rf_model, model_file)
print(f"Model berhasil disimpan: {model_file}")
# Simpan label encoder
encoder_file = os.path.join(models_folder, "label_encoder.pkl")
joblib.dump(label_encoder, encoder_file)
print(f"Label encoder berhasil disimpan: {encoder_file}")
# Simpan metadata
metadata = {
'model_type': 'RandomForestClassifier',
'n_estimators': 100,
'max_depth': 10,
'n_features': features.shape[1],
'classes': class_names,
'accuracy': accuracy,
'histogram_bins': (8, 8, 8)
}
metadata_file = os.path.join(models_folder, "metadata.pkl")
joblib.dump(metadata, metadata_file)
print(f"Metadata berhasil disimpan: {metadata_file}")
except Exception as e:
print(f"Error menyimpan model: {e}")
print("\n" + "=" * 60)
print("PROSES KLASIFIKASI SELESAI!")
print("=" * 60)
if __name__ == "__main__":
main()