Computer vision · Python

Face detection con scikit-learn

Versione integrata nel sito senza iframe. Il notebook ricostruito resta disponibile come file .ipynb.

Codice
import os
import threading
import time
from tqdm import tqdm
import sys
import math
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from joblib import load
import seaborn as sns
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.model_selection import RandomizedSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn import svm
from sklearn.metrics import classification_report, accuracy_score
from sklearn.utils import resample
import tkinter as tk
from tkinter import filedialog
from matplotlib.patches import Rectangle
Codice
from IPython.display import HTML

with open("face_detection_colab.html", "r", encoding="utf-8") as f:
    html_content = f.read()

display(HTML(html_content))
Codice
class Animation:
    def __init__(self, message="Sto convertendo", interval=0.5):

        self.message = message
        self.interval = interval
        self.stop_event = threading.Event()
        self.thread = threading.Thread(target=self.animate)

    def animate(self):
        dots = 0
        while not self.stop_event.is_set():
            dots = (dots + 1) % 4  # 0,1,2,3
            if dots == 0:
                display = self.message
            else:
                display = self.message + "." * dots
            # Carriage return per sovrascrivere la linea precedente
            sys.stdout.write("\r" + display)
            sys.stdout.flush()
            time.sleep(self.interval)
        # Pulizia della linea dopo l'animazione
        sys.stdout.write("\r" + " " * (len(self.message) + 3) + "\r")
        sys.stdout.flush()

    def start(self):

        self.thread.start()

    def stop(self):

        self.stop_event.set()
        self.thread.join()
Codice
def measure_time(func, *args, **kwargs):

    start_time = time.perf_counter()
    result = func(*args, **kwargs)
    end_time = time.perf_counter()
    elapsed_time = end_time - start_time
    return result, elapsed_time
Codice
def resize_image_binning(img, scale_percent):

    # Calcolo il fattore di ridimensionamento in un fattore decimale
    scale_factor = scale_percent / 100.0

    # Calcolo le nuove dimensioni, math.floor è per arrotondare
    new_height = math.floor(img.shape[0] * scale_factor) # img.shape[0] = dimensione originale h
    new_width = math.floor(img.shape[1] * scale_factor)  # img.shape[1] = dimensione originale w

    # Calcolo il fattore di binning, i fattori determinano quanti pixel devono essere combinati in un blocco per ridimensionare l’immagine tramite binning
    # Le dimensioni originali vengono divise per quelle nuove ed il risultato combinato in un unico pixel nell’immagine finale
    # Esempio: bin_factor_h = 2000 // 500
             # bin_factor_w = 1000 // 250
             # risultato: 4 e 4
    # ogni blocco di 4x4 pixel verrà combinato in un unico pixel nell’immagine finale

    bin_factor_h = img.shape[0] // new_height
    bin_factor_w = img.shape[1] // new_width

    if bin_factor_h < 1 or bin_factor_w < 1:
        raise ValueError("Scale_percentage ha un valore troppo grande e l'immagine risulterà troppo piccola.")

    # cropped_height e cropped_width sono il prodotto di new_height e new_width con i rispettivi fattori di binning (bin_factor_h e bin_factor_w)
    # Facendo così l'immagine è divisibile in blocchi per eseguire il binning
    # Rrappresentano la porzione dell’immagine che può essere divisa esattamente in blocchi di dimensioni bin_factor_h e bin_factor_w
    cropped_height = bin_factor_h * new_height
    cropped_width = bin_factor_w * new_width
    if img.shape[0] < cropped_height or img.shape[1] < cropped_width:
        raise ValueError("Dimensioni dell'immagine troppo piccole per il binning richiesto.")

    # Se img.ndim == 3 vuol dire che l'immagine ha tre dimensioni,quindi a colori RGB
    if img.ndim == 3:
        # Questo ritaglio assicura che l’immagine sia esattamente della dimensione richiesta per il binning, evitando bordi non divisibili
        img_cropped = img[:cropped_height, :cropped_width, :]
            # img_cropped viene ridimensionato in una struttura a 5 dimensioni
            # new_height e new_width sono le dimensioni finali che voglio ottenere
            # bin_factor_h e bin_factor_w indicano il numero di pixel da combinare
            # img.shape[2] mantiene i canali di colore
            # calcola la media dei pixel lungo le dimensioni di binning riducendo così l'immagine alla risoluzione richiesta
            # ogni pixel rappresenta la media dei blocchi originali
        img_binned = img_cropped.reshape(new_height, bin_factor_h, new_width, bin_factor_w, img.shape[2]).mean(axis=(1,3))
            # reshape applica il binning creando una struttura 5D contenente le nuove dimensioni, i fattori di bin e i canali colore
            # continuando con l'esempio di prima     500 righe di blocchi, ciascuno alto 4 pixel
                                                   # 250 colonne di blocchi, ciascuno largo 4 pixel
                                                   # 3 canali per i colori (rosso, verde, blu)
    else:
        raise ValueError("Formato immagine non supportato.")

    # Converto in uint8 se necessario
    # uint8 rappresenta numeri interi con un range di valori compreso tra 0 e 255 senza valori negativi
    if img_binned.dtype != np.uint8:
        img_binned = (img_binned).astype(np.uint8)

    return img_binned
Codice
def resize_images(input_folder, output_folder, scale_percent):
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)
        print(f"Creata cartella di output per il ridimensionamento: {output_folder}")

    # Elenco i file nella cartella di input
    files = os.listdir(input_folder)
    jpg_files = [f for f in files if f.lower().endswith(('.jpg', '.jpeg'))]
    total_files = len(jpg_files)
    print(f"Numero totale di immagini da ridimensionare: {total_files}")

    converted_count = 0

    # Istanza della classe Animation utilizzata durante la conversione delle immagini
    animation = None

    # Iterazione su ogni file contenuto su jpg_files
    for filename in jpg_files:
        img_path = os.path.join(input_folder, filename)
        resized_filename = os.path.splitext(filename)[0] + '_resized.jpg'
        resized_path = os.path.join(output_folder, resized_filename)

        try:
            # Funzione per il ridimensionamento
            def process_image(path):
                # Leggo l'immagine JPG
                img = plt.imread(path)
                return img #resituisce un array NUmPy

            # Misura del tempo di lettura dell'immagine
            # img_path viene passato a process_image tramite il parametro path
            img, elapsed_time = measure_time(process_image, img_path)

            # Ridimensiono l'immagine
            if scale_percent != 100:
                img_resized, resize_time = measure_time(resize_image_binning, img, scale_percent)
            else:
                img_resized = img
                resize_time = 0.0  # Nessun ridimensionamento effettuato

            # Salvo l'immagine ridimensionata come JPG nella cartella di output
            def save_image(path, image):
                plt.imsave(path, image)

            # questa funzione chiama save_image per salvare l’immagine img_resized in resized_path e
            # calcola il tempo necessario per farlo, assegnandolo alla variabile save_time
            _, save_time = measure_time(save_image, resized_path, img_resized)

            converted_count += 1

            # Calcola il tempo totale per questa operazione
            total_elapsed = elapsed_time + resize_time + save_time

            # Se il conteggio delle immagini elaborate è inferiore o uguale a 5, stampa un messaggio con
            # il numero di immagini elaborate, il percorso dell’immagine ridimensionata e il tempo totale impiegato
            if converted_count <= 5:
                print(f"[{converted_count}/{total_files}] Ridimensionata: {img_path} -> {resized_path} in {total_elapsed:.4f} secondi.")

            # Avvio l'animazione dopo i primi 5 ridimensionamenti
            if converted_count == 6:
                animation = Animation(message="Sto ridimensionando")
                animation.start()

        except Exception as e:
            print(f"Errore nel ridimensionamento di {img_path}: {e}")

    # Segnala di fermare l'animazione
    if converted_count > 5 and animation is not None:
        animation.stop()
        print("Ridimensionamento completato.")
    else:
        print("Ridimensionamento completato.")
