Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -27,6 +27,8 @@ class AttendanceSystem:
|
|
| 27 |
self.recognition_thread = None
|
| 28 |
self.last_recognition_time = {}
|
| 29 |
self.recognition_cooldown = 5 # seconds between recognitions for same person
|
|
|
|
|
|
|
| 30 |
|
| 31 |
# Create directories for data storage
|
| 32 |
os.makedirs("data", exist_ok=True)
|
|
@@ -274,6 +276,9 @@ class AttendanceSystem:
|
|
| 274 |
if self.is_streaming:
|
| 275 |
return "β οΈ Video stream is already running!"
|
| 276 |
|
|
|
|
|
|
|
|
|
|
| 277 |
self.video_capture = cv2.VideoCapture(camera_source)
|
| 278 |
if not self.video_capture.isOpened():
|
| 279 |
return "β Could not open camera/video source!"
|
|
@@ -302,15 +307,62 @@ class AttendanceSystem:
|
|
| 302 |
self.recognition_thread.daemon = True
|
| 303 |
self.recognition_thread.start()
|
| 304 |
|
| 305 |
-
return "β
|
| 306 |
|
| 307 |
except Exception as e:
|
| 308 |
return f"β Error starting video stream: {e}"
|
| 309 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
def stop_video_stream(self):
|
| 311 |
-
"""Stop video streaming"""
|
| 312 |
try:
|
| 313 |
self.is_streaming = False
|
|
|
|
| 314 |
|
| 315 |
if self.video_capture:
|
| 316 |
self.video_capture.release()
|
|
@@ -326,10 +378,10 @@ class AttendanceSystem:
|
|
| 326 |
except queue.Empty:
|
| 327 |
break
|
| 328 |
|
| 329 |
-
return "β
Video stream stopped successfully!"
|
| 330 |
|
| 331 |
except Exception as e:
|
| 332 |
-
return f"β Error stopping video
|
| 333 |
|
| 334 |
def get_current_frame(self):
|
| 335 |
"""Get current frame for display"""
|
|
@@ -434,7 +486,7 @@ attendance_system = AttendanceSystem()
|
|
| 434 |
|
| 435 |
def create_interface():
|
| 436 |
with gr.Blocks(
|
| 437 |
-
title="π― Advanced Attendance System with
|
| 438 |
theme=gr.themes.Soft(),
|
| 439 |
css="""
|
| 440 |
.gradio-container {
|
|
@@ -448,57 +500,72 @@ def create_interface():
|
|
| 448 |
border-radius: 5px;
|
| 449 |
margin: 5px 0;
|
| 450 |
}
|
|
|
|
|
|
|
|
|
|
| 451 |
"""
|
| 452 |
) as demo:
|
| 453 |
|
| 454 |
gr.Markdown(
|
| 455 |
"""
|
| 456 |
-
# π― Advanced Attendance System with
|
| 457 |
|
| 458 |
-
**Comprehensive facial recognition system with
|
| 459 |
|
| 460 |
## π **Key Features:**
|
| 461 |
-
- **π₯ Live
|
|
|
|
| 462 |
- **π€ Automatic Worker Registration** - Auto-register unknown faces with unique IDs
|
| 463 |
- **π€ Manual Registration** - Register workers manually with photos
|
| 464 |
- **π
24-Hour Attendance Rule** - One attendance mark per worker per day
|
| 465 |
- **π Advanced Analytics** - Detailed reports and data export
|
| 466 |
-
- **πΎ Persistent Data Storage** - All data saved locally in `/data` folder
|
| 467 |
-
|
| 468 |
-
## π **Data Storage Location:**
|
| 469 |
-
- **Worker Database:** `/data/workers.pkl`
|
| 470 |
-
- **Attendance Records:** `/data/attendance.json`
|
| 471 |
-
- **Face Images:** `/data/faces/` folder
|
| 472 |
"""
|
| 473 |
)
|
| 474 |
|
| 475 |
with gr.Tabs():
|
| 476 |
-
#
|
| 477 |
-
with gr.Tab("π₯
|
| 478 |
-
gr.Markdown("###
|
| 479 |
|
| 480 |
with gr.Row():
|
| 481 |
with gr.Column(scale=1):
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 487 |
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
)
|
| 494 |
-
stop_stream_btn = gr.Button(
|
| 495 |
-
"βΉοΈ Stop Stream",
|
| 496 |
-
variant="secondary",
|
| 497 |
-
size="lg"
|
| 498 |
-
)
|
| 499 |
|
| 500 |
stream_status = gr.Textbox(
|
| 501 |
-
label="
|
| 502 |
value="Ready to start...",
|
| 503 |
interactive=False,
|
| 504 |
lines=2
|
|
@@ -507,10 +574,9 @@ def create_interface():
|
|
| 507 |
gr.Markdown(
|
| 508 |
"""
|
| 509 |
**π Instructions:**
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
4. New workers get auto-assigned IDs (W0001, W0002, etc.)
|
| 514 |
|
| 515 |
**π¨ Color Coding:**
|
| 516 |
- π’ **Green:** Known worker (attendance marked)
|
|
@@ -520,6 +586,12 @@ def create_interface():
|
|
| 520 |
)
|
| 521 |
|
| 522 |
with gr.Column(scale=1):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 523 |
live_attendance_display = gr.Markdown(
|
| 524 |
value=attendance_system.get_today_attendance(),
|
| 525 |
label="Live Attendance Updates"
|
|
@@ -603,55 +675,6 @@ def create_interface():
|
|
| 603 |
value="Select date range and click 'Generate Report' to view attendance analytics.",
|
| 604 |
label="Attendance Report"
|
| 605 |
)
|
| 606 |
-
|
| 607 |
-
# System Info Tab
|
| 608 |
-
with gr.Tab("βΉοΈ System Information", elem_classes="tab-nav"):
|
| 609 |
-
gr.Markdown(
|
| 610 |
-
"""
|
| 611 |
-
## π System Guide
|
| 612 |
-
|
| 613 |
-
### π₯ Live Recognition System
|
| 614 |
-
- **Camera Setup:** Use camera index (0, 1, 2...) or RTSP URL for IP cameras
|
| 615 |
-
- **Auto Registration:** Unknown faces automatically get worker IDs (W0001, W0002...)
|
| 616 |
-
- **24-Hour Rule:** Each worker can only be marked present once per day
|
| 617 |
-
- **Real-time Processing:** Continuous face detection and recognition
|
| 618 |
-
|
| 619 |
-
### π€ Manual Registration
|
| 620 |
-
- Upload clear, front-facing photos for best results
|
| 621 |
-
- One face per image for registration
|
| 622 |
-
- Workers get unique IDs automatically assigned
|
| 623 |
-
|
| 624 |
-
### π Data Storage Structure
|
| 625 |
-
```
|
| 626 |
-
/data/
|
| 627 |
-
βββ workers.pkl # Worker database (embeddings, names, IDs)
|
| 628 |
-
βββ attendance.json # All attendance records
|
| 629 |
-
βββ faces/ # Saved face images
|
| 630 |
-
βββ W0001_John_Doe.jpg
|
| 631 |
-
βββ W0002_Jane_Smith.jpg
|
| 632 |
-
βββ ...
|
| 633 |
-
```
|
| 634 |
-
|
| 635 |
-
### π§ Technical Features
|
| 636 |
-
- **Face Recognition:** Uses DeepFace with Facenet embeddings
|
| 637 |
-
- **Distance Threshold:** 10 for face matching accuracy
|
| 638 |
-
- **Threading:** Separate threads for video processing and UI
|
| 639 |
-
- **Queue Management:** Efficient frame processing with queue system
|
| 640 |
-
- **Error Handling:** Robust error handling and recovery
|
| 641 |
-
|
| 642 |
-
### π¨ Troubleshooting
|
| 643 |
-
- **Camera Issues:** Check camera permissions and connections
|
| 644 |
-
- **Poor Recognition:** Ensure good lighting and clear face visibility
|
| 645 |
-
- **Performance:** Reduce video resolution for better performance
|
| 646 |
-
- **Storage:** Check disk space for face image storage
|
| 647 |
-
|
| 648 |
-
### π Privacy & Security
|
| 649 |
-
- All data stored locally in `/data` folder
|
| 650 |
-
- No external API calls or data transmission
|
| 651 |
-
- Face images saved securely with worker IDs
|
| 652 |
-
- Attendance records in JSON format for easy backup
|
| 653 |
-
"""
|
| 654 |
-
)
|
| 655 |
|
| 656 |
# Event handlers
|
| 657 |
start_stream_btn.click(
|
|
@@ -660,6 +683,12 @@ def create_interface():
|
|
| 660 |
outputs=[stream_status]
|
| 661 |
)
|
| 662 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 663 |
stop_stream_btn.click(
|
| 664 |
fn=attendance_system.stop_video_stream,
|
| 665 |
outputs=[stream_status]
|
|
@@ -694,8 +723,17 @@ def create_interface():
|
|
| 694 |
outputs=[export_status, export_file]
|
| 695 |
)
|
| 696 |
|
| 697 |
-
#
|
| 698 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 699 |
|
| 700 |
return demo
|
| 701 |
|
|
|
|
| 27 |
self.recognition_thread = None
|
| 28 |
self.last_recognition_time = {}
|
| 29 |
self.recognition_cooldown = 5 # seconds between recognitions for same person
|
| 30 |
+
self.video_file_path = None
|
| 31 |
+
self.video_processing = False
|
| 32 |
|
| 33 |
# Create directories for data storage
|
| 34 |
os.makedirs("data", exist_ok=True)
|
|
|
|
| 276 |
if self.is_streaming:
|
| 277 |
return "β οΈ Video stream is already running!"
|
| 278 |
|
| 279 |
+
# Clear previous video file if switching from file to camera
|
| 280 |
+
self.video_file_path = None
|
| 281 |
+
|
| 282 |
self.video_capture = cv2.VideoCapture(camera_source)
|
| 283 |
if not self.video_capture.isOpened():
|
| 284 |
return "β Could not open camera/video source!"
|
|
|
|
| 307 |
self.recognition_thread.daemon = True
|
| 308 |
self.recognition_thread.start()
|
| 309 |
|
| 310 |
+
return "β
Live camera stream started successfully!"
|
| 311 |
|
| 312 |
except Exception as e:
|
| 313 |
return f"β Error starting video stream: {e}"
|
| 314 |
|
| 315 |
+
def process_uploaded_video(self, video_path):
|
| 316 |
+
"""Process an uploaded video file for face recognition"""
|
| 317 |
+
try:
|
| 318 |
+
if self.is_streaming:
|
| 319 |
+
return "β οΈ Please stop current stream before processing a video file!"
|
| 320 |
+
|
| 321 |
+
if not os.path.exists(video_path):
|
| 322 |
+
return "β Video file not found!"
|
| 323 |
+
|
| 324 |
+
self.video_file_path = video_path
|
| 325 |
+
self.video_processing = True
|
| 326 |
+
|
| 327 |
+
def video_processing_loop():
|
| 328 |
+
cap = cv2.VideoCapture(video_path)
|
| 329 |
+
fps = cap.get(cv2.CAP_PROP_FPS)
|
| 330 |
+
frame_delay = 1.0 / fps if fps > 0 else 0.03
|
| 331 |
+
|
| 332 |
+
while self.video_processing and cap.isOpened():
|
| 333 |
+
ret, frame = cap.read()
|
| 334 |
+
if not ret:
|
| 335 |
+
break
|
| 336 |
+
|
| 337 |
+
# Process frame for face recognition
|
| 338 |
+
processed_frame = self.process_video_frame(frame)
|
| 339 |
+
|
| 340 |
+
# Add to queue for display
|
| 341 |
+
if not self.frame_queue.full():
|
| 342 |
+
try:
|
| 343 |
+
self.frame_queue.put_nowait(processed_frame)
|
| 344 |
+
except queue.Full:
|
| 345 |
+
pass
|
| 346 |
+
|
| 347 |
+
time.sleep(frame_delay)
|
| 348 |
+
|
| 349 |
+
cap.release()
|
| 350 |
+
self.video_processing = False
|
| 351 |
+
|
| 352 |
+
self.recognition_thread = threading.Thread(target=video_processing_loop)
|
| 353 |
+
self.recognition_thread.daemon = True
|
| 354 |
+
self.recognition_thread.start()
|
| 355 |
+
|
| 356 |
+
return f"β
Video processing started successfully! ({os.path.basename(video_path)})"
|
| 357 |
+
|
| 358 |
+
except Exception as e:
|
| 359 |
+
return f"β Error processing video: {e}"
|
| 360 |
+
|
| 361 |
def stop_video_stream(self):
|
| 362 |
+
"""Stop video streaming or processing"""
|
| 363 |
try:
|
| 364 |
self.is_streaming = False
|
| 365 |
+
self.video_processing = False
|
| 366 |
|
| 367 |
if self.video_capture:
|
| 368 |
self.video_capture.release()
|
|
|
|
| 378 |
except queue.Empty:
|
| 379 |
break
|
| 380 |
|
| 381 |
+
return "β
Video stream/processing stopped successfully!"
|
| 382 |
|
| 383 |
except Exception as e:
|
| 384 |
+
return f"β Error stopping video: {e}"
|
| 385 |
|
| 386 |
def get_current_frame(self):
|
| 387 |
"""Get current frame for display"""
|
|
|
|
| 486 |
|
| 487 |
def create_interface():
|
| 488 |
with gr.Blocks(
|
| 489 |
+
title="π― Advanced Attendance System with Video Recognition",
|
| 490 |
theme=gr.themes.Soft(),
|
| 491 |
css="""
|
| 492 |
.gradio-container {
|
|
|
|
| 500 |
border-radius: 5px;
|
| 501 |
margin: 5px 0;
|
| 502 |
}
|
| 503 |
+
.video-option-tabs {
|
| 504 |
+
margin-bottom: 15px;
|
| 505 |
+
}
|
| 506 |
"""
|
| 507 |
) as demo:
|
| 508 |
|
| 509 |
gr.Markdown(
|
| 510 |
"""
|
| 511 |
+
# π― Advanced Attendance System with Face Recognition
|
| 512 |
|
| 513 |
+
**Comprehensive facial recognition system with live camera and video file processing**
|
| 514 |
|
| 515 |
## π **Key Features:**
|
| 516 |
+
- **π₯ Live Camera Recognition** - Real-time face detection from camera/CCTV
|
| 517 |
+
- **πΉ Video File Processing** - Process pre-recorded videos for attendance
|
| 518 |
- **π€ Automatic Worker Registration** - Auto-register unknown faces with unique IDs
|
| 519 |
- **π€ Manual Registration** - Register workers manually with photos
|
| 520 |
- **π
24-Hour Attendance Rule** - One attendance mark per worker per day
|
| 521 |
- **π Advanced Analytics** - Detailed reports and data export
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 522 |
"""
|
| 523 |
)
|
| 524 |
|
| 525 |
with gr.Tabs():
|
| 526 |
+
# Video Recognition Tab
|
| 527 |
+
with gr.Tab("π₯ Video Recognition", elem_classes="tab-nav"):
|
| 528 |
+
gr.Markdown("### Face Recognition from Live Camera or Video File")
|
| 529 |
|
| 530 |
with gr.Row():
|
| 531 |
with gr.Column(scale=1):
|
| 532 |
+
with gr.Tabs(selected="live", elem_classes="video-option-tabs") as video_tabs:
|
| 533 |
+
with gr.Tab("Live Camera", id="live"):
|
| 534 |
+
camera_source = gr.Number(
|
| 535 |
+
label="Camera Source (0 for default camera, or RTSP URL)",
|
| 536 |
+
value=0,
|
| 537 |
+
precision=0
|
| 538 |
+
)
|
| 539 |
+
|
| 540 |
+
with gr.Row():
|
| 541 |
+
start_stream_btn = gr.Button(
|
| 542 |
+
"π₯ Start Live Recognition",
|
| 543 |
+
variant="primary",
|
| 544 |
+
size="lg"
|
| 545 |
+
)
|
| 546 |
+
|
| 547 |
+
with gr.Tab("Upload Video", id="upload"):
|
| 548 |
+
video_file = gr.Video(
|
| 549 |
+
label="Upload Video File",
|
| 550 |
+
sources=["upload"],
|
| 551 |
+
type="filepath"
|
| 552 |
+
)
|
| 553 |
+
|
| 554 |
+
with gr.Row():
|
| 555 |
+
process_video_btn = gr.Button(
|
| 556 |
+
"πΉ Process Video File",
|
| 557 |
+
variant="primary",
|
| 558 |
+
size="lg"
|
| 559 |
+
)
|
| 560 |
|
| 561 |
+
stop_stream_btn = gr.Button(
|
| 562 |
+
"βΉοΈ Stop Processing",
|
| 563 |
+
variant="stop",
|
| 564 |
+
size="lg"
|
| 565 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 566 |
|
| 567 |
stream_status = gr.Textbox(
|
| 568 |
+
label="Processing Status",
|
| 569 |
value="Ready to start...",
|
| 570 |
interactive=False,
|
| 571 |
lines=2
|
|
|
|
| 574 |
gr.Markdown(
|
| 575 |
"""
|
| 576 |
**π Instructions:**
|
| 577 |
+
- **Live Camera:** Select camera source and click "Start Live Recognition"
|
| 578 |
+
- **Video File:** Upload a video file and click "Process Video File"
|
| 579 |
+
- Click "Stop Processing" to stop current session
|
|
|
|
| 580 |
|
| 581 |
**π¨ Color Coding:**
|
| 582 |
- π’ **Green:** Known worker (attendance marked)
|
|
|
|
| 586 |
)
|
| 587 |
|
| 588 |
with gr.Column(scale=1):
|
| 589 |
+
video_output = gr.Image(
|
| 590 |
+
label="Recognition Output",
|
| 591 |
+
streaming=True,
|
| 592 |
+
interactive=False
|
| 593 |
+
)
|
| 594 |
+
|
| 595 |
live_attendance_display = gr.Markdown(
|
| 596 |
value=attendance_system.get_today_attendance(),
|
| 597 |
label="Live Attendance Updates"
|
|
|
|
| 675 |
value="Select date range and click 'Generate Report' to view attendance analytics.",
|
| 676 |
label="Attendance Report"
|
| 677 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 678 |
|
| 679 |
# Event handlers
|
| 680 |
start_stream_btn.click(
|
|
|
|
| 683 |
outputs=[stream_status]
|
| 684 |
)
|
| 685 |
|
| 686 |
+
process_video_btn.click(
|
| 687 |
+
fn=attendance_system.process_uploaded_video,
|
| 688 |
+
inputs=[video_file],
|
| 689 |
+
outputs=[stream_status]
|
| 690 |
+
)
|
| 691 |
+
|
| 692 |
stop_stream_btn.click(
|
| 693 |
fn=attendance_system.stop_video_stream,
|
| 694 |
outputs=[stream_status]
|
|
|
|
| 723 |
outputs=[export_status, export_file]
|
| 724 |
)
|
| 725 |
|
| 726 |
+
# Video frame update
|
| 727 |
+
def update_video_frame():
|
| 728 |
+
while True:
|
| 729 |
+
frame = attendance_system.get_current_frame()
|
| 730 |
+
if frame is not None:
|
| 731 |
+
# Convert BGR to RGB
|
| 732 |
+
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 733 |
+
yield frame
|
| 734 |
+
time.sleep(0.03)
|
| 735 |
+
|
| 736 |
+
demo.load(update_video_frame, None, video_output, every=0.03)
|
| 737 |
|
| 738 |
return demo
|
| 739 |
|