commit 6f5efceb8f6d8709756662c0b7ef3046a239aa26 Author: Joseph Date: Fri Apr 25 10:23:54 2025 +0700 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a24acc --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +path +__pycache__ +image \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/camera.py b/camera.py new file mode 100644 index 0000000..c32a071 --- /dev/null +++ b/camera.py @@ -0,0 +1,45 @@ +import cv2 +import requests +# source path/to/venv/bin/activate +API_URL = "http://localhost:8000/checkin" # Đổi lại nếu backend chạy ở địa chỉ khác +CAMERA_ID = "cam_pc_01" + +def capture_and_checkin(): + cap = cv2.VideoCapture(0) # Dùng camera mặc định (webcam) + + if not cap.isOpened(): + print("Không mở được camera.") + return + + print("Đang mở camera. Nhấn phím 'c' để check-in, 'q' để thoát.") + while True: + ret, frame = cap.read() + if not ret: + print("Không đọc được frame.") + break + + cv2.imshow("Camera", frame) + + key = cv2.waitKey(1) + if key == ord("q"): + break + elif key == ord("c"): + # Ghi tạm ảnh ra file + filename = "frame.jpg" + cv2.imwrite(filename, frame) + + # Gửi ảnh lên server + with open(filename, "rb") as f: + response = requests.post( + API_URL, + files={"file": ("frame.jpg", f, "image/jpeg")}, + data={"camera_id": CAMERA_ID} + ) + + print("📡 Server:", response.json()) + + cap.release() + cv2.destroyAllWindows() + +if __name__ == "__main__": + capture_and_checkin() diff --git a/database.py b/database.py new file mode 100644 index 0000000..ba9ee9f --- /dev/null +++ b/database.py @@ -0,0 +1,8 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, declarative_base + +DATABASE_URL = "mysql+pymysql://root:@localhost/face_checkin?charset=utf8mb4" + +engine = create_engine(DATABASE_URL) +SessionLocal = sessionmaker(bind=engine, autoflush=False) +Base = declarative_base() diff --git a/frame.jpg b/frame.jpg new file mode 100644 index 0000000..cb63663 Binary files /dev/null and b/frame.jpg differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..c6ef026 --- /dev/null +++ b/main.py @@ -0,0 +1,121 @@ +from fastapi import FastAPI, UploadFile, File, Form, Depends, HTTPException +from fastapi.responses import JSONResponse +from sqlalchemy.orm import Session +import face_recognition +import numpy as np +import os +import datetime +from fastapi.staticfiles import StaticFiles +from fastapi.responses import FileResponse +from database import SessionLocal, engine +from models import Base, Student, CheckInLog +from sqlalchemy.exc import IntegrityError +from sqlalchemy import text + +app = FastAPI() +Base.metadata.create_all(bind=engine) + +UPLOAD_DIR = "./uploads" +os.makedirs(UPLOAD_DIR, exist_ok=True) + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() +app.mount("/static", StaticFiles(directory="static"), name="static") + +@app.get("/") +def root(): + return FileResponse("static/index.html") + +@app.post("/register") +async def register_face(name: str = Form(...),email: str = Form(...),file: UploadFile = File(...)): + db = SessionLocal() + + # Check if email already exists + existing = db.execute( + text("SELECT id FROM students WHERE email = :email"), + {"email": email} + ).fetchone() + if existing: + db.close() + raise HTTPException(status_code=400, detail="Email đã được đăng ký.") + + # Save image + image_data = await file.read() + image_path = f"./uploads/{file.filename}" + with open(image_path, "wb") as f: + f.write(image_data) + + # Encode face + image = face_recognition.load_image_file(image_path) + encodings = face_recognition.face_encodings(image) + + if not encodings: + db.close() + return JSONResponse(content={"message": "Không phát hiện khuôn mặt."}, status_code=400) + + encoding_bytes = encodings[0].tobytes() + + try: + db.execute( + text("INSERT INTO students (name, email, encoding) VALUES (:name, :email, :encoding)"), + {"name": name, "email": email, "encoding": encoding_bytes} + ) + db.commit() + return {"message": "Đăng ký thành công."} + except IntegrityError: + db.rollback() + raise HTTPException(status_code=400, detail="Email đã tồn tại.") + finally: + db.close() + +@app.post("/checkin") +async def checkin(file: UploadFile = File(...), camera_id: str = Form("cam1"), db: Session = Depends(get_db)): + image_data = await file.read() + path = os.path.join(UPLOAD_DIR, "checkin.jpg") + with open(path, "wb") as f: + f.write(image_data) + + unknown_img = face_recognition.load_image_file(path) + unknown_encodings = face_recognition.face_encodings(unknown_img) + + if not unknown_encodings: + return {"message": "No face detected."} + + unknown_encoding = unknown_encodings[0] + + students = db.query(Student).all() + for student in students: + known_encoding = np.frombuffer(student.encoding) + result = face_recognition.compare_faces([known_encoding], unknown_encoding, tolerance=0.5) + if result[0]: + now = datetime.datetime.now() + recent_check = db.query(CheckInLog).filter( + CheckInLog.student_id == student.id, + CheckInLog.time > now - datetime.timedelta(minutes=5) + ).first() + + if recent_check: + return {"message": f"{student.name} already checked in recently."} + + log = CheckInLog(student_id=student.id, time=now, camera_id=camera_id) + db.add(log) + db.commit() + return {"message": f"Check-in successful for {student.name}"} + + return {"message": "No match found."} + +@app.get("/logs") +def get_logs(db: Session = Depends(get_db)): + logs = db.query(CheckInLog).all() + result = [] + for log in logs: + result.append({ + "name": log.student.name, + "time": log.time.strftime("%Y-%m-%d %H:%M:%S"), + "camera_id": log.camera_id + }) + return result diff --git a/models.py b/models.py new file mode 100644 index 0000000..e0c71aa --- /dev/null +++ b/models.py @@ -0,0 +1,28 @@ +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, LargeBinary, UniqueConstraint +from sqlalchemy.orm import relationship +from database import Base +import datetime + +class Student(Base): + __tablename__ = "students" + + __tablename__ = "students" + + id = Column(Integer, primary_key=True, index=True) + name = Column(String(100), nullable=False) + email = Column(String(100), nullable=False, unique=True, index=True) + encoding = Column(LargeBinary, nullable=False) + + __table_args__ = (UniqueConstraint('email', name='uq_student_email'),) + + checkins = relationship("CheckInLog", back_populates="student") + +class CheckInLog(Base): + __tablename__ = "checkin_logs" + + id = Column(Integer, primary_key=True, index=True) + student_id = Column(Integer, ForeignKey("students.id")) + time = Column(DateTime, default=datetime.datetime.utcnow) + camera_id = Column(String(100)) + + student = relationship("Student", back_populates="checkins") diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e389492 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,17 @@ +{ + "name": "school-checkin", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "database": "^0.0.2" + } + }, + "node_modules/database": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/database/-/database-0.0.2.tgz", + "integrity": "sha512-lrqvU32firrb2TBjn4Iv9V1Mh90rqUtyyLAvdSqdGP2w7IrNy+oI5IqGNowHq8o+/WVnQtKNfNIpLYd6uWZLHw==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ee50e65 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "database": "^0.0.2" + } +} diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..19ad68c --- /dev/null +++ b/static/index.html @@ -0,0 +1,372 @@ + + + + + Face Check-In / Register + + + +
+
+
+