Codice
# Percorsi delle cartelle
faces_jpg = '/content/drive/Othercomputers/Il mio laptop/Desktop/face_detection/prova/face_jpg'
faces_jpg_resized = '/content/drive/Othercomputers/Il mio laptop/Desktop/face_detection/prova/face_jpg_resized'

no_faces_jpg = '/content/drive/Othercomputers/Il mio laptop/Desktop/face_detection/prova/no_face_jpg'
no_faces_jpg_resized = '/content/drive/Othercomputers/Il mio laptop/Desktop/face_detection/prova/no_face_jpg_resized'
Codice
resize_images(
    input_folder=faces_jpg,
    output_folder=faces_jpg_resized,
    scale_percent=50
)

resize_images(
    input_folder=no_faces_jpg,
    output_folder=no_faces_jpg_resized,
    scale_percent=50
)
Codice
def convert_jpg_to_ppm(input_folder, output_folder):
    # Creazione della cartella di output se non esiste
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)
        print(f"Creata cartella di output: {output_folder}")

    # Lista dei file nella cartella di input
    files = os.listdir(input_folder)
    jpg_files = [f for f in files if f.lower().endswith(('.jpg', '.jpeg'))]
    total_files = len(jpg_files)
    print(f"Numero totale di immagini da convertire: {total_files}")

    # Contatore per le immagini convertite
    converted_count = 0

    # Istanza della classe Animation
    animation = None

    # Iterazione sulle immagini
    for filename in jpg_files:
        img_path = os.path.join(input_folder, filename)
        ppm_filename = os.path.splitext(filename)[0] + '.ppm'
        ppm_path = os.path.join(output_folder, ppm_filename)

        try:
            # Misurazione del tempo
            start_time = time.perf_counter()

            # Leggo l'immagine JPG
            img = plt.imread(img_path)

            # Salvo l'immagine in formato PPM
            plt.imsave(ppm_path, img)

            # Fine della misurazione del tempo
            end_time = time.perf_counter()
            elapsed_time = end_time - start_time

            converted_count += 1

            # Stampa del tempo per le prime 5 conversioni
            if converted_count <= 5:
                print(f"[{converted_count}/{total_files}] Convertita: {img_path} -> {ppm_path} in {elapsed_time:.4f} secondi.")

            # Avvia l'animazione dopo le prime 5 conversioni
            if converted_count == 6:
                animation = Animation(message="Sto convertendo")
                animation.start()

        except Exception as e:
            print(f"Errore nella conversione di {img_path}: {e}")

    # Segnala di fermare l'animazione
    if converted_count > 5 and animation is not None:
        animation.stop()
        print("Conversione completata.")
    else:
        print("Conversione completata.")
Codice
faces_resized_folder  = '/content/drive/Othercomputers/Il mio laptop/Desktop/face_detection/prova/face_jpg_resized'
faces_output_ppm  = '/content/drive/Othercomputers/Il mio laptop/Desktop/face_detection/prova/face_PPM'

no_faces_resized_folder  = '/content/drive/Othercomputers/Il mio laptop/Desktop/face_detection/prova/no_face_jpg_resized'
no_faces_output_ppm  = '/content/drive/Othercomputers/Il mio laptop/Desktop/face_detection/prova/no_face_PPM'
Codice
# Converto i fole da jpg a PPM
convert_jpg_to_ppm(faces_resized_folder, faces_output_ppm)

convert_jpg_to_ppm(no_faces_resized_folder, no_faces_output_ppm)
Codice
def read_ppm(filename):
    with open(filename, 'rb') as f:
        magic_number = f.readline().strip()
        if magic_number != b'P6':
            raise ValueError(f"Formato PPM non supportato: {magic_number.decode()}. Solo il formato P6 è supportato.")

        # Leggo le intestazioni: width, height, maxval
        width, height, maxval = None, None, None
        while True:
            line = f.readline().strip()
            if line.startswith(b'#') or len(line) == 0:
                continue  # Ignorare commenti e linee vuote
            tokens = line.split()
            if not width and len(tokens) >= 2:
                width, height = int(tokens[0]), int(tokens[1])
                if len(tokens) > 2:
                    maxval = int(tokens[2])
                    break
            elif width and not maxval and len(tokens) >= 1:
                maxval = int(tokens[0])
                break

        if width is None or height is None or maxval is None:
            raise ValueError("Intestazione PPM incompleta o formattata in modo errato.")

        # Leggo i dati dei pixel
        pixel_data = f.read(width * height * 3)
        if len(pixel_data) != width * height * 3:
            raise ValueError("I dati dei pixel nel file PPM sono incompleti o corrotti.")

        image = np.frombuffer(pixel_data, dtype=np.uint8).reshape((height, width, 3))
    return image
Codice
def rgb_to_hsv_manual(R, G, B):
    # Normalizzo i valori RGB a [0, 1]
    R_norm = R / 255.0
    G_norm = G / 255.0
    B_norm = B / 255.0

    # Stack dei canali
    rgb = np.stack((R_norm, G_norm, B_norm), axis=-1)

    hsv = colors.rgb_to_hsv(rgb)

    H = hsv[:, :, 0]
    S = hsv[:, :, 1]
    V = hsv[:, :, 2]

    return H, S, V
