Compare commits
3 Commits
main
...
Integracio
| Author | SHA1 | Date | |
|---|---|---|---|
| 349e5598a0 | |||
| c1e94dd96a | |||
| 1db19fbb84 |
12
README.md
12
README.md
@@ -69,7 +69,17 @@ Mantén los contenedores de Docker corriendo (Backend, DB, Frontend) y ejecuta e
|
|||||||
|
|
||||||
El script pedirá permisos de cámara. Una vez otorgados, verás el video en el Dashboard.
|
El script pedirá permisos de cámara. Una vez otorgados, verás el video en el Dashboard.
|
||||||
|
|
||||||
#### Opción B: Ejecución Full Docker (Linux / Raspberry Pi)
|
|
||||||
|
#### Opción B: Ejecución en Windows (Híbrida)
|
||||||
|
|
||||||
|
1. Asegúrate de tener **Docker Desktop** corriendo.
|
||||||
|
2. Abre la carpeta `alpr-service`.
|
||||||
|
3. Haz doble clic en el archivo `run_windows.bat`.
|
||||||
|
* Este script instalará las dependencias automáticamente.
|
||||||
|
* Configurará las variables de entorno.
|
||||||
|
* Iniciará el reconocimiento de patentes.
|
||||||
|
|
||||||
|
#### Opción C: Ejecución Full Docker (Linux / Raspberry Pi)
|
||||||
|
|
||||||
En sistemas Linux nativos donde se pueden mapear dispositivos (ej. `/dev/video0`), simplemente descomenta la sección `devices` en `docker-compose.yml` y todo correrá dentro de Docker.
|
En sistemas Linux nativos donde se pueden mapear dispositivos (ej. `/dev/video0`), simplemente descomenta la sección `devices` en `docker-compose.yml` y todo correrá dentro de Docker.
|
||||||
|
|
||||||
|
|||||||
BIN
alpr-service/best.pt
Normal file
BIN
alpr-service/best.pt
Normal file
Binary file not shown.
@@ -5,21 +5,26 @@ import os
|
|||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import re
|
||||||
from flask import Flask, Response
|
from flask import Flask, Response
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
|
from ultralytics import YOLO
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
BACKEND_URL = os.environ.get('BACKEND_URL', 'http://localhost:3000')
|
BACKEND_URL = os.environ.get('BACKEND_URL', 'http://localhost:3000')
|
||||||
CAMERA_ID = 0
|
CAMERA_ID = 0
|
||||||
PROCESS_INTERVAL = 2.0 # OCR every 2 seconds
|
PROCESS_INTERVAL = 0.5 # Faster processing with YOLO (it's efficient)
|
||||||
CONFIDENCE_THRESHOLD = 0.4
|
CONFIDENCE_THRESHOLD = 0.4
|
||||||
|
MODEL_PATH = 'best.pt' # Expecting the model here
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CORS(app)
|
CORS(app)
|
||||||
|
|
||||||
# Global variables (using simple globals for PoC)
|
# Global variables
|
||||||
outputFrame = None
|
outputFrame = None
|
||||||
lock = threading.Lock()
|
lock = threading.Lock()
|
||||||
|
# Store latest detections for visualization
|
||||||
|
latest_detections = []
|
||||||
|
|
||||||
def send_plate(plate_number):
|
def send_plate(plate_number):
|
||||||
try:
|
try:
|
||||||
@@ -31,14 +36,24 @@ def send_plate(plate_number):
|
|||||||
print(f"Error sending plate: {e}")
|
print(f"Error sending plate: {e}")
|
||||||
|
|
||||||
def alpr_loop():
|
def alpr_loop():
|
||||||
global outputFrame, lock
|
global outputFrame, lock, latest_detections
|
||||||
|
|
||||||
print("Initializing EasyOCR...")
|
print("Initializing EasyOCR...")
|
||||||
reader = easyocr.Reader(['en'], gpu=False)
|
reader = easyocr.Reader(['en'], gpu=False)
|
||||||
print("EasyOCR initialized.")
|
print("EasyOCR initialized.")
|
||||||
|
|
||||||
|
# Load YOLO Model
|
||||||
|
print(f"Loading YOLO model from {MODEL_PATH}...")
|
||||||
|
try:
|
||||||
|
model = YOLO(MODEL_PATH)
|
||||||
|
print("YOLO model loaded successfully!")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading YOLO model: {e}")
|
||||||
|
print("CRITICAL: Please place the 'best.pt' file in the alpr-service directory.")
|
||||||
|
return
|
||||||
|
|
||||||
cap = cv2.VideoCapture(CAMERA_ID)
|
cap = cv2.VideoCapture(CAMERA_ID)
|
||||||
time.sleep(2.0) # Warmup
|
time.sleep(2.0)
|
||||||
|
|
||||||
if not cap.isOpened():
|
if not cap.isOpened():
|
||||||
print("Error: Could not open video device.")
|
print("Error: Could not open video device.")
|
||||||
@@ -53,62 +68,70 @@ def alpr_loop():
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Resize for performance and streaming bandwidth
|
# Resize for performance
|
||||||
frame = cv2.resize(frame, (640, 480))
|
frame = cv2.resize(frame, (640, 480))
|
||||||
|
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
|
|
||||||
# OCR Processing
|
# Detection Processing
|
||||||
if current_time - last_process_time > PROCESS_INTERVAL:
|
if current_time - last_process_time > PROCESS_INTERVAL:
|
||||||
last_process_time = current_time
|
last_process_time = current_time
|
||||||
threading.Thread(target=perform_ocr, args=(reader, frame.copy())).start()
|
|
||||||
|
|
||||||
# Update output frame
|
# Run YOLO Inference
|
||||||
|
results = model(frame, verbose=False)
|
||||||
|
|
||||||
|
detections = []
|
||||||
|
|
||||||
|
for r in results:
|
||||||
|
boxes = r.boxes
|
||||||
|
for box in boxes:
|
||||||
|
# Bounding Box
|
||||||
|
x1, y1, x2, y2 = box.xyxy[0]
|
||||||
|
x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
|
||||||
|
conf = float(box.conf[0])
|
||||||
|
|
||||||
|
if conf > 0.5: # Valid plate detection
|
||||||
|
# Visualization data
|
||||||
|
detections.append((x1, y1, x2, y2, conf))
|
||||||
|
|
||||||
|
# Crop Plate
|
||||||
|
plate_img = frame[y1:y2, x1:x2]
|
||||||
|
|
||||||
|
# Run OCR on Crop
|
||||||
|
try:
|
||||||
|
ocr_results = reader.readtext(plate_img)
|
||||||
|
for (_, text, prob) in ocr_results:
|
||||||
|
if prob > CONFIDENCE_THRESHOLD:
|
||||||
|
clean_text = ''.join(e for e in text if e.isalnum()).upper()
|
||||||
|
validate_and_send(clean_text)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"OCR Error on crop: {e}")
|
||||||
|
|
||||||
|
with lock:
|
||||||
|
latest_detections = detections
|
||||||
|
|
||||||
|
# Draw Detections on Frame for Stream
|
||||||
|
display_frame = frame.copy()
|
||||||
with lock:
|
with lock:
|
||||||
outputFrame = frame.copy()
|
for (x1, y1, x2, y2, conf) in latest_detections:
|
||||||
|
cv2.rectangle(display_frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
|
||||||
|
cv2.putText(display_frame, f"Plate {conf:.2f}", (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
|
||||||
|
|
||||||
|
outputFrame = display_frame
|
||||||
|
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
|
|
||||||
import re
|
def validate_and_send(text):
|
||||||
|
# Chilean Plate Regex Patterns
|
||||||
|
is_valid = False
|
||||||
|
if re.match(r'^[A-Z]{4}\d{2}$', text): # BBBB11
|
||||||
|
is_valid = True
|
||||||
|
elif re.match(r'^[A-Z]{2}\d{4}$', text): # BB1111
|
||||||
|
is_valid = True
|
||||||
|
|
||||||
# ... (imports)
|
if is_valid:
|
||||||
|
print(f"Detected Valid Plate: {text}")
|
||||||
def perform_ocr(reader, img):
|
send_plate(text)
|
||||||
try:
|
|
||||||
results = reader.readtext(img)
|
|
||||||
for (bbox, text, prob) in results:
|
|
||||||
if prob > CONFIDENCE_THRESHOLD:
|
|
||||||
# Clean text: keep alphanumerics
|
|
||||||
clean_text = ''.join(e for e in text if e.isalnum()).upper()
|
|
||||||
|
|
||||||
# Chilean Plate Regex Patterns
|
|
||||||
# 1. New format: 4 letters + 2 numbers (e.g., BB-BB-10) -> ^[A-Z]{4}\d{2}$
|
|
||||||
# 2. Old format: 2 letters + 4 numbers (e.g., AA-1000) -> ^[A-Z]{2}\d{4}$
|
|
||||||
# 3. Moto format (common): 3 letters + 2 numbers or 3 numbers
|
|
||||||
|
|
||||||
# General robust filter for Chile:
|
|
||||||
# - Length 6 (standard)
|
|
||||||
# - Must match specific patterns
|
|
||||||
|
|
||||||
is_valid = False
|
|
||||||
|
|
||||||
# Check Pattern 1: BBBB11 (4 Letters, 2 Digits)
|
|
||||||
if re.match(r'^[A-Z]{4}\d{2}$', clean_text):
|
|
||||||
is_valid = True
|
|
||||||
# Check Pattern 2: BB1111 (2 Letters, 4 Digits)
|
|
||||||
elif re.match(r'^[A-Z]{2}\d{4}$', clean_text):
|
|
||||||
is_valid = True
|
|
||||||
|
|
||||||
if is_valid:
|
|
||||||
print(f"Detected Valid Plate: {clean_text}")
|
|
||||||
send_plate(clean_text)
|
|
||||||
else:
|
|
||||||
# Optional logging for debugging ignored text
|
|
||||||
# print(f"Ignored: {clean_text} (Format mismatch)")
|
|
||||||
pass
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"OCR Error: {e}")
|
|
||||||
|
|
||||||
def generate():
|
def generate():
|
||||||
global outputFrame, lock
|
global outputFrame, lock
|
||||||
@@ -128,12 +151,9 @@ def video_feed():
|
|||||||
return Response(generate(), mimetype = "multipart/x-mixed-replace; boundary=frame")
|
return Response(generate(), mimetype = "multipart/x-mixed-replace; boundary=frame")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Start capturing thread
|
|
||||||
t = threading.Thread(target=alpr_loop)
|
t = threading.Thread(target=alpr_loop)
|
||||||
t.daemon = True
|
t.daemon = True
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
# Start Flask Server
|
|
||||||
print("Starting Video Stream on port 5001...")
|
print("Starting Video Stream on port 5001...")
|
||||||
# Using 0.0.0.0 to allow access if needed, though mostly used locally
|
|
||||||
app.run(host="0.0.0.0", port=5001, debug=False, threaded=True, use_reloader=False)
|
app.run(host="0.0.0.0", port=5001, debug=False, threaded=True, use_reloader=False)
|
||||||
|
|||||||
@@ -3,3 +3,5 @@ easyocr
|
|||||||
requests
|
requests
|
||||||
numpy
|
numpy
|
||||||
flask
|
flask
|
||||||
|
flask-cors
|
||||||
|
ultralytics
|
||||||
|
|||||||
33
alpr-service/run_windows.bat
Normal file
33
alpr-service/run_windows.bat
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
@echo off
|
||||||
|
TITLE ControlPatente AI - ALPR Service
|
||||||
|
|
||||||
|
echo ========================================================
|
||||||
|
echo Inicializando Servicio de Reconocimiento de Patentes
|
||||||
|
echo Windows Launcher
|
||||||
|
echo ========================================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
cd /d "%~dp0"
|
||||||
|
|
||||||
|
echo [1/3] Verificando entorno Python...
|
||||||
|
python --version >nul 2>&1
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo ERROR: Python no esta instalado o no esta en el PATH.
|
||||||
|
echo Por favor instala Python desde https://python.org
|
||||||
|
pause
|
||||||
|
exit /b
|
||||||
|
)
|
||||||
|
|
||||||
|
echo [2/3] Instalando dependencias (si faltan)...
|
||||||
|
pip install -r requirements.txt
|
||||||
|
pip install flask flask-cors ultralytics
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo [3/3] Iniciando Servicio...
|
||||||
|
echo.
|
||||||
|
|
||||||
|
set BACKEND_URL=http://localhost:3000
|
||||||
|
|
||||||
|
python main.py
|
||||||
|
|
||||||
|
pause
|
||||||
Reference in New Issue
Block a user