Spaces:
Sleeping
Sleeping
| import cv2 | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| from .preprocessing import ImagePreprocessor | |
| class ImageEnhancer: | |
| """ | |
| Classe per l'elaborazione avanzata delle immagini di firme e documenti. | |
| Implementa funzionalità per migliorare la qualità delle immagini, | |
| evidenziare dettagli e applicare filtri speciali per l'analisi forense. | |
| """ | |
| def __init__(self): | |
| """Inizializza l'enhancer di immagini.""" | |
| self.preprocessor = ImagePreprocessor() | |
| def enhance_contrast(self, image, method='clahe'): | |
| """ | |
| Migliora il contrasto di un'immagine. | |
| Args: | |
| image (numpy.ndarray): Immagine di input | |
| method (str): Metodo di miglioramento ('clahe', 'histogram_eq', 'adaptive') | |
| Returns: | |
| numpy.ndarray: Immagine con contrasto migliorato | |
| """ | |
| # Converti in scala di grigi se necessario | |
| if len(image.shape) > 2: | |
| gray = self.preprocessor.convert_to_grayscale(image) | |
| else: | |
| gray = image.copy() | |
| if method == 'clahe': | |
| # Contrast Limited Adaptive Histogram Equalization | |
| clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) | |
| enhanced = clahe.apply(gray) | |
| elif method == 'histogram_eq': | |
| # Equalizzazione dell'istogramma globale | |
| enhanced = cv2.equalizeHist(gray) | |
| elif method == 'adaptive': | |
| # Miglioramento adattivo del contrasto | |
| enhanced = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, | |
| cv2.THRESH_BINARY, 11, 2) | |
| else: | |
| raise ValueError(f"Metodo di miglioramento del contrasto non supportato: {method}") | |
| return enhanced | |
| def sharpen_image(self, image, kernel_size=3, strength=1.0): | |
| """ | |
| Applica un filtro di sharpening all'immagine. | |
| Args: | |
| image (numpy.ndarray): Immagine di input | |
| kernel_size (int): Dimensione del kernel | |
| strength (float): Intensità dell'effetto di sharpening | |
| Returns: | |
| numpy.ndarray: Immagine affilata | |
| """ | |
| # Converti in scala di grigi se necessario | |
| if len(image.shape) > 2: | |
| gray = self.preprocessor.convert_to_grayscale(image) | |
| else: | |
| gray = image.copy() | |
| # Applica un filtro gaussiano per ridurre il rumore | |
| blurred = cv2.GaussianBlur(gray, (kernel_size, kernel_size), 0) | |
| # Calcola la maschera di sharpening (immagine originale - immagine sfocata) | |
| mask = cv2.subtract(gray, blurred) | |
| # Applica la maschera all'immagine originale | |
| sharpened = cv2.addWeighted(gray, 1.0, mask, strength, 0) | |
| return sharpened | |
| def apply_edge_detection(self, image, method='canny'): | |
| """ | |
| Applica un rilevatore di bordi all'immagine. | |
| Args: | |
| image (numpy.ndarray): Immagine di input | |
| method (str): Metodo di rilevamento bordi ('canny', 'sobel', 'laplacian') | |
| Returns: | |
| numpy.ndarray: Immagine con bordi rilevati | |
| """ | |
| # Converti in scala di grigi se necessario | |
| if len(image.shape) > 2: | |
| gray = self.preprocessor.convert_to_grayscale(image) | |
| else: | |
| gray = image.copy() | |
| # Applica un filtro gaussiano per ridurre il rumore | |
| blurred = cv2.GaussianBlur(gray, (5, 5), 0) | |
| if method == 'canny': | |
| # Rilevatore di bordi Canny | |
| edges = cv2.Canny(blurred, 50, 150) | |
| elif method == 'sobel': | |
| # Rilevatore di bordi Sobel | |
| sobelx = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=3) | |
| sobely = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=3) | |
| # Calcola il gradiente | |
| magnitude = cv2.magnitude(sobelx, sobely) | |
| # Normalizza e converti in uint8 | |
| edges = cv2.normalize(magnitude, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U) | |
| elif method == 'laplacian': | |
| # Rilevatore di bordi Laplaciano | |
| laplacian = cv2.Laplacian(blurred, cv2.CV_64F) | |
| # Normalizza e converti in uint8 | |
| edges = cv2.normalize(laplacian, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U) | |
| else: | |
| raise ValueError(f"Metodo di rilevamento bordi non supportato: {method}") | |
| return edges | |
| def highlight_pressure_points(self, image, threshold=50): | |
| """ | |
| Evidenzia i punti di pressione in una firma. | |
| Args: | |
| image (numpy.ndarray): Immagine di input | |
| threshold (int): Soglia per considerare un punto come punto di pressione | |
| Returns: | |
| numpy.ndarray: Immagine con punti di pressione evidenziati | |
| """ | |
| # Converti in scala di grigi se necessario | |
| if len(image.shape) > 2: | |
| gray = self.preprocessor.convert_to_grayscale(image) | |
| else: | |
| gray = image.copy() | |
| # Inverti l'immagine (testo bianco su sfondo nero) | |
| gray_inv = cv2.bitwise_not(gray) | |
| # Applica una soglia per isolare il testo | |
| _, binary = cv2.threshold(gray_inv, threshold, 255, cv2.THRESH_BINARY) | |
| # Crea un'immagine a colori per la visualizzazione | |
| result = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR) | |
| # Applica una mappa di colori per evidenziare i punti di pressione | |
| # Più scuro è il pixel, maggiore è la pressione | |
| for i in range(gray.shape[0]): | |
| for j in range(gray.shape[1]): | |
| if binary[i, j] > 0: | |
| # Calcola l'intensità normalizzata (0-1) | |
| intensity = gray_inv[i, j] / 255.0 | |
| # Applica una mappa di colori (blu -> verde -> rosso) | |
| if intensity < 0.33: | |
| # Blu (bassa pressione) | |
| result[i, j] = [255, 0, 0] | |
| elif intensity < 0.66: | |
| # Verde (media pressione) | |
| result[i, j] = [0, 255, 0] | |
| else: | |
| # Rosso (alta pressione) | |
| result[i, j] = [0, 0, 255] | |
| return result | |
| def extract_profile(self, image, direction='horizontal'): | |
| """ | |
| Estrae il profilo di un'immagine in una direzione specifica. | |
| Args: | |
| image (numpy.ndarray): Immagine di input | |
| direction (str): Direzione del profilo ('horizontal', 'vertical') | |
| Returns: | |
| numpy.ndarray: Profilo estratto | |
| """ | |
| # Converti in scala di grigi se necessario | |
| if len(image.shape) > 2: | |
| gray = self.preprocessor.convert_to_grayscale(image) | |
| else: | |
| gray = image.copy() | |
| # Inverti l'immagine (testo bianco su sfondo nero) | |
| gray_inv = cv2.bitwise_not(gray) | |
| if direction == 'horizontal': | |
| # Somma i pixel per ogni riga | |
| profile = np.sum(gray_inv, axis=1) | |
| elif direction == 'vertical': | |
| # Somma i pixel per ogni colonna | |
| profile = np.sum(gray_inv, axis=0) | |
| else: | |
| raise ValueError(f"Direzione del profilo non supportata: {direction}") | |
| # Normalizza il profilo | |
| if np.max(profile) > 0: | |
| profile = profile / np.max(profile) | |
| return profile | |
| def visualize_profile(self, image, save_path=None): | |
| """ | |
| Visualizza i profili orizzontale e verticale di un'immagine. | |
| Args: | |
| image (numpy.ndarray): Immagine di input | |
| save_path (str, optional): Percorso dove salvare l'immagine | |
| Returns: | |
| matplotlib.figure.Figure: Figura con la visualizzazione | |
| """ | |
| # Estrai i profili | |
| h_profile = self.extract_profile(image, direction='horizontal') | |
| v_profile = self.extract_profile(image, direction='vertical') | |
| # Crea una figura con più sottografici | |
| fig, axs = plt.subplots(1, 3, figsize=(15, 5)) | |
| # Immagine originale | |
| if len(image.shape) > 2: | |
| axs[0].imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) | |
| else: | |
| axs[0].imshow(image, cmap='gray') | |
| axs[0].set_title('Immagine Originale') | |
| axs[0].axis('off') | |
| # Profilo orizzontale | |
| axs[1].plot(h_profile, range(len(h_profile)), 'b-') | |
| axs[1].invert_yaxis() # Inverti l'asse y per corrispondere all'immagine | |
| axs[1].set_title('Profilo Orizzontale') | |
| axs[1].set_xlabel('Intensità Normalizzata') | |
| axs[1].set_ylabel('Riga') | |
| # Profilo verticale | |
| axs[2].plot(v_profile, 'r-') | |
| axs[2].set_title('Profilo Verticale') | |
| axs[2].set_xlabel('Colonna') | |
| axs[2].set_ylabel('Intensità Normalizzata') | |
| plt.tight_layout() | |
| # Salva l'immagine se richiesto | |
| if save_path: | |
| plt.savefig(save_path, dpi=300, bbox_inches='tight') | |
| return fig | |
| def apply_color_filter(self, image, color_range): | |
| """ | |
| Applica un filtro di colore all'immagine. | |
| Args: | |
| image (numpy.ndarray): Immagine di input (BGR) | |
| color_range (dict): Intervallo di colori in formato HSV | |
| {'lower': [h_min, s_min, v_min], 'upper': [h_max, s_max, v_max]} | |
| Returns: | |
| numpy.ndarray: Immagine filtrata | |
| """ | |
| # Verifica che l'immagine sia a colori | |
| if len(image.shape) < 3: | |
| # Converti in BGR se è in scala di grigi | |
| image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) | |
| # Converti in HSV | |
| hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) | |
| # Crea una maschera per il colore specificato | |
| lower = np.array(color_range['lower']) | |
| upper = np.array(color_range['upper']) | |
| mask = cv2.inRange(hsv, lower, upper) | |
| # Applica la maschera all'immagine originale | |
| filtered = cv2.bitwise_and(image, image, mask=mask) | |
| return filtered | |
| def extract_stamp(self, image): | |
| """ | |
| Estrae i timbri da un'immagine. | |
| Args: | |
| image (numpy.ndarray): Immagine di input (BGR) | |
| Returns: | |
| tuple: (immagine_originale_senza_timbri, timbri_estratti) | |
| """ | |
| # Verifica che l'immagine sia a colori | |
| if len(image.shape) < 3: | |
| # Converti in BGR se è in scala di grigi | |
| image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) | |
| # Definisci intervalli di colore per i timbri comuni | |
| color_ranges = [ | |
| # Blu (timbri comuni) | |
| {'lower': [100, 50, 50], 'upper': [140, 255, 255]}, | |
| # Rosso (timbri comuni) | |
| {'lower': [0, 50, 50], 'upper': [10, 255, 255]}, | |
| # Rosso (parte alta dello spettro HSV) | |
| {'lower': [170, 50, 50], 'upper': [180, 255, 255]}, | |
| # Viola (alcuni timbri ufficiali) | |
| {'lower': [140, 50, 50], 'upper': [170, 255, 255]} | |
| ] | |
| # Converti in HSV | |
| hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) | |
| # Crea una maschera combinata per tutti i colori | |
| combined_mask = np.zeros((image.shape[0], image.shape[1]), dtype=np.uint8) | |
| for color_range in color_ranges: | |
| lower = np.array(color_range['lower']) | |
| upper = np.array(color_range['upper']) | |
| mask = cv2.inRange(hsv, lower, upper) | |
| combined_mask = cv2.bitwise_or(combined_mask, mask) | |
| # Applica operazioni morfologiche per migliorare la maschera | |
| kernel = np.ones((5, 5), np.uint8) | |
| combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_OPEN, kernel) | |
| combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_CLOSE, kernel) | |
| # Estrai i timbri | |
| stamps = cv2.bitwise_and(image, image, mask=combined_mask) | |
| # Crea un'immagine senza timbri | |
| inv_mask = cv2.bitwise_not(combined_mask) | |
| image_without_stamps = cv2.bitwise_and(image, image, mask=inv_mask) | |
| return image_without_stamps, stamps | |
| def convert_to_grayscale_enhanced(self, image, method='weighted'): | |
| """ | |
| Converte un'immagine a colori in scala di grigi con metodi avanzati. | |
| Args: | |
| image (numpy.ndarray): Immagine di input (BGR) | |
| method (str): Metodo di conversione ('weighted', 'luminosity', 'desaturation', 'decomposition') | |
| Returns: | |
| numpy.ndarray: Immagine in scala di grigi | |
| """ | |
| # Verifica che l'immagine sia a colori | |
| if len(image.shape) < 3: | |
| return image.copy() | |
| if method == 'weighted': | |
| # Metodo standard (ponderato) | |
| gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) | |
| elif method == 'luminosity': | |
| # Metodo della luminosità (pesi personalizzati) | |
| b, g, r = cv2.split(image) | |
| gray = np.uint8(0.07 * b + 0.72 * g + 0.21 * r) | |
| elif method == 'desaturation': | |
| # Metodo della desaturazione (media di min e max) | |
| b, g, r = cv2.split(image) | |
| min_val = np.minimum(np.minimum(r, g), b) | |
| max_val = np.maximum(np.maximum(r, g), b) | |
| gray = np.uint8((min_val + max_val) / 2) | |
| elif method == 'decomposition': | |
| # Metodo della decomposizione (massimo dei canali) | |
| b, g, r = cv2.split(image) | |
| gray = np.maximum(np.maximum(r, g), b) | |
| else: | |
| raise ValueError(f"Metodo di conversione in scala di grigi non supportato: {method}") | |
| return gray | |
| def apply_emboss_effect(self, image, direction='top-left'): | |
| """ | |
| Applica un effetto di rilievo all'immagine. | |
| Args: | |
| image (numpy.ndarray): Immagine di input | |
| direction (str): Direzione della luce ('top-left', 'top-right', 'bottom-left', 'bottom-right') | |
| Returns: | |
| numpy.ndarray: Immagine con effetto di rilievo | |
| """ | |
| # Converti in scala di grigi se necessario | |
| if len(image.shape) > 2: | |
| gray = self.preprocessor.convert_to_grayscale(image) | |
| else: | |
| gray = image.copy() | |
| # Definisci il kernel in base alla direzione | |
| if direction == 'top-left': | |
| kernel = np.array([[-1, -1, 0], | |
| [-1, 0, 1], | |
| [0, 1, 1]]) | |
| elif direction == 'top-right': | |
| kernel = np.array([[0, -1, -1], | |
| [1, 0, -1], | |
| [1, 1, 0]]) | |
| elif direction == 'bottom-left': | |
| kernel = np.array([[0, 1, 1], | |
| [-1, 0, 1], | |
| [-1, -1, 0]]) | |
| elif direction == 'bottom-right': | |
| kernel = np.array([[1, 1, 0], | |
| [1, 0, -1], | |
| [0, -1, -1]]) | |
| else: | |
| raise ValueError(f"Direzione non supportata: {direction}") | |
| # Applica il filtro | |
| embossed = cv2.filter2D(gray, -1, kernel) | |
| # Aggiungi 128 per spostare i valori nel range medio | |
| embossed = cv2.add(embossed, 128) | |
| return embossed | |
| def create_signature_heatmap(self, image, kernel_size=15): | |
| """ | |
| Crea una mappa di calore della firma per evidenziare le aree di maggiore intensità. | |
| Args: | |
| image (numpy.ndarray): Immagine di input | |
| kernel_size (int): Dimensione del kernel per il filtro gaussiano | |
| Returns: | |
| numpy.ndarray: Mappa di calore della firma | |
| """ | |
| # Converti in scala di grigi se necessario | |
| if len(image.shape) > 2: | |
| gray = self.preprocessor.convert_to_grayscale(image) | |
| else: | |
| gray = image.copy() | |
| # Inverti l'immagine (testo bianco su sfondo nero) | |
| gray_inv = cv2.bitwise_not(gray) | |
| # Applica un filtro gaussiano per creare l'effetto di calore | |
| heatmap = cv2.GaussianBlur(gray_inv, (kernel_size, kernel_size), 0) | |
| # Normalizza la mappa di calore | |
| heatmap = cv2.normalize(heatmap, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U) | |
| # Applica una mappa di colori | |
| heatmap_color = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET) | |
| # Crea una maschera per isolare la firma | |
| _, mask = cv2.threshold(gray_inv, 10, 255, cv2.THRESH_BINARY) | |
| # Dilata la maschera per includere le aree circostanti | |
| kernel = np.ones((5, 5), np.uint8) | |
| mask_dilated = cv2.dilate(mask, kernel, iterations=2) | |
| # Applica la maschera alla mappa di calore | |
| result = cv2.bitwise_and(heatmap_color, heatmap_color, mask=mask_dilated) | |
| # Crea un'immagine di sfondo bianco | |
| background = np.ones_like(image) * 255 | |
| if len(background.shape) < 3: | |
| background = cv2.cvtColor(background, cv2.COLOR_GRAY2BGR) | |
| # Combina lo sfondo con la mappa di calore | |
| mask_dilated_3ch = cv2.cvtColor(mask_dilated, cv2.COLOR_GRAY2BGR) / 255.0 | |
| result = background * (1 - mask_dilated_3ch) + result * mask_dilated_3ch | |
| return result.astype(np.uint8) | |
| def enhance_signature(self, image): | |
| """ | |
| Applica una serie di miglioramenti a un'immagine di firma. | |
| Args: | |
| image (numpy.ndarray): Immagine di input | |
| Returns: | |
| dict: Dizionario con diverse versioni migliorate della firma | |
| """ | |
| # Carica l'immagine se è un percorso file | |
| if isinstance(image, str): | |
| image = self.preprocessor.load_image(image) | |
| # Converti in scala di grigi | |
| gray = self.preprocessor.convert_to_grayscale(image) | |
| # Migliora il contrasto | |
| contrast_enhanced = self.enhance_contrast(gray, method='clahe') | |
| # Applica sharpening | |
| sharpened = self.sharpen_image(gray, kernel_size=3, strength=1.5) | |
| # Rileva i bordi | |
| edges = self.apply_edge_detection(gray, method='canny') | |
| # Evidenzia i punti di pressione | |
| pressure_points = self.highlight_pressure_points(gray) | |
| # Applica effetto di rilievo | |
| embossed = self.apply_emboss_effect(gray) | |
| # Crea una mappa di calore | |
| heatmap = self.create_signature_heatmap(gray) | |
| return { | |
| 'original': image, | |
| 'grayscale': gray, | |
| 'contrast_enhanced': contrast_enhanced, | |
| 'sharpened': sharpened, | |
| 'edges': edges, | |
| 'pressure_points': pressure_points, | |
| 'embossed': embossed, | |
| 'heatmap': heatmap | |
| } | |