Codice
def create_dataset_from_folder(ppm_folder, label, csv_filename=None):
    X = []
    y = []

    for file in os.listdir(ppm_folder):
        if file.lower().endswith('.ppm'):
            ppm_path = os.path.join(ppm_folder, file)
            try:
                image = read_ppm(ppm_path)
                print(f"Lettura dell'immagine {ppm_path} riuscita.")

                R = image[:, :, 0].astype(float)
                G = image[:, :, 1].astype(float)
                B = image[:, :, 2].astype(float)

                # Evito divisioni per zero
                G[G == 0] = 1.0
                B[B == 0] = 1.0

                # Calcolo il rapporto R/G
                R_G_ratio = R / G

                # Definisco B come R / 3
                B = R / 3

                # Assegno B a B_value
                B_value = B

                # Converto in HSV
                H, S, V = rgb_to_hsv_manual(R, G, B)
                print(f"Conversione HSV completata per {ppm_path}.")

                tolerance = 0.2

                # Verifico se B è circa uguale a R/3
                if np.allclose(B, R / 3, atol=tolerance):
                    # Con flatten() ogni caratteristica dell’immagine (come il rapporto R/G, il valore B, e le componenti H, S, e V)
                    # diventa un singolo vettore di valori facilitando la creazione di un array di caratteristiche omogeneo
                    features = np.column_stack((
                        R_G_ratio.flatten(),
                        B_value.flatten(),
                        H.flatten(),
                        S.flatten(),
                        V.flatten()
                    ))
                    X.append(features)
                    y.extend([label] * len(R.flatten()))
                    print(f"Dati aggiunti per {ppm_path}.")
                else:
                    print(f"B non corrisponde a R/3 per il file {ppm_path}")
                    # Puoi decidere come gestire i casi non validi, ad esempio ignorare il file

            except Exception as e:
                print(f"Errore nel leggere o elaborare il file {ppm_path}: {e}")

    # Verifica se X contiene dati prima di eseguire np.vstack
    if X:
        try:
            # Converti X in un array NumPy
            X = np.vstack(X)  # Combina tutte le immagini in un unico array
        except ValueError as ve:
            print(f"Errore durante la concatenazione degli array: {ve}")
            X = np.array([])  # In caso di errore, assegna un array vuoto
    else:
        X = np.array([])  # Se X è vuoto, assegna un array vuoto

    # Salva in CSV se specificato e X non è vuoto
    if csv_filename and X.size > 0:
        df = pd.DataFrame(X, columns=['R_G_ratio', 'B_value', 'H', 'S', 'V'])
        df['Label'] = y
        df.to_csv(csv_filename, index=False)
        print(f"Dataset salvato in {csv_filename}")
    elif csv_filename:
        print(f"Nessun dato valido da salvare in {csv_filename}")

    return X, y
Codice
# Definizione delle cartelle e dei percorsi
skin_ppm_folder = '/content/drive/Othercomputers/Il mio laptop/Desktop/face_detection/prova/face_PPM'
non_skin_ppm_folder = '/content/drive/Othercomputers/Il mio laptop/Desktop/face_detection/prova/no_face_PPM'

skin_csv_path = '/content/drive/Othercomputers/Il mio laptop/Desktop/face_detection/prova/skin_dataset_rg_rb_hsv.csv'
non_skin_csv_path = '/content/drive/Othercomputers/Il mio laptop/Desktop/face_detection/prova/non_skin_dataset_rg_rb_hsv.csv'
combined_csv_path = '/content/drive/Othercomputers/Il mio laptop/Desktop/face_detection/prova/combined_skin_non_skin_dataset_rg_rb_hsv.csv'

count = 0

# Creo i dataset per skin e non-skin
print("Creazione del dataset per le immagini con volti...")
(X_skin, y_skin), time_skin = measure_time(lambda: create_dataset_from_folder(skin_ppm_folder, label=1, csv_filename=skin_csv_path))
print(f"Tempo per creare il dataset skin: {time_skin:.2f} secondi")
count += 1

# Stampa del tempo per le prime 5 conversioni
if count <= 5:
    print(f"[{count}/...] Dataset skin creato.")

# L'animazione inizia dopo le prime 5 conversioni
if count == 6:
    animation = Animation(message="Sto convertendo")
    animation.start()

print("Creazione del dataset per le immagini senza volti...")
(X_non_skin, y_non_skin), time_non_skin = measure_time(lambda: create_dataset_from_folder(non_skin_ppm_folder, label=0, csv_filename=non_skin_csv_path))
print(f"Tempo per creare il dataset non-skin: {time_non_skin:.2f} secondi")
count +=1

# Stampa del tempo per le prime 5 conversioni
if count <= 5:
    print(f"[{count}/...] Dataset non-skin creato.")

if count == 6:
    animation = Animation(message="Sto convertendo")
    animation.start()

# Combino i dataset e le etichette
if X_skin.size > 0 and X_non_skin.size > 0:
    X = np.vstack((X_skin, X_non_skin))  # Caratteristiche combinate (R_G_ratio, B_value, H, S, V)
    y = np.concatenate((y_skin, y_non_skin))  # Etichette combinate

    # Salvo il dataset combinato in CSV
    combined_df = pd.DataFrame(X, columns=['R_G_ratio'])
    combined_df['Label'] = y
    combined_df.to_csv(combined_csv_path, index=False)
    print(f"Dataset combinato salvato in '{combined_csv_path}'")


    print("Prime 5 righe del dataset combinato:")
    print(combined_df.head())

else:
    print("Uno dei dataset (skin o non-skin) è vuoto.")
Codice
BASE_URL = "/content/drive/Othercomputers/Il mio laptop/Desktop/face_detection/prova/"
Codice
df_combined_rg_rb_hsv = pd.read_csv(BASE_URL + "combined_skin_non_skin_dataset_rg_rb_hsv.csv")
Codice
df_combined_rg_rb_hsv.shape
Codice
df_combined_rg_rb_hsv.count()
Codice
df_combined_rg_rb_hsv.head()
Codice
df_combined_rg_rb_hsv.info()
Codice
df_combined_rg_rb_hsv.describe
Codice
df_combined_rg_rb_hsv.isna()
Codice
df_combined_rg_rb_hsv.isnull()
Codice
class_distribution=(df_combined_rg_rb_hsv['Label'].value_counts(normalize=True))
Codice
plt.figure(figsize=(10, 6))
class_distribution.plot(kind='bar')
plt.title('Distribuzione delle classi (Response)')
plt.xlabel('Classi')
plt.ylabel('Percentuale')
plt.xticks(rotation=0)
plt.show()
Codice
# Converto i dati in array NumPy per l'addestramento
X = np.array(X)
y = np.array(y)

# Suddivido il dataset in training (70%) e temporaneo (validation + test) (30%)
X_train, X_temp, y_train, y_temp = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)

# Suddivido il set temporaneo in validation (15%) e test (15%)
X_val, X_test, y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42, stratify=y_temp)

print(f"Shape di X_train: {X_train.shape}")
print(f"Shape di X_val: {X_val.shape}")
print(f"Shape di X_test: {X_test.shape}")

print(f"Numero di dati nel Training set: {len(X_train)}")
print(f"Numero di dati nel Validation set: {len(X_val)}")
print(f"Numero di dati nel Test set: {len(X_test)}")
Codice
animation = Animation(message="Normalizzazione delle feature", interval=0.5)
animation.start()

# Normalizzazione delle feature
scaler = StandardScaler()

X_train_scaled, time_train_scaler = measure_time(lambda: scaler.fit_transform(X_train))
X_val_scaled, time_val_scaler = measure_time(lambda: scaler.transform(X_val))
X_test_scaled, time_test_scaler = measure_time(lambda: scaler.transform(X_test))

animation.stop()

print(f"Tempo per normalizzare il training set: {time_train_scaler:.2f} secondi")
print(f"Tempo per normalizzare il validation set: {time_val_scaler:.2f} secondi")
print(f"Tempo per normalizzare il test set: {time_test_scaler:.2f} secondi")
Codice
animation = Animation(message="Addestramento del modello Random Forest", interval=0.5)
animation.start()

# Addestramento del modello Random Forest e misura del tempo
colab_clf_rf_rg_rb_hsv, time_rf = measure_time(RandomForestClassifier(
    n_estimators=100,
    max_depth=10,
    random_state=42,
    n_jobs=-1).fit, X_train_scaled, y_train)

