Spaces:
Sleeping
Sleeping
File size: 9,124 Bytes
5e1ffaf 331b0b4 5e1ffaf 0ef2874 5e1ffaf 0ef2874 5e1ffaf 0ef2874 5e1ffaf e46b405 5e1ffaf 331b0b4 5e1ffaf ac64bb5 5e1ffaf 331b0b4 5e1ffaf 331b0b4 5e1ffaf 331b0b4 5e1ffaf 331b0b4 25120f0 331b0b4 e46b405 331b0b4 0ef2874 331b0b4 0ef2874 331b0b4 5e1ffaf 331b0b4 5e1ffaf 4df2332 5e1ffaf ee0d844 5e1ffaf ac64bb5 | 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 | """
Sistema de Control de Acceso Vehicular (ANPR + Barrera)
=========================================================
Aplicación standalone. Ejecutar con:
python app.py
Requiere que 'best.pt' (el modelo entrenado) esté en la misma carpeta que este script.
Al correr, abre automáticamente una interfaz web en el navegador donde se sube un video
y se obtiene: video anotado, registro de accesos, y CSV descargable.
"""
import os
import cv2
import torch
import tempfile
import pandas as pd
from difflib import SequenceMatcher
from ultralytics import YOLO
import easyocr
import gradio as gr
# ---------------------------------------------------------------------------
# Carga de modelo y OCR
# ---------------------------------------------------------------------------
MODEL_PATH = 'best.pt'
if not os.path.exists(MODEL_PATH):
raise FileNotFoundError(
"No se encontró 'best.pt' en esta carpeta. "
"Colocá el archivo del modelo entrenado junto a app.py antes de ejecutar."
)
GPU_DISPONIBLE = torch.cuda.is_available()
print(f'GPU disponible: {GPU_DISPONIBLE}')
if not GPU_DISPONIBLE:
print('Corriendo en CPU. El procesamiento será más lento, pero funciona igual.')
model = YOLO(MODEL_PATH) # Ultralytics usa GPU automáticamente si está disponible
reader = easyocr.Reader(['en'], gpu=GPU_DISPONIBLE)
print('Modelo y OCR cargados correctamente.')
# ---------------------------------------------------------------------------
# OCR sobre el recorte de la placa
# ---------------------------------------------------------------------------
def leer_placa(frame, box):
x1, y1, x2, y2 = map(int, box)
recorte = frame[max(0, y1):y2, max(0, x1):x2]
if recorte.size == 0:
return '', 0.0
resultado = reader.readtext(recorte)
if not resultado:
return '', 0.0
texto = ''.join([r[1] for r in resultado]).strip().upper()
confianza = sum(r[2] for r in resultado) / len(resultado)
return texto, confianza
# ---------------------------------------------------------------------------
# Lógica de control de acceso
# ---------------------------------------------------------------------------
def es_similar(a, b, umbral=0.75):
if not a or not b:
return False
return SequenceMatcher(None, a, b).ratio() >= umbral
def verificar_acceso(texto_leido, placas_autorizadas):
for autorizada in placas_autorizadas:
if es_similar(texto_leido, autorizada.strip().upper().replace(' ', '')):
return True, autorizada
return False, None
def cajas_se_superponen(box1, box2, iou_min=0.3):
x1 = max(box1[0], box2[0])
y1 = max(box1[1], box2[1])
x2 = min(box1[2], box2[2])
y2 = min(box1[3], box2[3])
inter = max(0, x2 - x1) * max(0, y2 - y1)
area1 = (box1[2] - box1[0]) * (box1[3] - box1[1])
area2 = (box2[2] - box2[0]) * (box2[3] - box2[1])
union = area1 + area2 - inter
iou = inter / union if union > 0 else 0.0
return iou >= iou_min
def dibujar_barrera(frame, permitido, texto_placa):
h, w = frame.shape[:2]
color = (0, 200, 0) if permitido else (0, 0, 220)
mensaje = (
f'ACCESO PERMITIDO - BARRERA ABIERTA: {texto_placa}'
if permitido else
f'ACCESO DENEGADO - BARRERA CERRADA: {texto_placa}'
)
overlay = frame.copy()
cv2.rectangle(overlay, (0, 0), (w, 50), color, -1)
frame = cv2.addWeighted(overlay, 0.6, frame, 0.4, 0)
cv2.putText(frame, mensaje, (10, 33), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
return frame
# ---------------------------------------------------------------------------
# Procesamiento del video (detección + OCR + decisión estabilizada + registro)
# ---------------------------------------------------------------------------
def procesar_video(video_path, placas_autorizadas_str, confianza=0.25,
ocr_cada_n_frames=20, frames_gracia_desaparicion=15, progress=gr.Progress()):
placas_autorizadas = [p for p in placas_autorizadas_str.split(',') if p.strip()]
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS) or 25
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) or 1
out_path = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False).name
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
writer = cv2.VideoWriter(out_path, fourcc, fps, (w, h))
registro = [] # (placa, timestamp, resultado)
def estado_vacio():
return {'texto': '', 'permitido': False, 'activo': False, 'confianza': 0.0,
'box': None, 'decidido': False, 'frames_sin_deteccion': 0}
estado_actual = estado_vacio()
frame_idx = 0
progress(0, desc=f'Iniciando procesamiento ({total_frames} frames totales)...')
while True:
ret, frame = cap.read()
if not ret:
break
results = model.predict(frame, conf=confianza, verbose=False)[0]
timestamp = round(frame_idx / fps, 1)
vehiculo_presente_este_frame = False
for box in results.boxes.xyxy.cpu().numpy():
x1, y1, x2, y2 = map(int, box)
es_el_que_seguimos = (
estado_actual['box'] is None or
cajas_se_superponen(box, estado_actual['box'])
)
if not es_el_que_seguimos:
# Un vehículo distinto mientras ya se sigue a otro: se ignora hasta que el actual desaparezca.
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 255), 2)
continue
estado_actual['box'] = box
estado_actual['frames_sin_deteccion'] = 0
vehiculo_presente_este_frame = True
if not estado_actual['decidido'] and frame_idx % ocr_cada_n_frames == 0:
texto, conf_lectura = leer_placa(frame, box)
if texto and conf_lectura > estado_actual['confianza']:
permitido, _ = verificar_acceso(texto, placas_autorizadas)
es_primer_registro = not estado_actual['activo']
estado_actual.update({'texto': texto, 'permitido': permitido, 'activo': True, 'confianza': conf_lectura})
if permitido:
estado_actual['decidido'] = True # ya está confirmado: se deja de leer, solo se sigue el tracker
if es_primer_registro:
registro.append((texto, timestamp, 'PERMITIDO' if permitido else 'DENEGADO'))
elif registro:
registro[-1] = (texto, registro[-1][1], 'PERMITIDO' if permitido else 'DENEGADO')
color_caja = (0, 200, 0) if estado_actual['permitido'] else (0, 0, 220)
cv2.rectangle(frame, (x1, y1), (x2, y2), color_caja, 2)
if estado_actual['box'] is not None and not vehiculo_presente_este_frame:
estado_actual['frames_sin_deteccion'] += 1
if estado_actual['frames_sin_deteccion'] >= frames_gracia_desaparicion:
# El vehículo ya no está en cuadro: se libera el seguimiento para poder leer el próximo.
estado_actual = estado_vacio()
if estado_actual['activo']:
frame = dibujar_barrera(frame, estado_actual['permitido'], estado_actual['texto'])
writer.write(frame)
frame_idx += 1
if frame_idx % 50 == 0 or frame_idx == total_frames - 1:
progress(min(frame_idx / total_frames, 1.0), desc=f'Procesando frame {frame_idx}/{total_frames}')
cap.release()
writer.release()
df = pd.DataFrame(registro, columns=['Placa detectada', 'Timestamp (s)', 'Resultado'])
csv_path = tempfile.NamedTemporaryFile(suffix='.csv', delete=False).name
df.to_csv(csv_path, index=False)
n_permitidos = sum(1 for r in registro if r[2] == 'PERMITIDO')
n_denegados = len(registro) - n_permitidos
resumen = f'Vehículos detectados: {len(registro)} | Acceso permitido: {n_permitidos} | Acceso denegado: {n_denegados}'
return out_path, df, csv_path, resumen
# ---------------------------------------------------------------------------
# Interfaz web (Gradio)
# ---------------------------------------------------------------------------
demo = gr.Interface(
fn=procesar_video,
inputs=[
gr.Video(label='Subí un video de vehículos'),
gr.Textbox(label='Placas autorizadas (separadas por coma)', placeholder='ABC123, XYZ789', value='TS07JS9670'),
gr.Slider(0.1, 0.9, value=0.25, step=0.05, label='Umbral de confianza de detección')
],
outputs=[
gr.Video(label='Video con resultado de acceso'),
gr.Dataframe(label='Registro de accesos'),
gr.File(label='Descargar registro (CSV)'),
gr.Textbox(label='Resumen')
],
title='Sistema de Control de Acceso Vehicular (ANPR)',
description=(
'Detección de placas (YOLOv8n) + lectura OCR + verificación contra lista de '
'autorizados, simulando la apertura/cierre de una barrera de estacionamiento.'
)
)
if __name__ == '__main__':
demo.launch() |