Agregué app.py y requirements.txt
Browse files- app.py +233 -0
- requirements.txt +5 -0
app.py
ADDED
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
|
3 |
+
import cv2
|
4 |
+
import numpy as np
|
5 |
+
from PIL import Image
|
6 |
+
from PIL.ExifTags import TAGS, GPSTAGS
|
7 |
+
import hashlib
|
8 |
+
import gradio as gr
|
9 |
+
import matplotlib.pyplot as plt
|
10 |
+
import tempfile
|
11 |
+
import os
|
12 |
+
from datetime import datetime
|
13 |
+
|
14 |
+
def obtener_metadatos(imagen):
|
15 |
+
"""
|
16 |
+
Obtiene los metadatos de la imagen.
|
17 |
+
"""
|
18 |
+
metadatos = {}
|
19 |
+
try:
|
20 |
+
exif_data = imagen._getexif()
|
21 |
+
if exif_data:
|
22 |
+
for tag_id, value in exif_data.items():
|
23 |
+
tag = TAGS.get(tag_id, tag_id)
|
24 |
+
metadatos[tag] = value
|
25 |
+
except Exception as e:
|
26 |
+
print(f"Error al obtener metadatos: {e}")
|
27 |
+
return metadatos
|
28 |
+
|
29 |
+
def obtener_coordenadas(exif_data):
|
30 |
+
"""
|
31 |
+
Obtiene las coordenadas GPS de los metadatos EXIF.
|
32 |
+
"""
|
33 |
+
if not exif_data:
|
34 |
+
return None
|
35 |
+
|
36 |
+
gps_info = exif_data.get("GPSInfo", {})
|
37 |
+
if not gps_info:
|
38 |
+
return None
|
39 |
+
|
40 |
+
# Convertir las coordenadas GPS a grados decimales
|
41 |
+
def gps_to_degrees(value):
|
42 |
+
d, m, s = value
|
43 |
+
return d + (m / 60.0) + (s / 3600.0)
|
44 |
+
|
45 |
+
try:
|
46 |
+
latitud = gps_info.get(2) # Latitud
|
47 |
+
longitud = gps_info.get(4) # Longitud
|
48 |
+
latitud_ref = gps_info.get(1) # Norte/Sur
|
49 |
+
longitud_ref = gps_info.get(3) # Este/Oeste
|
50 |
+
|
51 |
+
if latitud and longitud and latitud_ref and longitud_ref:
|
52 |
+
latitud = gps_to_degrees(latitud)
|
53 |
+
longitud = gps_to_degrees(longitud)
|
54 |
+
if latitud_ref == "S": # Si es Sur, convertir a negativo
|
55 |
+
latitud = -latitud
|
56 |
+
if longitud_ref == "W": # Si es Oeste, convertir a negativo
|
57 |
+
longitud = -longitud
|
58 |
+
return latitud, longitud
|
59 |
+
except Exception as e:
|
60 |
+
print(f"Error al procesar coordenadas GPS: {e}")
|
61 |
+
return None
|
62 |
+
|
63 |
+
return None
|
64 |
+
|
65 |
+
def calcular_hash(imagen):
|
66 |
+
"""
|
67 |
+
Calcula el hash MD5 de la imagen para verificar su integridad.
|
68 |
+
"""
|
69 |
+
imagen_bytes = imagen.tobytes()
|
70 |
+
return hashlib.md5(imagen_bytes).hexdigest()
|
71 |
+
|
72 |
+
def analizar_manipulacion(imagen, metadatos):
|
73 |
+
"""
|
74 |
+
Analiza si la imagen ha sido manipulada.
|
75 |
+
"""
|
76 |
+
manipulada = False
|
77 |
+
razones = []
|
78 |
+
|
79 |
+
# Verificar si la imagen tiene marcas de agua
|
80 |
+
if imagen.mode == "P":
|
81 |
+
razones.append("La imagen tiene marcas de agua o es una imagen indexada.")
|
82 |
+
manipulada = True
|
83 |
+
|
84 |
+
# Verificar si los metadatos han sido alterados
|
85 |
+
if not metadatos:
|
86 |
+
razones.append("La imagen no tiene metadatos EXIF.")
|
87 |
+
manipulada = True
|
88 |
+
else:
|
89 |
+
if "Software" in metadatos:
|
90 |
+
razones.append(f"La imagen fue editada con: {metadatos['Software']}")
|
91 |
+
manipulada = True
|
92 |
+
|
93 |
+
# Verificar si el hash de la imagen coincide con un hash conocido (simulación)
|
94 |
+
hash_conocido = "1a79a4d60de6718e8e5b326e338ae533" # Hash de ejemplo
|
95 |
+
hash_actual = calcular_hash(imagen)
|
96 |
+
if hash_actual != hash_conocido:
|
97 |
+
razones.append("El hash de la imagen no coincide con el hash conocido.")
|
98 |
+
manipulada = True
|
99 |
+
|
100 |
+
return manipulada, razones
|
101 |
+
|
102 |
+
def realizar_ela(imagen, quality=95, scale=100):
|
103 |
+
"""
|
104 |
+
Realiza el Error Level Analysis (ELA) en la imagen utilizando OpenCV.
|
105 |
+
"""
|
106 |
+
# Convertir la imagen a formato compatible con OpenCV
|
107 |
+
imagen_cv = cv2.cvtColor(np.array(imagen), cv2.COLOR_RGB2BGR)
|
108 |
+
|
109 |
+
# Guardar la imagen comprimida
|
110 |
+
temp_path = "temp_image.jpg"
|
111 |
+
cv2.imwrite(temp_path, imagen_cv, [cv2.IMWRITE_JPEG_QUALITY, quality])
|
112 |
+
|
113 |
+
# Leer la imagen comprimida
|
114 |
+
imagen_comprimida = cv2.imread(temp_path)
|
115 |
+
|
116 |
+
# Calcular la diferencia absoluta entre la imagen original y la comprimida
|
117 |
+
diferencia = cv2.absdiff(imagen_cv, imagen_comprimida)
|
118 |
+
|
119 |
+
# Escalar la diferencia para resaltar las áreas modificadas
|
120 |
+
ela_imagen = scale * diferencia
|
121 |
+
|
122 |
+
# Convertir a escala de grises para mejorar la visualización
|
123 |
+
ela_imagen = cv2.cvtColor(ela_imagen, cv2.COLOR_BGR2GRAY)
|
124 |
+
|
125 |
+
# Eliminar el archivo temporal
|
126 |
+
os.remove(temp_path)
|
127 |
+
|
128 |
+
return ela_imagen
|
129 |
+
|
130 |
+
def procesar_imagen(archivo_imagen):
|
131 |
+
# Cargar la imagen
|
132 |
+
try:
|
133 |
+
imagen = Image.open(archivo_imagen)
|
134 |
+
except Exception as e:
|
135 |
+
return f"Error al cargar la imagen: {e}", None
|
136 |
+
|
137 |
+
# Obtener información básica de la imagen
|
138 |
+
nombre_archivo = os.path.basename(archivo_imagen)
|
139 |
+
info_basica = f"""
|
140 |
+
Información básica de la imagen:
|
141 |
+
Nombre del archivo: {nombre_archivo}
|
142 |
+
Formato: {imagen.format}
|
143 |
+
Tamaño: {imagen.size} píxeles
|
144 |
+
Modo: {imagen.mode}
|
145 |
+
"""
|
146 |
+
|
147 |
+
# Obtener tamaño del archivo, fecha de creación y modificación
|
148 |
+
stats = os.stat(archivo_imagen)
|
149 |
+
tamaño_archivo = stats.st_size / 1024 # Tamaño en KB
|
150 |
+
fecha_creacion = datetime.fromtimestamp(stats.st_ctime).strftime('%Y-%m-%d %H:%M:%S')
|
151 |
+
fecha_modificacion = datetime.fromtimestamp(stats.st_mtime).strftime('%Y-%m-%d %H:%M:%S')
|
152 |
+
|
153 |
+
info_basica += f"""
|
154 |
+
Tamaño del archivo: {tamaño_archivo:.2f} KB
|
155 |
+
Fecha de creación: {fecha_creacion}
|
156 |
+
Fecha de modificación: {fecha_modificacion}
|
157 |
+
"""
|
158 |
+
|
159 |
+
# Obtener los primeros 10 bytes del archivo en formato hexadecimal
|
160 |
+
with open(archivo_imagen, "rb") as f:
|
161 |
+
primeros_10_bytes = f.read(10)
|
162 |
+
header_hex = " ".join(f"{byte:02x}" for byte in primeros_10_bytes)
|
163 |
+
|
164 |
+
info_basica += f"""
|
165 |
+
Primeros 10 bytes del archivo (hex): {header_hex}
|
166 |
+
"""
|
167 |
+
|
168 |
+
# Obtener metadatos
|
169 |
+
metadatos = obtener_metadatos(imagen)
|
170 |
+
info_metadatos = "\n" + "-" * 50 + "\nANÁLISIS FORENSE DE LOS METADATOS DE LA IMAGEN:\n"
|
171 |
+
if metadatos:
|
172 |
+
for tag, value in metadatos.items():
|
173 |
+
if tag == "DateTime":
|
174 |
+
info_metadatos += f"Fecha y hora de la captura: {value}\n"
|
175 |
+
elif tag == "Make":
|
176 |
+
info_metadatos += f"Fabricante de la cámara: {value}\n"
|
177 |
+
elif tag == "Model":
|
178 |
+
info_metadatos += f"Modelo de la cámara: {value}\n"
|
179 |
+
elif tag == "Software":
|
180 |
+
info_metadatos += f"Software utilizado para editar la imagen: {value}\n"
|
181 |
+
elif tag == "ExifImageWidth":
|
182 |
+
info_metadatos += f"Ancho de la imagen: {value} píxeles\n"
|
183 |
+
elif tag == "ExifImageHeight":
|
184 |
+
info_metadatos += f"Alto de la imagen: {value} píxeles\n"
|
185 |
+
elif tag == "GPSInfo":
|
186 |
+
info_metadatos += "Información de ubicación GPS:\n"
|
187 |
+
coordenadas = obtener_coordenadas(metadatos)
|
188 |
+
if coordenadas:
|
189 |
+
latitud, longitud = coordenadas
|
190 |
+
enlace_google_maps = f"https://www.google.com/maps?q={latitud},{longitud}"
|
191 |
+
info_metadatos += f"- Coordenadas GPS: {latitud}, {longitud}\n"
|
192 |
+
info_metadatos += f"- Enlace a Google Maps: {enlace_google_maps}\n"
|
193 |
+
else:
|
194 |
+
info_metadatos += "- No se encontraron coordenadas GPS.\n"
|
195 |
+
else:
|
196 |
+
info_metadatos += f"{tag}: {value}\n"
|
197 |
+
else:
|
198 |
+
info_metadatos += "No se encontraron metadatos.\n"
|
199 |
+
|
200 |
+
# Analizar si la imagen ha sido manipulada
|
201 |
+
manipulada, razones = analizar_manipulacion(imagen, metadatos)
|
202 |
+
info_manipulacion = "\nAnálisis de manipulación:\n"
|
203 |
+
if manipulada:
|
204 |
+
info_manipulacion += "La imagen ha sido manipulada.\n"
|
205 |
+
info_manipulacion += "Razones:\n"
|
206 |
+
for razon in razones:
|
207 |
+
info_manipulacion += f"- {razon}\n"
|
208 |
+
else:
|
209 |
+
info_manipulacion += "La imagen NO ha sido manipulada.\n"
|
210 |
+
|
211 |
+
# Realizar Error Level Analysis (ELA)
|
212 |
+
ela_imagen = realizar_ela(imagen)
|
213 |
+
|
214 |
+
# Guardar la imagen ELA en un archivo temporal
|
215 |
+
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_file:
|
216 |
+
cv2.imwrite(tmp_file.name, ela_imagen)
|
217 |
+
ela_image_path = tmp_file.name
|
218 |
+
|
219 |
+
return ela_image_path, info_basica + info_metadatos + info_manipulacion
|
220 |
+
|
221 |
+
# Interfaz de GRADIO
|
222 |
+
iface = gr.Interface(
|
223 |
+
fn=procesar_imagen,
|
224 |
+
inputs=gr.Image(type="filepath", label="Sube una imagen"),
|
225 |
+
outputs=[gr.Image(label="Error Level Analysis (ELA)"), gr.Textbox(label="Resultado del análisis")],
|
226 |
+
title="Análisis de metadatos y Error de ELA en imágenes digitales con Gradio",
|
227 |
+
description="""<div style="text-align: center;">
|
228 |
+
<p>Este programa es una herramienta de computación forense diseñado para analizar imágenes en busca de evidencia de manipulación o edición. Utiliza la técnica de Error Level Analysis (ELA) y fue desarrollado por José R. Leonett para los peritos forenses digitales de Guatemala <a href="http://www.forensedigital.gt">www.forensedigital.gt</a>, analistas forenses o cualquier persona interesada en verificar la autenticidad de imágenes digitales.</p>
|
229 |
+
</div>"""
|
230 |
+
)
|
231 |
+
|
232 |
+
# Lanzar la interfaz de Gradio
|
233 |
+
iface.launch()
|
requirements.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
gradio
|
2 |
+
pillow
|
3 |
+
opencv-python
|
4 |
+
numpy
|
5 |
+
matplotlib
|