# Ferma l'animazione
animation.stop()


# Salva il modello addestrato
dump(colab_clf_rf_rg_rb_hsv, '/content/drive/Othercomputers/Il mio laptop/Desktop/face_detection/prova/colab_clf_rf_rg_rb_hsv.joblib')

print(f"Tempo per addestrare il modello Random Forest (con max_depth=10 e n_estimators=100): {time_rf:.2f} secondi")
Codice
# Inizializza l'animazione per la previsione
animation = Animation(message="Previsione sul test set", interval=0.5)
animation.start()

# Previsione sul test set e misura del tempo
y_test_pred, time_test_pred = measure_time(colab_clf_rf_rg_rb_hsv.predict, X_test_scaled)

# Ferma l'animazione
animation.stop()

# Valutazione del modello sul test set
print("Report di classificazione sul Test Set:")
print(classification_report(y_test, y_test_pred))
print(f"Accuratezza Test: {accuracy_score(y_test, y_test_pred)*100:.2f}%")
print(f"Tempo per prevedere sul test set: {time_test_pred:.2f} secondi")
Codice
# Inizializza l'animazione per la previsione sul validation set
animation = Animation(message="Previsione sul validation set", interval=0.5)
animation.start()

# Previsione sul validation set e misura del tempo
y_val_pred, time_val_pred = measure_time(colab_clf_rf_rg_rb_hsv.predict, X_val_scaled)

# Ferma l'animazione
animation.stop()

# Valutazione del modello sul validation set
print("Report di classificazione sul Validation Set:")
print(classification_report(y_val, y_val_pred))
print(f"Accuratezza Validation: {accuracy_score(y_val, y_val_pred)*100:.2f}%")
print(f"Tempo per prevedere sul validation set: {time_val_pred:.2f} secondi")
Codice
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix

def plot_confusion_matrix(y_true, y_pred, model_name):

    cm = confusion_matrix(y_true, y_pred)

    plt.figure(figsize=(6, 4))

    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', cbar=False)

    plt.title(f"Matrice di Confusione - {model_name}")
    plt.xlabel('Previsione')
    plt.ylabel('Reale')

    plt.show()

    print(f"\nMatrice di Confusione - {model_name}:")
    print(cm)
Codice
plot_confusion_matrix(y_test, y_test_pred, model_name="Random Forest Test Set")
Codice
# Definizione della griglia degli iperparametri
param_grid = {
    'n_estimators': [50, 80, 110],
    'max_depth': [2, 3, 4],
    'min_samples_split': [14, 18, 22],
    'min_samples_leaf': [3, 4, 5],
    'bootstrap': [True, False]
}


scoring = {
    'f1': 'f1',
    'accuracy': 'accuracy',
    'precision': 'precision',
    'recall': 'recall'
}



# Inizializzazione GridSearchCV con RandomForestClassifier
grid_search = GridSearchCV(
    estimator=RandomForestClassifier(
        random_state=42,
        n_jobs=-1,
        class_weight='balanced'  # Manteniamo il bilanciamento delle classi
    ),
    param_grid=param_grid,
    cv=5,  # Numero di fold per la cross-validazione
    n_jobs=-1,  # Utilizza tutti i core disponibili
    verbose=2,  # Livello di verbosità
    scoring=scoring,  # Specifica le metriche di scoring
    refit='f1'  # Indica quale metrica usare per rifitare il modello
)
Codice
# Inizializzazione l'animazione per Grid Search
animation = Animation(message="Ottimizzazione con Grid Search", interval=0.5)
animation.start()

# Addestramento del modello con Grid Search e misura del tempo
grid_search, time_grid_search = measure_time(grid_search.fit, X_train_scaled, y_train)

animation.stop()


print(f"Tempo per Grid Search: {time_grid_search:.2f} secondi")

# Migliori parametri trovati
print("Migliori parametri trovati con Grid Search:")
print(grid_search.best_params_)


clf_rf_gridsearch = grid_search.best_estimator_

model_save_path = '/content/drive/Othercomputers/Il mio laptop/Desktop/face_detection/prova/clf_rf_rg_rb_hsv_gridsearch.joblib'
dump(clf_rf_gridsearch, model_save_path)
print(f"Migliore modello Random Forest addestrato e salvato in: {model_save_path}")
Codice
param_dist = {
    'n_estimators': [10, 30, 50],
    'max_depth': [10, 20, None],
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4],
    'bootstrap': [True, False]
}


scoring = {
    'f1': 'f1',
    'accuracy': 'accuracy',
    'precision': 'precision',
    'recall': 'recall'
}

# Inizializza RandomizedSearchCV con RandomForestClassifier
random_search = RandomizedSearchCV(
    estimator=RandomForestClassifier(
        random_state=42,
        n_jobs=-1,
        class_weight='balanced'  # Manteniamo il bilanciamento delle classi
    ),
    param_distributions=param_dist,
    n_iter=50,  # Numero di combinazioni casuali da esplorare
    cv=3,  # Numero di fold per la cross-validazione (ridotto per risparmiare risorse)
    n_jobs=-1,  # Utilizza tutti i core disponibili
    verbose=2,  # Livello di verbosità
    scoring=scoring,  # Specifica le metriche di scoring
    refit='f1',  # Indica quale metrica usare per rifitare il modello
    random_state=42
)
Codice
animation = Animation(message="Ottimizzazione con Randomized Search", interval=0.5)
animation.start()

random_search, time_random_search = measure_time(random_search.fit, X_train_scaled, y_train)

animation.stop()

print(f"Tempo per Randomized Search: {time_random_search:.2f} secondi")

print("Migliori parametri trovati con Randomized Search:")
print(random_search.best_params_)

clf_rf_randomsearch = random_search.best_estimator_

model_save_path = '/content/drive/Othercomputers/Il mio laptop/Desktop/face_detection/prova/clf_rf_rg_rb_hsv_randomizedsearch.joblib'
dump(clf_rf_randomsearch, model_save_path)
print(f"Migliore modello Random Forest addestrato e salvato in: {model_save_path}")
Codice
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.utils.class_weight import compute_class_weight
from joblib import dump
import numpy as np
from tqdm import tqdm
import time

