fras-backend / services /camera.py
pranaykumar2005's picture
feat: add semester 5 subjects database seeding and search by email
9c25657
Raw
History Blame Contribute Delete
6.19 kB
import cv2
import base64
import asyncio
import threading
import logging
from services.face_engine import face_engine
from services.recognizer import embedding_store
logger = logging.getLogger(__name__)
class CameraStreamer:
def __init__(self):
self.cap = None
self.is_running = False
self.thread = None
self._event_queue = None # queue to pass events to websocket
self.active_subject_id = None
# Frame sampling control
self.frame_count = 0
self.process_every_n = 20
self.last_faces = [] # Store last known faces for smooth rendering
@property
def event_queue(self):
if self._event_queue is None:
# Lazily initialize queue so it binds to the correct uvicorn event loop
self._event_queue = asyncio.Queue()
return self._event_queue
def start(self, subject_id=None):
if self.is_running:
return
self.active_subject_id = subject_id
# Capture the main event loop to safely put items in the queue from the thread
try:
self.main_loop = asyncio.get_running_loop()
except RuntimeError:
self.main_loop = asyncio.get_event_loop()
# Open default webcam
self.cap = cv2.VideoCapture(0)
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
if not self.cap.isOpened():
logger.error("Failed to open webcam.")
return False
self.is_running = True
self.thread = threading.Thread(target=self._capture_loop, daemon=True)
self.thread.start()
logger.info(f"Camera started for subject {subject_id}")
return True
def stop(self):
self.is_running = False
if self.thread:
self.thread.join(timeout=2.0)
if self.cap:
self.cap.release()
logger.info("Camera stopped")
def _capture_loop(self):
while self.is_running:
try:
ret, frame = self.cap.read()
if not ret:
continue
self.frame_count += 1
if self.frame_count % self.process_every_n == 0:
# Run detection and recognition
detected_faces = face_engine.detect_faces(frame)
self.last_faces = [] # Reset last faces
for face in detected_faces:
# Check liveness
if face_engine.check_liveness(face['crop']):
embedding = face_engine.get_embedding(face['crop'])
try:
roll_no, confidence = face_engine.identify(embedding)
except Exception as e:
logger.error(f"Identify error: {e}")
roll_no, confidence = "unknown", 0.0
if roll_no != "unknown":
# Queue identified event
event = {
"type": "detected",
"roll_no": roll_no,
"name": "Student", # Can be enriched by DB lookup
"confidence": confidence,
"subject_id": self.active_subject_id
}
# Send event to queue using threadsafe method
self.main_loop.call_soon_threadsafe(self.event_queue.put_nowait, event)
self.last_faces.append({
"bbox": face['bbox'],
"label": f"{roll_no} ({int(confidence*100)}%)",
"color": (0, 255, 0)
})
else:
event = {
"type": "unknown",
"confidence": confidence
}
self.main_loop.call_soon_threadsafe(self.event_queue.put_nowait, event)
self.last_faces.append({
"bbox": face['bbox'],
"label": "Unknown",
"color": (0, 0, 255)
})
else:
# Failed liveness check
self.last_faces.append({
"bbox": face['bbox'],
"label": "Spoof Detected",
"color": (0, 165, 255)
})
# Draw last known faces
for f in self.last_faces:
x1, y1, x2, y2 = f['bbox']
cv2.rectangle(frame, (x1, y1), (x2, y2), f['color'], 2)
cv2.putText(frame, f['label'], (x1, max(0, y1 - 10)),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, f['color'], 2)
# Encode frame to JPEG
_, buffer = cv2.imencode('.jpg', frame)
b64_frame = base64.b64encode(buffer).decode('utf-8')
# Send frame event
frame_event = {
"type": "frame",
"data": b64_frame
}
self.main_loop.call_soon_threadsafe(self.event_queue.put_nowait, frame_event)
except Exception as e:
logger.error(f"Error in capture loop: {e}")
import time
time.sleep(0.5) # Prevent spamming logs if failing continuously
# Cleanup handled in stop()
camera_streamer = CameraStreamer()