first commit

This commit is contained in:
2025-12-22 22:40:38 -03:00
commit 309d9eefba
25 changed files with 902 additions and 0 deletions

24
alpr-service/Dockerfile Normal file
View File

@@ -0,0 +1,24 @@
FROM python:3.9-slim
WORKDIR /app
RUN apt-get update && apt-get install -y \
libgl1 \
libglib2.0-0 \
libsm6 \
libxext6 \
libxrender-dev \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements (we will create this file next)
COPY requirements.txt .
# Install Python dependencies
# Using --no-cache-dir to keep image small
RUN pip install --no-cache-dir -r requirements.txt
# Copy source code
COPY . .
# Run the application
CMD ["python", "main.py"]

139
alpr-service/main.py Normal file
View File

@@ -0,0 +1,139 @@
import cv2
import easyocr
import requests
import os
import time
import threading
import numpy as np
from flask import Flask, Response
from flask_cors import CORS
# Configuration
BACKEND_URL = os.environ.get('BACKEND_URL', 'http://localhost:3000')
CAMERA_ID = 0
PROCESS_INTERVAL = 2.0 # OCR every 2 seconds
CONFIDENCE_THRESHOLD = 0.4
app = Flask(__name__)
CORS(app)
# Global variables (using simple globals for PoC)
outputFrame = None
lock = threading.Lock()
def send_plate(plate_number):
try:
url = f"{BACKEND_URL}/api/detect"
payload = {'plate_number': plate_number}
print(f"Sending plate: {plate_number} to {url}")
requests.post(url, json=payload, timeout=2)
except Exception as e:
print(f"Error sending plate: {e}")
def alpr_loop():
global outputFrame, lock
print("Initializing EasyOCR...")
reader = easyocr.Reader(['en'], gpu=False)
print("EasyOCR initialized.")
cap = cv2.VideoCapture(CAMERA_ID)
time.sleep(2.0) # Warmup
if not cap.isOpened():
print("Error: Could not open video device.")
return
last_process_time = 0
while True:
ret, frame = cap.read()
if not ret:
print("Failed to grab frame")
time.sleep(1)
continue
# Resize for performance and streaming bandwidth
frame = cv2.resize(frame, (640, 480))
current_time = time.time()
# OCR Processing
if current_time - last_process_time > PROCESS_INTERVAL:
last_process_time = current_time
threading.Thread(target=perform_ocr, args=(reader, frame.copy())).start()
# Update output frame
with lock:
outputFrame = frame.copy()
time.sleep(0.01)
import re
# ... (imports)
def perform_ocr(reader, img):
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():
global outputFrame, lock
while True:
with lock:
if outputFrame is None:
continue
(flag, encodedImage) = cv2.imencode(".jpg", outputFrame)
if not flag:
continue
yield(b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' +
bytearray(encodedImage) + b'\r\n')
@app.route("/video_feed")
def video_feed():
return Response(generate(), mimetype = "multipart/x-mixed-replace; boundary=frame")
if __name__ == "__main__":
# Start capturing thread
t = threading.Thread(target=alpr_loop)
t.daemon = True
t.start()
# Start Flask Server
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)

View File

@@ -0,0 +1,5 @@
opencv-python-headless
easyocr
requests
numpy
flask