class ChunkedDataProcessor:
    def __init__(self, chunk_size=1000):
        self.chunk_size = chunk_size
        self.scaler = StandardScaler()

    def count_classes(self, y, chunk_size=10000):
        """Conta le classi in chunks per evitare di caricare tutto in memoria"""
        unique_classes = set()
        class_counts = {}

        for i in range(0, len(y), chunk_size):
            chunk = y[i:min(i + chunk_size, len(y))]
            chunk_unique = np.unique(chunk)

            for cls in chunk_unique:
                unique_classes.add(cls)
                if cls not in class_counts:
                    class_counts[cls] = 0
                class_counts[cls] += np.sum(chunk == cls)

        return sorted(list(unique_classes)), class_counts

    def create_stratified_indices(self, y, test_size=0.3, chunk_size=10000):
        """Crea indici per split stratificato processando in chunks"""
        print("Conteggio delle classi...")
        classes, class_counts = self.count_classes(y, chunk_size)

        # Inizializza dizionari per gli indici
        train_indices = []
        test_indices = []

        print("Creazione indici stratificati...")
        for i in tqdm(range(0, len(y), chunk_size)):
            chunk_y = y[i:min(i + chunk_size, len(y))]
            chunk_indices = np.arange(i, min(i + chunk_size, len(y)))

            for cls in classes:
                cls_indices = chunk_indices[chunk_y == cls]
                n_samples = len(cls_indices)

                if n_samples > 0:
                    n_test = int(test_size * n_samples)
                    np.random.shuffle(cls_indices)

                    test_indices.extend(cls_indices[:n_test])
                    train_indices.extend(cls_indices[n_test:])

        return np.array(train_indices), np.array(test_indices)

    def process_chunk(self, X_chunk, y_chunk, is_first_chunk=False):
        """Processa un singolo chunk di dati"""
        if is_first_chunk:
            X_chunk_scaled = self.scaler.fit_transform(X_chunk)
        else:
            X_chunk_scaled = self.scaler.transform(X_chunk)
        return X_chunk_scaled, y_chunk

    def split_data(self, X, y):
        """Split dei dati in train, validation e test"""
        print("Creazione split train/val/test...")

        # Primo split per separare il training set
        train_idx, temp_idx = self.create_stratified_indices(y, test_size=0.3, chunk_size=self.chunk_size)

        # Split dei dati rimanenti in validation e test
        temp_y = y[temp_idx]
        val_idx_local, test_idx_local = self.create_stratified_indices(temp_y, test_size=0.5, chunk_size=self.chunk_size)

        # Converti gli indici locali in indici globali per validation e test
        val_idx = temp_idx[val_idx_local]
        test_idx = temp_idx[test_idx_local]

        return train_idx, val_idx, test_idx

    def train_random_forest(self, X, y, model_path):
        """Addestra il Random Forest usando chunks di dati"""
        print("Iniziando il processamento dei dati...")
        start_time = time.time()

        # Split dei dati
        train_indices, val_indices, test_indices = self.split_data(X, y)

        # Inizializza il modello
        rf_model = RandomForestClassifier(
            n_estimators=50,
            max_depth=5,
            random_state=42,
            n_jobs=-1,
            verbose=1
        )

        # Processa e addestra in chunks
        print("Processamento e addestramento in chunks...")
        X_processed = []
        y_processed = []

        for i in tqdm(range(0, len(train_indices), self.chunk_size)):
            chunk_indices = train_indices[i:min(i + self.chunk_size, len(train_indices))]
            X_chunk = X[chunk_indices]
            y_chunk = y[chunk_indices]

            # Processa il chunk
            X_chunk_scaled, y_chunk = self.process_chunk(
                X_chunk, y_chunk, is_first_chunk=(i == 0)
            )

            X_processed.append(X_chunk_scaled)
            y_processed.append(y_chunk)

        # Concatena tutti i chunks processati
        X_train_scaled = np.vstack(X_processed)
        y_train = np.concatenate(y_processed)

        # Addestra il modello
        print("Addestramento del modello...")
        rf_model.fit(X_train_scaled, y_train)

        # Salva il modello
        print("Salvando il modello...")
        dump(rf_model, model_path)

        total_time = time.time() - start_time
        print(f"Tempo totale di esecuzione: {total_time:.2f} secondi")

        return rf_model, self.scaler

# Uso del codice
CHUNK_SIZE = 1000  # Riduci se hai ancora problemi di memoria
MODEL_PATH = '/content/drive/Othercomputers/Il mio laptop/Desktop/face_detection/prova/colab_clf_rf_rg_rb_hsv_due.joblib'

processor = ChunkedDataProcessor(chunk_size=CHUNK_SIZE)
model, scaler = processor.train_random_forest(X, y, MODEL_PATH)
Codice
# Percorso della cartella delle immagini
skin_ppm_folder = 'C:/Users/gabri/Desktop/face_detection/prova/face_PPM'

# Seleziono tutti i file PPM nella cartella
sample_files = [file for file in os.listdir(skin_ppm_folder) if file.lower().endswith('.ppm')]

if sample_files:
    # Inizializzo gli istogrammi accumulati per R, G e B
    hist_R_accum = np.zeros(256)
    hist_G_accum = np.zeros(256)
    hist_B_accum = np.zeros(256)

    # Itero su tutti i file PPM e accumula gli istogrammi
    for file in sample_files:
        sample_ppm_path = os.path.join(skin_ppm_folder, file)
        try:
            image = read_ppm(sample_ppm_path)

            # Estrazione dei canali
            R = image[:, :, 0].flatten()
            G = image[:, :, 1].flatten()
            B = image[:, :, 2].flatten()

            # Calcolo gli istogrammi per R, G e B con 256 bin, normalizzati a unità
            # np.histogram() calcola l'istogramma di un array di valori
            # bins=256 indica il numero di intervalli in cui i valori del canale di colore vengono suddivisi, quante volte ciascun valore di intensità appare nell'immagine
            # density=True serve per normalizzare l'istogramma
            hist_R, _ = np.histogram(R, bins=256, range=(0, 255), density=True)
            hist_G, _ = np.histogram(G, bins=256, range=(0, 255), density=True)
            hist_B, _ = np.histogram(B, bins=256, range=(0, 255), density=True)

            hist_R_accum += hist_R
            hist_G_accum += hist_G
            hist_B_accum += hist_B
        except Exception as e:
            print(f"Errore nel processare il file {sample_ppm_path}: {e}")


    hist_R_avg = hist_R_accum / len(sample_files)
    hist_G_avg = hist_G_accum / len(sample_files)
    hist_B_avg = hist_B_accum / len(sample_files)

    # Converto le distribuzioni in percentuali
    hist_R_avg_percent = hist_R_avg * 100
    hist_G_avg_percent = hist_G_avg * 100
    hist_B_avg_percent = hist_B_avg * 100

    plt.figure(figsize=(18, 12))  # Imposto la dimensione della figura per due righe

    # Primo grafico per il canale R
    plt.subplot(2, 2, 1)
    plt.plot(hist_R_avg_percent, color='red')
    plt.fill_between(range(256), hist_R_avg_percent, color='red', alpha=0.3)
    plt.title('Distribuzione Media del Canale R')
    plt.xlabel('Valore di Intensità')
    plt.ylabel('Percentuale (%)')
    plt.grid(True)

    # Secondo grafico per il canale G
    plt.subplot(2, 2, 2)
    plt.plot(hist_G_avg_percent, color='green')
    plt.fill_between(range(256), hist_G_avg_percent, color='green', alpha=0.3)
    plt.title('Distribuzione Media del Canale G')
    plt.xlabel('Valore di Intensità')
    plt.ylabel('Percentuale (%)')
    plt.grid(True)

    # Terzo grafico per il canale B
    plt.subplot(2, 2, 3)
    plt.plot(hist_B_avg_percent, color='blue')
    plt.fill_between(range(256), hist_B_avg_percent, color='blue', alpha=0.3)
    plt.title('Distribuzione Media del Canale B')
    plt.xlabel('Valore di Intensità')
    plt.ylabel('Percentuale (%)')
    plt.grid(True)

    # Quarto grafico per i canali sovrapposti
    plt.subplot(2, 2, 4)
    plt.plot(hist_R_avg_percent, color='red', label='R', alpha=0.7)
    plt.plot(hist_G_avg_percent, color='green', label='G', alpha=0.7)
    plt.plot(hist_B_avg_percent, color='blue', label='B', alpha=0.7)
    plt.fill_between(range(256), hist_R_avg_percent, color='red', alpha=0.2)
    plt.fill_between(range(256), hist_G_avg_percent, color='green', alpha=0.2)
    plt.fill_between(range(256), hist_B_avg_percent, color='blue', alpha=0.2)
    plt.title('Distribuzione Media dei Canali R, G, B')
    plt.xlabel('Valore di Intensità')
    plt.ylabel('Percentuale (%)')
    plt.legend()
    plt.grid(True)

    plt.tight_layout()  # Ottimizza la disposizione dei grafici
    plt.show()
