Update app.py
Browse files
app.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
|
|
| 1 |
import cv2
|
| 2 |
import numpy as np
|
| 3 |
from PIL import Image
|
| 4 |
from PIL.ExifTags import TAGS, GPSTAGS
|
| 5 |
import hashlib
|
| 6 |
import gradio as gr
|
| 7 |
-
import matplotlib.pyplot as plt
|
| 8 |
-
import tempfile
|
| 9 |
import os
|
| 10 |
from datetime import datetime
|
| 11 |
|
|
|
|
| 12 |
def obtener_metadatos(imagen):
|
| 13 |
metadatos = {}
|
| 14 |
try:
|
|
@@ -21,9 +21,11 @@ def obtener_metadatos(imagen):
|
|
| 21 |
print(f"Error al obtener metadatos: {e}")
|
| 22 |
return metadatos
|
| 23 |
|
|
|
|
| 24 |
def obtener_coordenadas(exif_data):
|
| 25 |
if not exif_data:
|
| 26 |
return None
|
|
|
|
| 27 |
gps_info = exif_data.get("GPSInfo", {})
|
| 28 |
if not gps_info:
|
| 29 |
return None
|
|
@@ -52,10 +54,12 @@ def obtener_coordenadas(exif_data):
|
|
| 52 |
|
| 53 |
return None
|
| 54 |
|
|
|
|
| 55 |
def calcular_hash(imagen):
|
| 56 |
imagen_bytes = imagen.tobytes()
|
| 57 |
return hashlib.md5(imagen_bytes).hexdigest()
|
| 58 |
|
|
|
|
| 59 |
def analizar_manipulacion(imagen, metadatos):
|
| 60 |
manipulada = False
|
| 61 |
razones = []
|
|
@@ -80,17 +84,23 @@ def analizar_manipulacion(imagen, metadatos):
|
|
| 80 |
|
| 81 |
return manipulada, razones
|
| 82 |
|
| 83 |
-
|
|
|
|
| 84 |
imagen_cv = cv2.cvtColor(np.array(imagen), cv2.COLOR_RGB2BGR)
|
| 85 |
-
|
| 86 |
-
cv2.
|
| 87 |
-
|
| 88 |
diferencia = cv2.absdiff(imagen_cv, imagen_comprimida)
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
return ela_imagen
|
| 93 |
|
|
|
|
| 94 |
def procesar_imagen(archivo_imagen):
|
| 95 |
try:
|
| 96 |
imagen = Image.open(archivo_imagen)
|
|
@@ -99,81 +109,177 @@ def procesar_imagen(archivo_imagen):
|
|
| 99 |
|
| 100 |
nombre_archivo = os.path.basename(archivo_imagen)
|
| 101 |
info_basica = f"""
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
|
|
|
| 107 |
"""
|
|
|
|
| 108 |
stats = os.stat(archivo_imagen)
|
| 109 |
tamaño_archivo = stats.st_size / 1024
|
| 110 |
fecha_creacion = datetime.fromtimestamp(stats.st_ctime).strftime('%Y-%m-%d %H:%M:%S')
|
| 111 |
fecha_modificacion = datetime.fromtimestamp(stats.st_mtime).strftime('%Y-%m-%d %H:%M:%S')
|
| 112 |
|
| 113 |
info_basica += f"""
|
| 114 |
-
Tamaño del archivo
|
| 115 |
-
Fecha de creación
|
| 116 |
-
Fecha de modificación
|
| 117 |
"""
|
| 118 |
|
| 119 |
with open(archivo_imagen, "rb") as f:
|
| 120 |
primeros_10_bytes = f.read(10)
|
| 121 |
header_hex = " ".join(f"{byte:02x}" for byte in primeros_10_bytes)
|
| 122 |
-
|
| 123 |
-
|
|
|
|
|
|
|
| 124 |
|
| 125 |
metadatos = obtener_metadatos(imagen)
|
| 126 |
-
info_metadatos = "
|
|
|
|
|
|
|
| 127 |
if metadatos:
|
| 128 |
for tag, value in metadatos.items():
|
| 129 |
if tag == "DateTime":
|
| 130 |
-
info_metadatos += f"Fecha y hora de
|
| 131 |
elif tag == "Make":
|
| 132 |
-
info_metadatos += f"Fabricante
|
| 133 |
elif tag == "Model":
|
| 134 |
-
info_metadatos += f"Modelo
|
| 135 |
elif tag == "Software":
|
| 136 |
-
info_metadatos += f"Software
|
| 137 |
elif tag == "ExifImageWidth":
|
| 138 |
-
info_metadatos += f"Ancho
|
| 139 |
elif tag == "ExifImageHeight":
|
| 140 |
-
info_metadatos += f"Alto
|
| 141 |
elif tag == "GPSInfo":
|
|
|
|
| 142 |
coordenadas = obtener_coordenadas(metadatos)
|
| 143 |
if coordenadas:
|
| 144 |
latitud, longitud = coordenadas
|
| 145 |
-
|
| 146 |
-
info_metadatos += f"Coordenadas
|
|
|
|
| 147 |
else:
|
| 148 |
-
info_metadatos += "No se encontraron coordenadas GPS
|
| 149 |
-
else:
|
| 150 |
-
info_metadatos += f"{tag}: {value}\n"
|
| 151 |
else:
|
| 152 |
-
info_metadatos += "No se encontraron metadatos
|
|
|
|
| 153 |
|
| 154 |
manipulada, razones = analizar_manipulacion(imagen, metadatos)
|
| 155 |
-
info_manipulacion = "
|
|
|
|
|
|
|
| 156 |
if manipulada:
|
| 157 |
-
info_manipulacion += "
|
|
|
|
| 158 |
for razon in razones:
|
| 159 |
-
info_manipulacion += f"
|
|
|
|
| 160 |
else:
|
| 161 |
-
info_manipulacion += "
|
|
|
|
| 162 |
|
| 163 |
ela_imagen = realizar_ela(imagen)
|
|
|
|
|
|
|
| 164 |
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
|
| 179 |
-
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
import cv2
|
| 3 |
import numpy as np
|
| 4 |
from PIL import Image
|
| 5 |
from PIL.ExifTags import TAGS, GPSTAGS
|
| 6 |
import hashlib
|
| 7 |
import gradio as gr
|
|
|
|
|
|
|
| 8 |
import os
|
| 9 |
from datetime import datetime
|
| 10 |
|
| 11 |
+
# Función para obtener metadatos
|
| 12 |
def obtener_metadatos(imagen):
|
| 13 |
metadatos = {}
|
| 14 |
try:
|
|
|
|
| 21 |
print(f"Error al obtener metadatos: {e}")
|
| 22 |
return metadatos
|
| 23 |
|
| 24 |
+
# Función para obtener coordenadas GPS
|
| 25 |
def obtener_coordenadas(exif_data):
|
| 26 |
if not exif_data:
|
| 27 |
return None
|
| 28 |
+
|
| 29 |
gps_info = exif_data.get("GPSInfo", {})
|
| 30 |
if not gps_info:
|
| 31 |
return None
|
|
|
|
| 54 |
|
| 55 |
return None
|
| 56 |
|
| 57 |
+
# Función para calcular hash
|
| 58 |
def calcular_hash(imagen):
|
| 59 |
imagen_bytes = imagen.tobytes()
|
| 60 |
return hashlib.md5(imagen_bytes).hexdigest()
|
| 61 |
|
| 62 |
+
# Función para analizar manipulación
|
| 63 |
def analizar_manipulacion(imagen, metadatos):
|
| 64 |
manipulada = False
|
| 65 |
razones = []
|
|
|
|
| 84 |
|
| 85 |
return manipulada, razones
|
| 86 |
|
| 87 |
+
# Función para realizar ELA
|
| 88 |
+
def realizar_ela(imagen, quality=95, scale=15):
|
| 89 |
imagen_cv = cv2.cvtColor(np.array(imagen), cv2.COLOR_RGB2BGR)
|
| 90 |
+
_, buffer = cv2.imencode(".jpg", imagen_cv, [cv2.IMWRITE_JPEG_QUALITY, quality])
|
| 91 |
+
imagen_comprimida = cv2.imdecode(buffer, cv2.IMREAD_COLOR)
|
| 92 |
+
|
| 93 |
diferencia = cv2.absdiff(imagen_cv, imagen_comprimida)
|
| 94 |
+
diferencia = np.max(diferencia, axis=2)
|
| 95 |
+
diferencia = scale * diferencia
|
| 96 |
+
diferencia = np.clip(diferencia, 0, 255).astype(np.uint8)
|
| 97 |
+
|
| 98 |
+
ela_imagen = cv2.applyColorMap(diferencia, cv2.COLORMAP_HOT)
|
| 99 |
+
ela_imagen = cv2.cvtColor(ela_imagen, cv2.COLOR_BGR2RGB)
|
| 100 |
+
|
| 101 |
return ela_imagen
|
| 102 |
|
| 103 |
+
# Función principal de procesamiento
|
| 104 |
def procesar_imagen(archivo_imagen):
|
| 105 |
try:
|
| 106 |
imagen = Image.open(archivo_imagen)
|
|
|
|
| 109 |
|
| 110 |
nombre_archivo = os.path.basename(archivo_imagen)
|
| 111 |
info_basica = f"""
|
| 112 |
+
<div style='text-align: left;'>
|
| 113 |
+
<h3>Información básica</h3>
|
| 114 |
+
<b>Nombre del archivo:</b> {nombre_archivo}<br>
|
| 115 |
+
<b>Formato:</b> {imagen.format}<br>
|
| 116 |
+
<b>Tamaño:</b> {imagen.size[0]}x{imagen.size[1]} píxeles<br>
|
| 117 |
+
<b>Modo:</b> {imagen.mode}<br>
|
| 118 |
"""
|
| 119 |
+
|
| 120 |
stats = os.stat(archivo_imagen)
|
| 121 |
tamaño_archivo = stats.st_size / 1024
|
| 122 |
fecha_creacion = datetime.fromtimestamp(stats.st_ctime).strftime('%Y-%m-%d %H:%M:%S')
|
| 123 |
fecha_modificacion = datetime.fromtimestamp(stats.st_mtime).strftime('%Y-%m-%d %H:%M:%S')
|
| 124 |
|
| 125 |
info_basica += f"""
|
| 126 |
+
<b>Tamaño del archivo:</b> {tamaño_archivo:.2f} KB<br>
|
| 127 |
+
<b>Fecha de creación:</b> {fecha_creacion}<br>
|
| 128 |
+
<b>Fecha de modificación:</b> {fecha_modificacion}<br>
|
| 129 |
"""
|
| 130 |
|
| 131 |
with open(archivo_imagen, "rb") as f:
|
| 132 |
primeros_10_bytes = f.read(10)
|
| 133 |
header_hex = " ".join(f"{byte:02x}" for byte in primeros_10_bytes)
|
| 134 |
+
info_basica += f"""
|
| 135 |
+
<b>Primeros 10 bytes (hex):</b> {header_hex}<br>
|
| 136 |
+
</div>
|
| 137 |
+
"""
|
| 138 |
|
| 139 |
metadatos = obtener_metadatos(imagen)
|
| 140 |
+
info_metadatos = "<div style='text-align: left;'>"
|
| 141 |
+
info_metadatos += "<h3>ANÁLISIS FORENSE DE METADATOS</h3>"
|
| 142 |
+
|
| 143 |
if metadatos:
|
| 144 |
for tag, value in metadatos.items():
|
| 145 |
if tag == "DateTime":
|
| 146 |
+
info_metadatos += f"<b>Fecha y hora de captura:</b> {value}<br>"
|
| 147 |
elif tag == "Make":
|
| 148 |
+
info_metadatos += f"<b>Fabricante:</b> {value}<br>"
|
| 149 |
elif tag == "Model":
|
| 150 |
+
info_metadatos += f"<b>Modelo:</b> {value}<br>"
|
| 151 |
elif tag == "Software":
|
| 152 |
+
info_metadatos += f"<b>Software de edición:</b> {value}<br>"
|
| 153 |
elif tag == "ExifImageWidth":
|
| 154 |
+
info_metadatos += f"<b>Ancho EXIF:</b> {value} píxeles<br>"
|
| 155 |
elif tag == "ExifImageHeight":
|
| 156 |
+
info_metadatos += f"<b>Alto EXIF:</b> {value} píxeles<br>"
|
| 157 |
elif tag == "GPSInfo":
|
| 158 |
+
info_metadatos += "<b>Información GPS:</b><br>"
|
| 159 |
coordenadas = obtener_coordenadas(metadatos)
|
| 160 |
if coordenadas:
|
| 161 |
latitud, longitud = coordenadas
|
| 162 |
+
enlace_google_maps = f"https://www.google.com/maps?q={latitud},{longitud}"
|
| 163 |
+
info_metadatos += f"- <b>Coordenadas:</b> {latitud}, {longitud}<br>"
|
| 164 |
+
info_metadatos += f"- <b>Enlace Maps:</b> <a href='{enlace_google_maps}' target='_blank'>{enlace_google_maps}</a><br>"
|
| 165 |
else:
|
| 166 |
+
info_metadatos += "- No se encontraron coordenadas GPS<br>"
|
|
|
|
|
|
|
| 167 |
else:
|
| 168 |
+
info_metadatos += "No se encontraron metadatos EXIF<br>"
|
| 169 |
+
info_metadatos += "</div>"
|
| 170 |
|
| 171 |
manipulada, razones = analizar_manipulacion(imagen, metadatos)
|
| 172 |
+
info_manipulacion = "<div style='text-align: left;'>"
|
| 173 |
+
info_manipulacion += "<h3>ANÁLISIS DE MANIPULACIÓN</h3>"
|
| 174 |
+
|
| 175 |
if manipulada:
|
| 176 |
+
info_manipulacion += "<span style='color: red; font-weight: bold;'>LA IMAGEN HA SIDO MANIPULADA</span><br>"
|
| 177 |
+
info_manipulacion += "<b>Razones:</b><ul>"
|
| 178 |
for razon in razones:
|
| 179 |
+
info_manipulacion += f"<li>{razon}</li>"
|
| 180 |
+
info_manipulacion += "</ul>"
|
| 181 |
else:
|
| 182 |
+
info_manipulacion += "<span style='color: green; font-weight: bold;'>NO SE DETECTÓ MANIPULACIÓN</span><br>"
|
| 183 |
+
info_manipulacion += "</div>"
|
| 184 |
|
| 185 |
ela_imagen = realizar_ela(imagen)
|
| 186 |
+
|
| 187 |
+
return ela_imagen, info_basica + info_metadatos + info_manipulacion
|
| 188 |
|
| 189 |
+
# CSS personalizado para mejorar el diseño
|
| 190 |
+
css = """
|
| 191 |
+
.centered-image {
|
| 192 |
+
display: block;
|
| 193 |
+
margin-left: auto;
|
| 194 |
+
margin-right: auto;
|
| 195 |
+
max-width: 100%;
|
| 196 |
+
max-height: 400px;
|
| 197 |
+
}
|
| 198 |
+
.panel {
|
| 199 |
+
border: 1px solid #e0e0e0;
|
| 200 |
+
border-radius: 10px;
|
| 201 |
+
padding: 15px;
|
| 202 |
+
margin-bottom: 15px;
|
| 203 |
+
background-color: #f9f9f9;
|
| 204 |
+
}
|
| 205 |
+
.header {
|
| 206 |
+
text-align: center;
|
| 207 |
+
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
|
| 208 |
+
color: white;
|
| 209 |
+
padding: 20px;
|
| 210 |
+
border-radius: 10px;
|
| 211 |
+
margin-bottom: 20px;
|
| 212 |
+
}
|
| 213 |
+
.footer {
|
| 214 |
+
text-align: center;
|
| 215 |
+
padding: 10px;
|
| 216 |
+
color: #666;
|
| 217 |
+
font-size: 0.9em;
|
| 218 |
+
}
|
| 219 |
+
.analysis-btn {
|
| 220 |
+
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%) !important;
|
| 221 |
+
color: white !important;
|
| 222 |
+
border: none !important;
|
| 223 |
+
font-weight: bold !important;
|
| 224 |
+
padding: 12px 24px !important;
|
| 225 |
+
border-radius: 50px !important;
|
| 226 |
+
margin-top: 10px;
|
| 227 |
+
}
|
| 228 |
+
"""
|
| 229 |
|
| 230 |
+
# Interfaz Gradio con diseño mejorado
|
| 231 |
+
with gr.Blocks(css=css) as demo:
|
| 232 |
+
with gr.Column():
|
| 233 |
+
gr.Markdown("""
|
| 234 |
+
<div class="header">
|
| 235 |
+
<h1>Análisis Forense de Imágenes Digitales</h1>
|
| 236 |
+
<p>Detección de manipulación mediante Error Level Analysis (ELA) y análisis de metadatos</p>
|
| 237 |
+
</div>
|
| 238 |
+
""")
|
| 239 |
+
|
| 240 |
+
with gr.Row():
|
| 241 |
+
with gr.Column(scale=1):
|
| 242 |
+
gr.Markdown("### Imagen Original")
|
| 243 |
+
with gr.Column(elem_classes="panel"):
|
| 244 |
+
input_image = gr.Image(
|
| 245 |
+
type="filepath",
|
| 246 |
+
label="",
|
| 247 |
+
show_label=False,
|
| 248 |
+
height=400,
|
| 249 |
+
elem_classes="centered-image"
|
| 250 |
+
)
|
| 251 |
+
analyze_btn = gr.Button("Analizar Imagen", elem_classes="analysis-btn")
|
| 252 |
+
|
| 253 |
+
with gr.Column(scale=1):
|
| 254 |
+
gr.Markdown("### Análisis ELA")
|
| 255 |
+
with gr.Column(elem_classes="panel"):
|
| 256 |
+
output_ela = gr.Image(
|
| 257 |
+
label="",
|
| 258 |
+
show_label=False,
|
| 259 |
+
height=400,
|
| 260 |
+
elem_classes="centered-image",
|
| 261 |
+
interactive=False
|
| 262 |
+
)
|
| 263 |
+
|
| 264 |
+
with gr.Row():
|
| 265 |
+
with gr.Column():
|
| 266 |
+
gr.Markdown("### Resultados del Análisis")
|
| 267 |
+
with gr.Column(elem_classes="panel"):
|
| 268 |
+
metadata_output = gr.HTML()
|
| 269 |
+
|
| 270 |
+
gr.Markdown("""
|
| 271 |
+
<div class="footer">
|
| 272 |
+
<p>Desarrollado por José R. Leonett | Perito Forense Digital | www.forensedigital.gt</p>
|
| 273 |
+
<p>Este sistema analiza imágenes mediante Error Level Analysis (ELA) para detectar posibles manipulaciones</p>
|
| 274 |
+
</div>
|
| 275 |
+
""")
|
| 276 |
+
|
| 277 |
+
analyze_btn.click(
|
| 278 |
+
fn=procesar_imagen,
|
| 279 |
+
inputs=[input_image],
|
| 280 |
+
outputs=[output_ela, metadata_output]
|
| 281 |
+
)
|
| 282 |
|
| 283 |
+
# Iniciar la aplicación
|
| 284 |
+
if __name__ == "__main__":
|
| 285 |
+
demo.launch()
|