221 lines
8.1 KiB
Python
221 lines
8.1 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Analizador de capturas de pantalla del canvas con QR codes de referencia
|
||
Lee QR codes de las capturas para detectar problemas de resolución y posicionamiento.
|
||
|
||
Uso: python analyze_canvas_screenshot.py <imagen.png>
|
||
"""
|
||
|
||
import sys
|
||
import json
|
||
from PIL import Image
|
||
import cv2
|
||
import numpy as np
|
||
from pyzbar import pyzbar
|
||
import argparse
|
||
from datetime import datetime
|
||
|
||
|
||
def analyze_qr_codes(image_path):
|
||
"""Analiza los QR codes en una imagen y extrae información de posición"""
|
||
try:
|
||
# Cargar imagen
|
||
image = Image.open(image_path)
|
||
image_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
|
||
|
||
# Detectar QR codes
|
||
qr_codes = pyzbar.decode(image_cv)
|
||
|
||
if not qr_codes:
|
||
return {
|
||
"status": "error",
|
||
"message": "No se encontraron QR codes en la imagen",
|
||
}
|
||
|
||
results = {
|
||
"status": "success",
|
||
"image_path": image_path,
|
||
"image_size": image.size,
|
||
"analysis_timestamp": datetime.now().isoformat(),
|
||
"qr_codes_found": len(qr_codes),
|
||
"qr_data": [],
|
||
}
|
||
|
||
print(f"📸 Analizando imagen: {image_path}")
|
||
print(f"📐 Tamaño de imagen: {image.size[0]} × {image.size[1]} píxeles")
|
||
print(f"🔍 QR codes encontrados: {len(qr_codes)}")
|
||
print()
|
||
|
||
for i, qr in enumerate(qr_codes):
|
||
try:
|
||
# Decodificar datos JSON del QR
|
||
qr_json = json.loads(qr.data.decode("utf-8"))
|
||
|
||
# Información de posición del QR en la imagen
|
||
qr_rect = qr.rect
|
||
qr_center_pixels = (
|
||
qr_rect.left + qr_rect.width // 2,
|
||
qr_rect.top + qr_rect.height // 2,
|
||
)
|
||
|
||
# Información del QR
|
||
qr_info = {
|
||
"index": i + 1,
|
||
"label": qr_json.get("label", "Unknown"),
|
||
"expected_position_meters": qr_json.get("position", [0, 0]),
|
||
"found_position_pixels": qr_center_pixels,
|
||
"qr_size_pixels": (qr_rect.width, qr_rect.height),
|
||
"original_data": qr_json,
|
||
}
|
||
|
||
results["qr_data"].append(qr_info)
|
||
|
||
print(f"🎯 QR #{i+1}: {qr_info['label']}")
|
||
print(
|
||
f" 📍 Posición esperada: {qr_info['expected_position_meters']} metros"
|
||
)
|
||
print(f" 📱 Encontrado en píxeles: {qr_center_pixels}")
|
||
print(f" 📏 Tamaño QR: {qr_rect.width} × {qr_rect.height} px")
|
||
|
||
# Calcular escala si tenemos la información original
|
||
if "pixels_per_meter" in qr_json:
|
||
original_ppm = qr_json["pixels_per_meter"]
|
||
expected_canvas_size = qr_json.get("canvas_size", [78.31, 54.53])
|
||
|
||
# Calcular escala actual basada en el canvas
|
||
if image.size[0] > 0 and expected_canvas_size[0] > 0:
|
||
current_ppm = image.size[0] / expected_canvas_size[0]
|
||
scale_factor = current_ppm / original_ppm
|
||
|
||
print(f" 📊 Escala original: {original_ppm:.1f} px/m")
|
||
print(f" 📊 Escala actual: {current_ppm:.1f} px/m")
|
||
print(f" 📊 Factor de escala: {scale_factor:.3f}")
|
||
|
||
qr_info["scale_analysis"] = {
|
||
"original_pixels_per_meter": original_ppm,
|
||
"current_pixels_per_meter": current_ppm,
|
||
"scale_factor": scale_factor,
|
||
}
|
||
|
||
print()
|
||
|
||
except (json.JSONDecodeError, KeyError) as e:
|
||
print(f"❌ Error decodificando QR #{i+1}: {e}")
|
||
continue
|
||
|
||
# Análisis de distribución espacial
|
||
if len(results["qr_data"]) >= 2:
|
||
print("📏 Análisis de distribución espacial:")
|
||
analyze_spatial_distribution(results["qr_data"], results)
|
||
|
||
return results
|
||
|
||
except Exception as e:
|
||
return {"status": "error", "message": f"Error analizando imagen: {e}"}
|
||
|
||
|
||
def analyze_spatial_distribution(qr_data, results):
|
||
"""Analiza la distribución espacial de los QR codes para detectar distorsiones"""
|
||
|
||
# Buscar QR codes de esquinas conocidas para análisis de distorsión
|
||
corners = {}
|
||
for qr in qr_data:
|
||
label = qr["label"]
|
||
if label in ["TL", "TR", "BL", "BR"]: # Top-Left, Top-Right, etc.
|
||
corners[label] = qr
|
||
|
||
if len(corners) >= 2:
|
||
print(" 🔍 Detectando distorsión basada en esquinas...")
|
||
|
||
# Calcular distancias esperadas vs reales
|
||
if "TL" in corners and "TR" in corners:
|
||
tl_pos = corners["TL"]["found_position_pixels"]
|
||
tr_pos = corners["TR"]["found_position_pixels"]
|
||
width_pixels = abs(tr_pos[0] - tl_pos[0])
|
||
|
||
# Comparar con ancho esperado del canvas
|
||
if corners["TL"]["original_data"].get("canvas_size"):
|
||
expected_width_meters = corners["TL"]["original_data"]["canvas_size"][0]
|
||
current_ppm = width_pixels / expected_width_meters
|
||
print(
|
||
f" 📐 Ancho detectado: {width_pixels} px = {expected_width_meters}m"
|
||
)
|
||
print(f" 📊 Resolución horizontal: {current_ppm:.1f} px/m")
|
||
|
||
if "TL" in corners and "BL" in corners:
|
||
tl_pos = corners["TL"]["found_position_pixels"]
|
||
bl_pos = corners["BL"]["found_position_pixels"]
|
||
height_pixels = abs(bl_pos[1] - tl_pos[1])
|
||
|
||
if corners["TL"]["original_data"].get("canvas_size"):
|
||
expected_height_meters = corners["TL"]["original_data"]["canvas_size"][
|
||
1
|
||
]
|
||
current_ppm = height_pixels / expected_height_meters
|
||
print(
|
||
f" 📐 Alto detectado: {height_pixels} px = {expected_height_meters}m"
|
||
)
|
||
print(f" 📊 Resolución vertical: {current_ppm:.1f} px/m")
|
||
|
||
# Verificar área de objetos si existe el QR "OBJECTS"
|
||
objects_qr = next((qr for qr in qr_data if qr["label"] == "OBJECTS"), None)
|
||
if objects_qr:
|
||
obj_pos = objects_qr["found_position_pixels"]
|
||
print(f" 🎯 Área de objetos detectada en: {obj_pos} px")
|
||
|
||
# Calcular si está en la posición esperada relativa
|
||
expected_pos = objects_qr["expected_position_meters"]
|
||
print(f" 🎯 Posición esperada: {expected_pos} metros")
|
||
|
||
|
||
def main():
|
||
"""Función principal"""
|
||
parser = argparse.ArgumentParser(
|
||
description="Analiza capturas del canvas con QR de referencia"
|
||
)
|
||
parser.add_argument("image", help="Ruta a la imagen de captura a analizar")
|
||
parser.add_argument("--output", "-o", help="Archivo JSON de salida para resultados")
|
||
parser.add_argument("--verbose", "-v", action="store_true", help="Salida detallada")
|
||
|
||
args = parser.parse_args()
|
||
|
||
# Verificar que existe la imagen
|
||
try:
|
||
with open(args.image, "rb"):
|
||
pass
|
||
except FileNotFoundError:
|
||
print(f"❌ Error: No se encontró la imagen '{args.image}'")
|
||
return 1
|
||
|
||
# Analizar imagen
|
||
results = analyze_qr_codes(args.image)
|
||
|
||
if results["status"] == "error":
|
||
print(f"❌ {results['message']}")
|
||
return 1
|
||
|
||
# Mostrar resumen
|
||
print("=" * 60)
|
||
print("📋 RESUMEN DEL ANÁLISIS:")
|
||
print(f"✅ QR codes válidos encontrados: {results['qr_codes_found']}")
|
||
|
||
if results["qr_data"]:
|
||
labels_found = [qr["label"] for qr in results["qr_data"]]
|
||
print(f"🏷️ Etiquetas detectadas: {', '.join(labels_found)}")
|
||
|
||
# Guardar resultados si se especifica
|
||
if args.output:
|
||
try:
|
||
with open(args.output, "w") as f:
|
||
json.dump(results, f, indent=2)
|
||
print(f"💾 Resultados guardados en: {args.output}")
|
||
except Exception as e:
|
||
print(f"❌ Error guardando resultados: {e}")
|
||
|
||
print("✅ Análisis completado")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
exit(main())
|