leonett commited on
Commit
314ee6f
·
verified ·
1 Parent(s): d26745c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +32 -33
app.py CHANGED
@@ -21,7 +21,7 @@ logger.info(f"Directorio de evidencia creado en: {evidence_dir}")
21
  def obtener_metadatos(imagen):
22
  try:
23
  exif_data = imagen.getexif()
24
- if not exif_data:
25
  return {}
26
  metadata = {}
27
  for tag_id, value in exif_data.items():
@@ -36,7 +36,7 @@ def obtener_metadatos(imagen):
36
  return {}
37
 
38
  def obtener_coordenadas(exif_data):
39
- if not exif_data or "GPSInfo" not in exif_data:
40
  return None
41
  try:
42
  gps_info = exif_data["GPSInfo"]
@@ -70,25 +70,20 @@ def analizar_manipulacion(imagen, metadatos):
70
  if imagen.mode == "P":
71
  razones.append("La imagen tiene marcas de agua o es una imagen indexada.")
72
  manipulada = True
73
- # ❌ Eliminado: ya no se añade por defecto "no tiene metadatos" ni "hash no coincide"
74
  if "Software" in metadatos:
75
  razones.append(f"La imagen fue editada con: {metadatos['Software']}")
76
  manipulada = True
77
  return manipulada, razones
78
 
79
  def calcular_porcentaje_ela(ela_imagen, mask):
80
- """Calcula porcentaje de píxeles blancos/amarillos (altos valores) en la imagen ELA."""
81
  if mask is None or mask.size == 0:
82
  return 0.0
83
-
84
  total_pixeles = mask.size
85
  pixeles_detectados = np.count_nonzero(mask)
86
  porcentaje = (pixeles_detectados / total_pixeles) * 100
87
-
88
  return porcentaje
89
 
90
  def estimar_probabilidad_manipulacion(porcentaje_ela):
91
- """Estima probabilidad de manipulación basado en porcentaje de áreas detectadas."""
92
  if porcentaje_ela < 0.5:
93
  return "Muy baja (< 0.5%) - Imagen probablemente auténtica."
94
  elif porcentaje_ela < 2.0:
@@ -101,15 +96,13 @@ def estimar_probabilidad_manipulacion(porcentaje_ela):
101
  return "Muy alta (> 15%) - Manipulación extensa o generación por IA detectada."
102
 
103
  def realizar_ela(imagen):
104
- """Realiza ELA optimizado para detectar manipulaciones incluso en imágenes generadas por IA."""
105
  try:
106
  img_np = np.array(imagen.convert("RGB"))
107
  img_cv = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR)
108
 
109
- # Parámetros ajustados para mayor sensibilidad (especialmente en IA)
110
- quality = 99 # Bajamos calidad para resaltar más diferencias
111
- noise_level = 3 # Aumentamos sensibilidad al ruido
112
- error_scale = 15 # Más sensible a errores
113
  brightness_pct = 100
114
  equalize_histogram = False
115
 
@@ -117,15 +110,14 @@ def realizar_ela(imagen):
117
  cv2.imwrite(temp_path, img_cv, [cv2.IMWRITE_JPEG_QUALITY, quality])
118
  img_comprimida = cv2.imread(temp_path)
119
  if img_comprimida is None:
120
- raise ValueError("Error al leer la imagen comprimida")
121
 
122
  diferencia = cv2.absdiff(img_cv.astype(np.float32), img_comprimida.astype(np.float32))
123
  scaled_diff = diferencia * (noise_level / 10.0) * (error_scale / 100.0) * 20.0
124
  scaled_diff = np.clip(scaled_diff, 0, 255).astype(np.uint8)
125
 
126
- # ✅ Reducimos umbral para captar sutilezas de IA
127
  gray_diff = cv2.cvtColor(scaled_diff, cv2.COLOR_BGR2GRAY)
128
- _, mask = cv2.threshold(gray_diff, 20, 255, cv2.THRESH_BINARY) # ← ¡MÁS SENSIBLE!
129
  mask = mask.astype(np.uint8)
130
 
131
  if equalize_histogram:
@@ -133,7 +125,7 @@ def realizar_ela(imagen):
133
  gray_eq = cv2.equalizeHist(gray)
134
  scaled_diff = cv2.cvtColor(gray_eq, cv2.COLOR_GRAY2BGR)