Codice
model_colab_clf_rf_rg_rb_hsv_path = 'C:/Users/gabri/Desktop/face_detection/prova/colab_clf_rf_rg_rb_hsv.joblib'
colab_clf_rf_rg_rb_hsv = load(model_colab_clf_rf_rg_rb_hsv_path)

print("Parametri del modello caricato:")
print(colab_clf_rf_rg_rb_hsv.get_params())
Codice
def select_image():

    try:
        root = tk.Tk()
        root.withdraw()  # Nasconde la finestra principale di Tkinter
        file_path = filedialog.askopenfilename(
            title="Seleziona un'immagine",
            filetypes=[("Image Files", "*.jpg;*.jpeg;*.ppm;*.png;*.bmp;*.tiff")]
        )
        root.destroy()  # Chiude la finestra di Tkinter dopo la selezione
        if file_path:
            # Normalizzo il percorso per evitare problemi con i separatori di slash
            file_path = os.path.normpath(file_path)
            print(f"Percorso immagine selezionato: {file_path}")
            return file_path
        else:
            print("Nessuna immagine selezionata.")
            return None
    except Exception as e:
        print(f"Errore nella selezione dell'immagine: {e}")
        return None
Codice
def load_image(image_path):

    image = plt.imread(image_path)

    # Se l'immagine è in formato intero converto a uint8
    if image.dtype != np.uint8:
        image = (image * 255).astype(np.uint8)

    # Se l'immagine ha un canale alpha viene rimosso
    if image.shape[-1] == 4:
        image = image[:, :, :3]

    return image
Codice
def convert_single_jpg_to_ppm(input_image_path, output_ppm_path):
  try:
        # Verifico se il percorso del file di input esiste e sia un file
        if not os.path.isfile(input_image_path):
            print(f"Errore: Il percorso specificato non è un file valido: {input_image_path}")
            return

        img = plt.imread(input_image_path)

        plt.imsave(output_ppm_path, img)
        print(f"Convertita: {input_image_path} -> {output_ppm_path}")

    except Exception as e:
        print(f"Errore nella conversione dell'immagine a PPM: {e}")
Codice
def cluster_face_pixels(mask, n_clusters=5):
    # mask (ndarray) maschera binaria con 1 per volti rilevati e 0 altrimenti
    # n_clusters (int) numero di cluster (volti) da identificare

    # Returns:
    # list: Lista delle coordinate dei bounding box dei volti rilevati

    try:
        # Estraggo le coordinate dei pixel classificati come 'volti'
        # np.column_stack trasforma le coordinate in un array di coordinate
        # face_pixels[:, 1]  inverte l'ordine delle colonne e righe
        # questo perchè in molti framework di elaborazione delle immagini le coordinate degli pixel sono espresse
        # nel formato - riga (y), colonna(x) -
        # gli algoritmi di visione artificiale richiedono le coordinate in formato - colonna(x), riga(y) -
        face_pixels = np.column_stack(np.where(mask == 1))
        face_pixels = np.column_stack((face_pixels[:, 1], face_pixels[:, 0]))

        if len(face_pixels) == 0:
            print("Nessun pixel classificato come volto.")
            return []

        # Standardizzazione dei dati
        scaler = StandardScaler()
        face_pixels_scaled = scaler.fit_transform(face_pixels)

        # Applico K-Means
        kmeans = KMeans(n_clusters=n_clusters, random_state=42)
        labels = kmeans.fit_predict(face_pixels_scaled)

        bounding_boxes = []

        for cluster_label in range(n_clusters):
            cluster_points = face_pixels[labels == cluster_label]

            if len(cluster_points) < 5:
                continue

            # Analisi della forma del cluster
            try:
                hull = ConvexHull(cluster_points)
                hull_points = cluster_points[hull.vertices]

                # Calcolo l'area del Convex Hull
                # Convex Hull è il più piccolo poligono convesso che contiene tutti i punti
                hull_area = hull.volume

                # Calcolo l'area rettangolare
                min_x, min_y = np.min(cluster_points, axis=0)
                max_x, max_y = np.max(cluster_points, axis=0)
                rect_area = (max_x - min_x) * (max_y - min_y)

                # Calcolo l'aspect ratio
                aspect_ratio = (max_x - min_x) / (max_y - min_y) if (max_y - min_y) != 0 else 0

                # Calcolo la copertura dell'area
                coverage = hull_area / rect_area if rect_area != 0 else 0

                # Criteri per identificare un ovale verticalmente orientato
                if aspect_ratio > 1.1 and coverage > 0.5:
                    bounding_boxes.append((min_x, min_y, max_x, max_y))
                    print(f"Cluster {cluster_label}: bounding box (x_min={min_x}, y_min={min_y}, x_max={max_x}, y_max={max_y}), aspect_ratio={aspect_ratio:.2f}, coverage={coverage:.2f}")
            except Exception as e:
                print(f"Errore nell'analisi del cluster {cluster_label}: {e}")
                continue

        if not bounding_boxes:
            print("Nessun volto ovale rilevato dopo clustering.")

        return bounding_boxes
    except Exception as e:
        print(f"Errore nel clustering: {e}")
        return []
