first commit
This commit is contained in:
24
alpr-service/Dockerfile
Normal file
24
alpr-service/Dockerfile
Normal 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
139
alpr-service/main.py
Normal 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)
|
||||
5
alpr-service/requirements.txt
Normal file
5
alpr-service/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
opencv-python-headless
|
||||
easyocr
|
||||
requests
|
||||
numpy
|
||||
flask
|
||||
Reference in New Issue
Block a user