135
 
136
- brightness_factor = min(1.0, brightness_pct / 100.0)
137
  scaled_diff = cv2.convertScaleAbs(scaled_diff, alpha=brightness_factor, beta=0)
138
 
139
  ela_color = scaled_diff.copy()
@@ -144,7 +136,7 @@ def realizar_ela(imagen):
144
  result = np.where(mask[..., None] > 0, ela_color, img_gray)
145
 
146
  os.remove(temp_path)
147
- return result, mask # ← ¡Ahora devuelve también la máscara para cálculo!
148
 
149
  except Exception as e:
150
  logger.error(f"Error en ELA: {str(e)}")
@@ -153,10 +145,12 @@ def realizar_ela(imagen):
153
  return error_img, None
154
 
155
  def procesar_imagen(archivo_imagen):
156
- """Procesa la imagen y devuelve ZIP + texto de análisis + imagen ELA + URL de Google Maps (si aplica)."""
157
- # Validación: si no hay imagen, devolver mensaje
158
- if not archivo_imagen or not os.path.exists(archivo_imagen):
159
- return None, "❌ Por favor, cargue una imagen válida antes de analizar.", None, ""
 
 
160
 
161
  try:
162
  img = Image.open(archivo_imagen)
@@ -171,25 +165,23 @@ def procesar_imagen(archivo_imagen):
171
  zip_path = os.path.join(evidence_dir, f"{nombre}_errorELA.zip")
172
 
173
  img.save(original_path)
174
- ela_result, mask = realizar_ela(img) # ← Recibimos máscara
175
 
176
  if mask is None:
177
- raise Exception("No se pudo generar la máscara ELA.")
178
 
179
  cv2.imwrite(ela_path, ela_result)
180
 
181
- # Calcular porcentaje de áreas detectadas
182
  porcentaje_ela = calcular_porcentaje_ela(ela_result, mask)
183
  probabilidad = estimar_probabilidad_manipulacion(porcentaje_ela)
184
 
185
- # ✅ Obtener tamaño del archivo
186
  file_size_bytes = os.path.getsize(archivo_imagen)
187
  if file_size_bytes < 1024 * 1024:
188
  file_size = f"{file_size_bytes / 1024:.2f} KB"
189
  else:
190
  file_size = f"{file_size_bytes / (1024 * 1024):.2f} MB"
191
 
192
- # Información básica
193
  info_basica = f"**Nombre del archivo:** {nombre_original}\r\n"
194
  info_basica += f"**Tamaño del archivo:** {file_size}\r\n"
195
  info_basica += f"**Dimensiones:** {img.size[0]} x {img.size[1]} píxeles\r\n"
@@ -226,12 +218,10 @@ def procesar_imagen(archivo_imagen):
226
  info_metadatos += "- No se encontraron metadatos EXIF\r\n"
227
 
228
  sha3_hash = calcular_hash(img)
229
- info_metadatos += f"\r\n- SHA3-256: {sha3_hash}\r\n\r\n"
230
 
231
  manipulada, razones = analizar_manipulacion(img, metadatos)
232
  info_manipulacion = "**ANÁLISIS DE MANIPULACIÓN:**\r\n\r\n"
233
-
234
- # ✅ Añadir porcentaje y estimación
235
  info_manipulacion += f"- **Porcentaje de áreas detectadas (blanco/amarillo en ELA):** {porcentaje_ela:.3f}%\r\n"
236
  info_manipulacion += f"- **Estimación forense:** {probabilidad}\r\n\r\n"
237
 
@@ -260,9 +250,10 @@ def procesar_imagen(archivo_imagen):
260
 
261
  except Exception as e:
262
  logger.error(f"Error en procesamiento: {str(e)}")
 
263
  error_img = np.zeros((300, 600, 3), dtype=np.uint8)
264
- cv2.putText(error_img, f"ERROR: {str(e)[:50]}", (10, 150), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,255), 2)
265
- return f"Error: {str(e)}", f"Error al procesar la imagen: {str(e)}", error_img, ""
266
 
267
  # 🎨 Tema moderno
268
  theme = gr.themes.Soft(primary_hue="blue", secondary_hue="slate", neutral_hue="stone")
@@ -284,7 +275,14 @@ with gr.Blocks(title="Análisis Forense de Imágenes con ELA", theme=theme) as d
284
  sources=["upload"]