Codice
def classify_pixels(image, colab_clf_rf_rg_rb_hsv, L_i_rg=0.9, L_s_rg=1.4, b_onR=1.2):
        # image (np.ndarray): Immagine in formato NumPy array
        # colab_clf_rf_rg_rb_hsv (RandomForestClassifier), modello addestrato
        # L_i_rg (float): Limite inferiore per il rapporto R/G
        # L_s_rg (float): Limite superiore per il rapporto R/G
        # b_onR (float): Tolleranza per il rapporto B_value = R / 3
        # np.ndarray: Maschera binaria con 1 per pelle e 0 per non-pelle.

    try:
        # Estraggo i canali R, G e B
        R = image[:, :, 0].astype(float)
        G = image[:, :, 1].astype(float)
        B = image[:, :, 2].astype(float)

        # Evito divisioni per zero
        G[G == 0] = 1.0
        B[B == 0] = 1.0

        # Calcolo i rapporti R/G e R/3
        R_G_ratio = R / G
        B_value = R / 3

        # Calcolo i limiti basati su B_value con la tolleranza b_onR
        L_i_b = B_value - b_onR
        L_s_b = B_value + b_onR

        # Applico i limiti sul rapporto R/G e R/B
        mask_rg_limits = (R_G_ratio > L_i_rg) & (R_G_ratio < L_s_rg)
        mask_rb_limits = (B_value > L_i_b) & (B_value < L_s_b)
        valid_pixels = mask_rg_limits & mask_rb_limits

        # Calcolo i valori H, S e V
        H, S, V = rgb_to_hsv_manual(R, G, B)
        # np.stack combina tutte le feature calcolate in un singolo array lungo l'ultimo asse axis=-1
        #  i singoli array (rapporti e componenti di colore) formano un array multidimensionale
        features = np.stack((R_G_ratio, B_value, H, S, V), axis=-1).reshape(-1, 5)

        # Seleziono solo i pixel validi per la classificazione
        features_filtered = features[valid_pixels.flatten()]

        # Verifico se ci sono pixel validi da classificare
        if features_filtered.shape[0] == 0:
            print("Nessun pixel valido da classificare.")
            return np.zeros(R_G_ratio.shape, dtype=np.uint8)

        # Classifico i pixel validi
        predictions = colab_clf_rf_rg_rb_hsv.predict(features_filtered)

        # Creo una maschera con 0 e 1 per rappresentare pelle e non-pelle
        mask = np.zeros(R_G_ratio.shape, dtype=np.uint8)
        mask[valid_pixels] = predictions
        num_face_pixels = np.sum(mask == 1)
        print(f"Numero di pixel classificati come volti: {num_face_pixels}")

        return mask
    except Exception as e:
        print(f"Errore nella classificazione dei pixel: {e}")
        return None
Codice
def sliding_window(image, step_size, window_size):

        # image (ndarray) l'immagine su cui applicare lo sliding_window
        # step_size (int) dimensione del passo per la finestra
        # window_size (tuple) dimensioni della finestra (larghezza, altezza)

        # Returns:
        #  list: un elenco di finestre mobili come tuple (x, y, window_size, image_window)

    windows = []
    for y in range(0, image.shape[0] - window_size[1] + 1, step_size):
        for x in range(0, image.shape[1] - window_size[0] + 1, step_size):
            image_window = image[y:y + window_size[1], x:x + window_size[0]]
            windows.append((x, y, window_size, image_window))
    return windows
Codice
def draw_bounding_boxes(image, bounding_boxes, save_path=None):
    try:
        fig, ax = plt.subplots(figsize=(10, 10))
        ax.imshow(image)

        for box in bounding_boxes:
            x_min, y_min, x_max, y_max = box
            width = x_max - x_min
            height = y_max - y_min
            # Crea un rettangolo verde
            rect = Rectangle((x_min, y_min), width, height, linewidth=2, edgecolor='green', facecolor='none')
            ax.add_patch(rect)

        plt.axis('off')
        plt.title('Immagine con Volti Rilevati')
        if save_path:
            # Normalizzo il percorso per evitare problemi con gli slash
            save_path = os.path.normpath(save_path)
            plt.savefig(save_path, bbox_inches='tight', pad_inches=0)
            print(f"Immagine con bounding box salvata in: {save_path}")
        plt.show()
    except Exception as e:
        print(f"Errore nel disegnare i bounding box: {e}")
Codice
def process_image(image_path=None, output_folder='C:/temp',
                 model_folder='C:/Users/gabri/Desktop/face_detection/prova',
                 scale_percent=15, n_clusters=3):

        #image_path (str, optional) percorso dell'immagine caricata
        #output_folder (str) cartella dove salvare i risultati
        #model_folder (str) cartella dove si trova il modello addestrato
        #scale_percent (float): Percentuale di ridimensionamento.
        #n_clusters (int) numero di cluster (volti) da identificare

        #Returns:
            #list: Lista delle coordinate dei bounding box

    if image_path is None:
        image_path = select_image()

    if not image_path:
        print("Nessuna immagine selezionata.")
        return []

    # Definisco i percorsi per la conversione
    base_name = os.path.splitext(os.path.basename(image_path))[0]
    ppm_image_path = os.path.join(output_folder, f"{base_name}.ppm")
    resized_image_path = os.path.join(output_folder, f"{base_name}_resized.ppm")
    result_image_path = os.path.join(output_folder, f"{base_name}_result.png")

    if not os.path.exists(output_folder):
        try:
            os.makedirs(output_folder)
            print(f"Cartella di output creata: {output_folder}")
        except Exception as e:
            print(f"Errore nella creazione della cartella di output: {e}")
            return []

    # Converto JPG a PPM
    if not image_path.lower().endswith('.ppm'):
        try:
            convert_single_jpg_to_ppm(image_path, ppm_image_path)
            print(f"Convertita: {image_path} -> {ppm_image_path}")
        except Exception as e:
            print(f"Errore nella conversione dell'immagine a PPM: {e}")
            return []
    else:
        ppm_image_path = image_path

    # Leggo l'immagine PPM
    try:
        image = read_ppm(ppm_image_path)
    except Exception as e:
        print(f"Errore nella lettura dell'immagine PPM: {e}")
        return []

    if image is None:
        print("Impossibile leggere l'immagine PPM.")
        return []

    # Effettuo binning e ridimensionamento per l'immagine caricata
    resized_image = resize_image_binning(image, scale_percent)
    if resized_image is None:
        print("Errore nel ridimensionamento dell'immagine.")
        return []

    # Salvo l'immagine ridimensionata come PPM
    try:
        save_ppm(resized_image_path, resized_image)
        print(f"Immagine ridimensionata salvata in: {resized_image_path}")
    except Exception as e:
        print(f"Errore nel salvataggio dell'immagine ridimensionata: {e}")
        return []

    model_filename = 'colab_clf_rf_rg_rb_hsv.joblib'
    model_path = os.path.join(model_folder, model_filename)

    if not os.path.exists(model_path):
        print(f"Il modello '{model_path}' non esiste.")
        return []

    try:
        colab_clf_rf_rg_rb_hsv = load(model_path)
        print(f"Modello '{model_path}' caricato con successo.")
    except Exception as e:
        print(f"Errore nel caricamento del modello: {e}")
        return []

    # Classifico i pixel usando le 4 features
    mask = classify_pixels(resized_image, colab_clf_rf_rg_rb_hsv)
    if mask is None:
        print("Impossibile classificare i pixel.")
        return []

    # Visualizzo la maschera binaria
    plt.figure(figsize=(10, 10))
    plt.imshow(mask, cmap='gray')
    plt.title('Maschera Binaria dei Pixel Classificati')
    plt.axis('off')
    plt.show()

    # Applico il clustering con K-Means per identificare ovali verticali
    refined_bounding_boxes = cluster_face_pixels(mask, n_clusters=n_clusters)

    if not refined_bounding_boxes:
        print("Nessun volto ovale rilevato dopo clustering.")
        plt.figure(figsize=(10, 10))
        plt.imshow(resized_image)
        plt.axis('off')
        plt.title('Immagine Senza Volti Rilevati')
        plt.show()
        return []

    # Stampo le coordinate dei bounding box
    print("Volti ovali rilevati:")
    for idx, box in enumerate(refined_bounding_boxes, start=1):
        x_min, y_min, x_max, y_max = box
        print(f"Volto {idx}: (x_min={x_min}, y_min={y_min}, x_max={x_max}, y_max={y_max})")

    # Disegno i bounding box sull'immagine e salvalo
    try:
        draw_bounding_boxes(resized_image, refined_bounding_boxes, save_path=result_image_path)
        print(f"Immagine con bounding box salvata in: {result_image_path}")
    except Exception as e:
        print(f"Errore nel disegnare o salvare i bounding box: {e}")
        return []

    return refined_bounding_boxes