📸 Face Camera

+ +
+ +
+ +
+
+ +
+ +
+ + +
+
+ Nhấn phím Space để điểm danh nhanh +
+
+
+ +
+
+

📋 Lịch sử điểm danh

+ +
+ + + + + + + + + + + +
Tên học sinhThời gianCamera ID
+
+
+
+
+ + + + + + diff --git a/uploads/DANG TRUNG KIEN_check in_at_2025_01_09_07_11_15.png b/uploads/DANG TRUNG KIEN_check in_at_2025_01_09_07_11_15.png new file mode 100644 index 0000000..b513bef Binary files /dev/null and b/uploads/DANG TRUNG KIEN_check in_at_2025_01_09_07_11_15.png differ diff --git a/uploads/LE TAN LUAN_check in_at_2024_06_10_07_09_07.png b/uploads/LE TAN LUAN_check in_at_2024_06_10_07_09_07.png new file mode 100644 index 0000000..e95fa02 Binary files /dev/null and b/uploads/LE TAN LUAN_check in_at_2024_06_10_07_09_07.png differ diff --git a/uploads/checkin.jpg b/uploads/checkin.jpg new file mode 100644 index 0000000..5c43d74 Binary files /dev/null and b/uploads/checkin.jpg differ diff --git a/uploads/frame.jpg b/uploads/frame.jpg new file mode 100644 index 0000000..60bc87d Binary files /dev/null and b/uploads/frame.jpg differ