File size: 9,812 Bytes
c7ccdd9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
import cv2
import numpy as np
import os
from PIL import Image
import fitz  # PyMuPDF

class ImagePreprocessor:
    """
    Classe per l'acquisizione e pre-elaborazione delle immagini di firme e documenti.
    Implementa funzionalità di base come la conversione in scala di grigi,
    normalizzazione, scontorno dei timbri, ecc.
    """
    
    def __init__(self):
        """Inizializza il preprocessore di immagini."""
        pass
    
    def load_image(self, image_path):
        """
        Carica un'immagine da un percorso file.
        
        Args:
            image_path (str): Percorso dell'immagine da caricare
            
        Returns:
            numpy.ndarray: Immagine caricata in formato BGR
        """
        if not os.path.exists(image_path):
            raise FileNotFoundError(f"Il file {image_path} non esiste")
        
        # Controlla l'estensione del file
        _, ext = os.path.splitext(image_path)
        ext = ext.lower()
        
        if ext == '.pdf':
            return self.extract_image_from_pdf(image_path)
        else:
            # Carica l'immagine usando OpenCV
            image = cv2.imread(image_path)
            if image is None:
                raise ValueError(f"Impossibile caricare l'immagine {image_path}")
            return image
    
    def extract_image_from_pdf(self, pdf_path, page_num=0):
        """
        Estrae un'immagine da un file PDF.
        
        Args:
            pdf_path (str): Percorso del file PDF
            page_num (int): Numero di pagina da cui estrarre l'immagine (default: 0)
            
        Returns:
            numpy.ndarray: Immagine estratta in formato BGR
        """
        # Apri il documento PDF
        doc = fitz.open(pdf_path)
        
        # Controlla se il numero di pagina è valido
        if page_num >= len(doc):
            raise ValueError(f"Il PDF ha {len(doc)} pagine, ma è stata richiesta la pagina {page_num}")
        
        # Ottieni la pagina
        page = doc.load_page(page_num)
        
        # Renderizza la pagina come immagine
        pix = page.get_pixmap(matrix=fitz.Matrix(2, 2))  # Fattore di scala 2 per migliore qualità
        
        # Converti in formato immagine
        img_data = pix.samples
        
        # Crea un array numpy dall'immagine
        img_array = np.frombuffer(img_data, dtype=np.uint8).reshape(pix.height, pix.width, pix.n)
        
        # Se l'immagine è in formato RGB, converti in BGR per OpenCV
        if pix.n == 3:
            img_array = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
        
        return img_array
    
    def convert_to_grayscale(self, image):
        """
        Converte un'immagine in scala di grigi.
        
        Args:
            image (numpy.ndarray): Immagine di input in formato BGR
            
        Returns:
            numpy.ndarray: Immagine in scala di grigi
        """
        if len(image.shape) == 2:
            # L'immagine è già in scala di grigi
            return image
        
        return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    
    def normalize_image(self, image):
        """
        Normalizza un'immagine per migliorare contrasto e luminosità.
        
        Args:
            image (numpy.ndarray): Immagine di input (scala di grigi o BGR)
            
        Returns:
            numpy.ndarray: Immagine normalizzata
        """
        # Converti in scala di grigi se necessario
        if len(image.shape) > 2:
            gray = self.convert_to_grayscale(image)
        else:
            gray = image
        
        # Applica equalizzazione dell'istogramma
        return cv2.equalizeHist(gray)
    
    def detect_and_extract_stamps(self, image, lower_color=None, upper_color=None):
        """
        Rileva e estrae i timbri da un'immagine utilizzando il filtraggio del colore.
        
        Args:
            image (numpy.ndarray): Immagine di input in formato BGR
            lower_color (numpy.ndarray, optional): Limite inferiore del colore in formato HSV
            upper_color (numpy.ndarray, optional): Limite superiore del colore in formato HSV
            
        Returns:
            tuple: (immagine_originale_senza_timbri, maschera_timbri, timbri_estratti)
        """
        # Valori predefiniti per rilevare timbri blu (comuni nei documenti)
        if lower_color is None:
            lower_color = np.array([100, 50, 50])  # Blu in HSV
        if upper_color is None:
            upper_color = np.array([140, 255, 255])
        
        # Converti l'immagine in HSV
        hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
        
        # Crea una maschera per il colore specificato
        mask = cv2.inRange(hsv, lower_color, upper_color)
        
        # Applica operazioni morfologiche per migliorare la maschera
        kernel = np.ones((5, 5), np.uint8)
        mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
        mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
        
        # Estrai i timbri
        stamps = cv2.bitwise_and(image, image, mask=mask)
        
        # Crea un'immagine senza timbri
        inv_mask = cv2.bitwise_not(mask)
        image_without_stamps = cv2.bitwise_and(image, image, mask=inv_mask)
        
        return image_without_stamps, mask, stamps
    
    def threshold_image(self, image, method='adaptive'):
        """
        Applica una soglia all'immagine per binarizzarla.
        
        Args:
            image (numpy.ndarray): Immagine in scala di grigi
            method (str): Metodo di soglia ('simple', 'adaptive', 'otsu')
            
        Returns:
            numpy.ndarray: Immagine binaria
        """
        if len(image.shape) > 2:
            gray = self.convert_to_grayscale(image)
        else:
            gray = image
        
        if method == 'simple':
            _, binary = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY)
        elif method == 'adaptive':
            binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, 
                                          cv2.THRESH_BINARY_INV, 11, 2)
        elif method == 'otsu':
            _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        else:
            raise ValueError(f"Metodo di soglia non supportato: {method}")
        
        return binary
    
    def resize_image(self, image, width=None, height=None, keep_aspect_ratio=True):
        """
        Ridimensiona un'immagine a una larghezza o altezza specificata.
        
        Args:
            image (numpy.ndarray): Immagine di input
            width (int, optional): Larghezza desiderata
            height (int, optional): Altezza desiderata
            keep_aspect_ratio (bool): Mantiene il rapporto d'aspetto originale
            
        Returns:
            numpy.ndarray: Immagine ridimensionata
        """
        if width is None and height is None:
            return image
        
        h, w = image.shape[:2]
        
        if keep_aspect_ratio:
            if width is None:
                aspect_ratio = height / float(h)
                dim = (int(w * aspect_ratio), height)
            else:
                aspect_ratio = width / float(w)
                dim = (width, int(h * aspect_ratio))
        else:
            dim = (width if width is not None else w, height if height is not None else h)
        
        return cv2.resize(image, dim, interpolation=cv2.INTER_AREA)
    
    def denoise_image(self, image, method='gaussian'):
        """
        Applica un filtro di riduzione del rumore all'immagine.
        
        Args:
            image (numpy.ndarray): Immagine di input
            method (str): Metodo di denoising ('gaussian', 'median', 'bilateral')
            
        Returns:
            numpy.ndarray: Immagine filtrata
        """
        if method == 'gaussian':
            return cv2.GaussianBlur(image, (5, 5), 0)
        elif method == 'median':
            return cv2.medianBlur(image, 5)
        elif method == 'bilateral':
            if len(image.shape) > 2:
                return cv2.bilateralFilter(image, 9, 75, 75)
            else:
                # Per immagini in scala di grigi, convertiamo temporaneamente in BGR
                temp = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
                temp = cv2.bilateralFilter(temp, 9, 75, 75)
                return cv2.cvtColor(temp, cv2.COLOR_BGR2GRAY)
        else:
            raise ValueError(f"Metodo di denoising non supportato: {method}")
    
    def preprocess_signature(self, image_path, resize_width=800):
        """
        Pipeline completa di pre-elaborazione per le firme.
        
        Args:
            image_path (str): Percorso dell'immagine della firma
            resize_width (int): Larghezza a cui ridimensionare l'immagine
            
        Returns:
            dict: Dizionario contenente le diverse fasi di pre-elaborazione
        """
        # Carica l'immagine
        original = self.load_image(image_path)
        
        # Ridimensiona l'immagine
        resized = self.resize_image(original, width=resize_width)
        
        # Converti in scala di grigi
        gray = self.convert_to_grayscale(resized)
        
        # Normalizza l'immagine
        normalized = self.normalize_image(gray)
        
        # Applica denoising
        denoised = self.denoise_image(normalized, method='bilateral')
        
        # Applica soglia
        binary = self.threshold_image(denoised, method='adaptive')
        
        # Restituisci tutte le fasi di pre-elaborazione
        return {
            'original': original,
            'resized': resized,
            'grayscale': gray,
            'normalized': normalized,
            'denoised': denoised,
            'binary': binary
        }