285
  )
286
  process_btn = gr.Button("Analizar imagen", variant="primary")
287
- download_zip = gr.File(label="⬇️ Descargar resultados (ZIP)", interactive=False, visible=False)
 
 
 
 
 
 
 
288
 
289
  with gr.Column():
290
  with gr.Accordion("Resultado del Análisis ELA", open=True):
@@ -309,6 +307,7 @@ with gr.Blocks(title="Análisis Forense de Imágenes con ELA", theme=theme) as d
309
  ""
310
  )
311
 
 
312
  process_btn.click(
313
  fn=procesar_imagen,
314
  inputs=input_image,
@@ -344,5 +343,5 @@ if __name__ == "__main__":
344
  server_port=7860,
345
  share=True,
346
  inbrowser=True,
347
- favicon_path="https://huggingface.co/datasets/huggingface/logo/resolve/main/hf-logo.png"
348
  )
 
21
  def obtener_metadatos(imagen):
22
  try:
23
  exif_data = imagen.getexif()
24
+ if not exif_
25
  return {}
26
  metadata = {}
27
  for tag_id, value in exif_data.items():
 
36
  return {}
37
 
38
  def obtener_coordenadas(exif_data):
39
+ if not exif_data or "GPSInfo" not in exif_
40
  return None
41
  try:
42
  gps_info = exif_data["GPSInfo"]
 
70
  if imagen.mode == "P":
71
  razones.append("La imagen tiene marcas de agua o es una imagen indexada.")
72
  manipulada = True
 
73
  if "Software" in metadatos:
74
  razones.append(f"La imagen fue editada con: {metadatos['Software']}")
75
  manipulada = True
76
  return manipulada, razones
77
 
78
  def calcular_porcentaje_ela(ela_imagen, mask):
 
79
  if mask is None or mask.size == 0:
80
  return 0.0
 
81
  total_pixeles = mask.size
82
  pixeles_detectados = np.count_nonzero(mask)
83
  porcentaje = (pixeles_detectados / total_pixeles) * 100
 
84
  return porcentaje
85
 
86
  def estimar_probabilidad_manipulacion(porcentaje_ela):
 
87
  if porcentaje_ela < 0.5:
88
  return "Muy baja (< 0.5%) - Imagen probablemente auténtica."
89
  elif porcentaje_ela < 2.0:
 
96
  return "Muy alta (> 15%) - Manipulación extensa o generación por IA detectada."
97
 
98
  def realizar_ela(imagen):
 
99
  try:
100
  img_np = np.array(imagen.convert("RGB"))
101
  img_cv = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR)
102
 
103
+ quality = 90
104
+ noise_level = 8
105
+ error_scale = 25
 
106
  brightness_pct = 100
107
  equalize_histogram = False
108
 
 
110
  cv2.imwrite(temp_path, img_cv, [cv2.IMWRITE_JPEG_QUALITY, quality])
111
  img_comprimida = cv2.imread(temp_path)
112
  if img_comprimida is None:
113
+ raise ValueError("No se pudo leer la imagen comprimida.")
114
 
115
  diferencia = cv2.absdiff(img_cv.astype(np.float32), img_comprimida.astype(np.float32))
116
  scaled_diff = diferencia * (noise_level / 10.0) * (error_scale / 100.0) * 20.0
117
  scaled_diff = np.clip(scaled_diff, 0, 255).astype(np.uint8)
118
 
 
119
  gray_diff = cv2.cvtColor(scaled_diff, cv2.COLOR_BGR2GRAY)
120
+ _, mask = cv2.threshold(gray_diff, 20, 255, cv2.THRESH_BINARY)
121
  mask = mask.astype(np.uint8)
122
 
123
  if equalize_histogram:
 
125
  gray_eq = cv2.equalizeHist(gray)
126
  scaled_diff = cv2.cvtColor(gray_eq, cv2.COLOR_GRAY2BGR)
127
 
128
+ brightness_factor = min(1.1, brightness_pct / 100.0)
129
  scaled_diff = cv2.convertScaleAbs(scaled_diff, alpha=brightness_factor, beta=0)
130
 
131
  ela_color = scaled_diff.copy()
 
136
  result = np.where(mask[..., None] > 0, ela_color, img_gray)
137
 
138
  os.remove(temp_path)