Codice
def non_maximum_suppression(detections, threshold=0.5):
    # detections è un elenco di riquadri di delimitazione, ciascuno associato a un punteggio
    # threshold è la soglia iou per il filtraggio delle caselle sovrapposte
    if len(detections) == 0:
        return []

    # Ordino le finestre basate sul punteggio
    detections = sorted(detections, key=lambda x: x[2], reverse=True)
    final_detections = []

    while detections:
        chosen_box = detections.pop(0)#  seleziono la casella con il punteggio più alto e la rimuovo
        final_detections.append(chosen_box)

        # Applico la funzione IoU a ogni rilevamento rimanente filtrando le caselle
        detections = [box for box in detections if iou(chosen_box, box) < threshold]

    return final_detections

def iou(box1, box2):
    # calcolo l'intersezione tra due bounding box
    x1, y1, w1, h1 = box1[:4]
    x2, y2, w2, h2 = box2[:4]

    #Trovo il rettangolo di intersezione calcolando il massimo delle coordinate in alto a sinistra ( xi1, yi1)
    # e il minimo delle coordinate in basso a destra ( xi2, yi2).
    xi1 = max(x1, x2)
    yi1 = max(y1, y2)
    xi2 = min(x1 + w1, x2 + w2)
    yi2 = min(y1 + h1, y2 + h2)

    # area dell'intersezione
    inter_area = max(0, xi2 - xi1) * max(0, yi2 - yi1)
    box1_area = w1 * h1
    box2_area = w2 * h2
    union_area = box1_area + box2_area - inter_area

    return inter_area / union_area
Codice
def process_image_sliding_window(image_path=None, output_folder='C:/temp',
                 model_folder='C:/Users/gabri/Desktop/face_detection/prova',
                 scale_percent=15, n_clusters=1, iou_threshold=0.5,
                 step_size=128, window_size=(128, 128)):

    if image_path is None:
        image_path = select_image()

    if not image_path:
        print("Nessuna immagine selezionata.")
        return []

    # Percorsi per la conversione
    base_name = os.path.splitext(os.path.basename(image_path))[0]
    ppm_image_path = os.path.join(output_folder, f"{base_name}.ppm")
    resized_image_path = os.path.join(output_folder, f"{base_name}_resized.ppm")
    result_image_path = os.path.join(output_folder, f"{base_name}_result.png")

    if not os.path.exists(output_folder):
        try:
            os.makedirs(output_folder)
            print(f"Cartella di output creata: {output_folder}")
        except Exception as e:
            print(f"Errore nella creazione della cartella di output: {e}")
            return []

        # Converto JPG a PPM se necessario
    if not image_path.lower().endswith('.ppm'):
        try:
            convert_single_jpg_to_ppm(image_path, ppm_image_path)
            print(f"Convertita: {image_path} -> {ppm_image_path}")
        except Exception as e:
            print(f"Errore nella conversione dell'immagine a PPM: {e}")
            return []
    else:
        ppm_image_path = image_path

    try:
        image = read_ppm(ppm_image_path)
    except Exception as e:
        print(f"Errore nella lettura dell'immagine PPM: {e}")
        return []

    if image is None:
        print("Impossibile leggere l'immagine PPM.")
        return []

    # Effettuo binning e ridimensionamento per l'immagine corrente
    resized_image = resize_image_binning(image, scale_percent)
    if resized_image is None:
        print("Errore nel ridimensionamento dell'immagine.")
        return []

    try:
        save_ppm(resized_image_path, resized_image)
        print(f"Immagine ridimensionata salvata in: {resized_image_path}")
    except Exception as e:
        print(f"Errore nel salvataggio dell'immagine ridimensionata: {e}")
        return []

    model_filename = 'colab_clf_rf_rg_rb_hsv.joblib'
    model_path = os.path.join(model_folder, model_filename)

    if not os.path.exists(model_path):
        print(f"Il modello '{model_path}' non esiste.")
        return []

    try:
        colab_clf_rf_rg_rb_hsv = load(model_path)
        print(f"Modello '{model_path}' caricato con successo.")
    except Exception as e:
        print(f"Errore nel caricamento del modello: {e}")
        return []

    # Applico sliding window per ottenere le regioni di interesse
    sliding_windows = sliding_window(resized_image, step_size, window_size)

    # Lista per memorizzare i bounding box
    all_bounding_boxes = []

    for window in sliding_windows:
        # window è una tupla: (x, y, window_size, image_window)
        x, y, window_size, image_window = window

        # Classifico i pixel per ogni finestra
        mask = classify_pixels(image_window, colab_clf_rf_rg_rb_hsv)
        bounding_boxes = cluster_face_pixels(mask, n_clusters=n_clusters)
        all_bounding_boxes.extend(bounding_boxes)

    # Applico Non-Maximum Suppression
    final_bounding_boxes = non_maximum_suppression(all_bounding_boxes, iou_threshold)

    if not final_bounding_boxes:
        print("Nessun volto ovale rilevato dopo clustering e non-maximum suppression.")
        plt.figure(figsize=(10, 10))
        plt.imshow(resized_image)
        plt.axis('off')
        plt.title('Immagine Senza Volti Rilevati')
        plt.show()
        return []

    # Stampo le coordinate dei bounding box
    print("Volti ovali rilevati:")
    for idx, box in enumerate(final_bounding_boxes, start=1):
        x_min, y_min, x_max, y_max = box
        print(f"Volto {idx}: (x_min={x_min}, y_min={y_min}, x_max={x_max}, y_max={y_max})")

    try:
        draw_bounding_boxes(resized_image, final_bounding_boxes, save_path=result_image_path)
        print(f"Immagine con bounding box salvata in: {result_image_path}")
    except Exception as e:
        print(f"Errore nel disegnare o salvare i bounding box: {e}")
        return []

    return final_bounding_boxes
Codice
def save_ppm(ppm_path, img):
    try:
        height, width, channels = img.shape
        if channels != 3:
            raise ValueError("L'immagine deve avere 3 canali (RGB).")
        with open(ppm_path, 'wb') as f:
            # Scrivi l'header PPM
            header = f"P6\n{width} {height}\n255\n"
            f.write(header.encode())
            # Scrivi i dati RGB
            f.write(img.tobytes())
        print(f"Immagine PPM salvata in: {ppm_path}")
    except Exception as e:
        print(f"Errore nel salvataggio dell'immagine PPM: {e}")
Codice
process_image()
Codice
process_image()
Codice
process_image()
Codice
process_image()
Codice
process_image()
Codice
process_image()
Codice
process_image()
Codice
process_image_sliding_window()
Codice
process_image_sliding_window()
Codice
process_image_sliding_window()
Codice
process_image_sliding_window()
Codice
process_image_sliding_window()