139
+ return result, mask
140
 
141
  except Exception as e:
142
  logger.error(f"Error en ELA: {str(e)}")
 
145
  return error_img, None
146
 
147
  def procesar_imagen(archivo_imagen):
148
+ # VALIDACIÓN URGENTE: Si no hay imagen, detener y avisar
149
+ if not archivo_imagen:
150
+ return None, "❌ **ERROR: Por favor, cargue una imagen antes de analizar.**", None, ""
151
+
152
+ if not os.path.exists(archivo_imagen):
153
+ return None, "❌ **ERROR: El archivo de imagen no existe o es inválido.**", None, ""
154
 
155
  try:
156
  img = Image.open(archivo_imagen)
 
165
  zip_path = os.path.join(evidence_dir, f"{nombre}_errorELA.zip")
166
 
167
  img.save(original_path)
168
+ ela_result, mask = realizar_ela(img)
169
 
170
  if mask is None:
171
+ raise Exception("No se pudo generar el análisis ELA.")
172
 
173
  cv2.imwrite(ela_path, ela_result)
174
 
 
175
  porcentaje_ela = calcular_porcentaje_ela(ela_result, mask)
176
  probabilidad = estimar_probabilidad_manipulacion(porcentaje_ela)
177
 
 
178
  file_size_bytes = os.path.getsize(archivo_imagen)
179
  if file_size_bytes < 1024 * 1024:
180
  file_size = f"{file_size_bytes / 1024:.2f} KB"
181
  else:
182
  file_size = f"{file_size_bytes / (1024 * 1024):.2f} MB"
183
 
184
+ # TODOS LOS CAMPOS EN NEGRITA como solicitaste
185
  info_basica = f"**Nombre del archivo:** {nombre_original}\r\n"
186
  info_basica += f"**Tamaño del archivo:** {file_size}\r\n"
187
  info_basica += f"**Dimensiones:** {img.size[0]} x {img.size[1]} píxeles\r\n"
 
218
  info_metadatos += "- No se encontraron metadatos EXIF\r\n"
219
 
220
  sha3_hash = calcular_hash(img)
221
+ info_metadatos += f"\r\n**SHA3-256:** {sha3_hash}\r\n\r\n"
222
 
223
  manipulada, razones = analizar_manipulacion(img, metadatos)
224
  info_manipulacion = "**ANÁLISIS DE MANIPULACIÓN:**\r\n\r\n"
 
 
225
  info_manipulacion += f"- **Porcentaje de áreas detectadas (blanco/amarillo en ELA):** {porcentaje_ela:.3f}%\r\n"
226
  info_manipulacion += f"- **Estimación forense:** {probabilidad}\r\n\r\n"
227
 
 
250
 
251
  except Exception as e:
252
  logger.error(f"Error en procesamiento: {str(e)}")
253
+ mensaje_error = f"❌ **ERROR CRÍTICO:** {str(e)}"
254
  error_img = np.zeros((300, 600, 3), dtype=np.uint8)
255
+ cv2.putText(error_img, "ERROR INTERNO", (80, 160), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
256
+ return None, mensaje_error, error_img, ""
257
 
258
  # 🎨 Tema moderno
259
  theme = gr.themes.Soft(primary_hue="blue", secondary_hue="slate", neutral_hue="stone")
 
275
  sources=["upload"]
276
  )
277
  process_btn = gr.Button("Analizar imagen", variant="primary")
278
+
279
+ # ✅ BOTÓN DE DESCARGA NARANJA MODERNO
280
+ download_zip = gr.DownloadButton(
281
+ label="⬇️ Descargar resultados (ZIP)",
282
+ variant="primary",
283
+ visible=False,
284
+ interactive=True
285
+ )
286
 
287
  with gr.Column():
288
  with gr.Accordion("Resultado del Análisis ELA", open=True):
 
307
  ""
308
  )
309
 
310
+ # ✅ Conexión del evento de análisis — ahora con manejo de errores visible
311
  process_btn.click(
312
  fn=procesar_imagen,
313
  inputs=input_image,
 
343
  server_port=7860,
344
  share=True,
345
  inbrowser=True,
346
+ favicon_path="https://www.forensedigital.gt/wp-content/uploads/2019/07/cropped-40fb84a6-c75a-4c38-bfa0-c0a9777430cd-1.jpg"
347
  )