Skip to content

QR Code Attendance System

Abstract

QR codes turn any phone or printout into a scannable ID — which is why they run everything from event check-ins to classroom attendance. This project wires together two libraries: qrcodeqrcode to generate a code for each user, and OpenCV to scan it through a webcam and stamp the time. You’ll build the generate-and-scan core, then turn the toy into a usable system: a database of records, duplicate-scan prevention, a live camera overlay that shows the decoded ID, and CSV export for reports.

You will leave understanding:

  • How QR codes encode arbitrary text and how qrcodeqrcode renders them.
  • The OpenCV capture loop: open camera → read frame → detect → release.
  • Why the naive version can’t tell who’s already checked in (and how to fix it).
  • How to move from “popup says scanned” to a real attendance log.

Prerequisites

  • Python 3.6 or above.
  • A text editor or IDE.
  • pip install qrcode[pil] opencv-pythonpip install qrcode[pil] opencv-python.
  • A working webcam.
  • Tkinter (bundled with Python).

Getting Started

Create the project

  1. Create a folder named qr-attendanceqr-attendance.
  2. Inside it, create qr_code_attendance_system.pyqr_code_attendance_system.py.
  3. Install dependencies: pip install qrcode[pil] opencv-pythonpip install qrcode[pil] opencv-python.

Write the code

qr_code_attendance_system.pySource
qr_code_attendance_system.py
"""
QR Code Attendance System
 
A Python application that generates QR codes for users and scans them to mark attendance. Features include:
- Generating QR codes for user identification.
- Scanning QR codes to mark attendance.
"""
 
import qrcode
import cv2
from tkinter import Tk, Label, Entry, Button, messagebox
from datetime import datetime
 
 
class QRCodeAttendanceSystem:
    def __init__(self, root):
        self.root = root
        self.root.title("QR Code Attendance System")
 
        Label(root, text="Enter User ID:").grid(row=0, column=0, padx=10, pady=10)
        self.user_id_entry = Entry(root, width=30)
        self.user_id_entry.grid(row=0, column=1, padx=10, pady=10)
 
        Button(root, text="Generate QR Code", command=self.generate_qr_code).grid(row=1, column=0, columnspan=2, pady=10)
        Button(root, text="Scan QR Code", command=self.scan_qr_code).grid(row=2, column=0, columnspan=2, pady=10)
 
    def generate_qr_code(self):
        """Generate a QR code for the entered user ID."""
        user_id = self.user_id_entry.get()
        if not user_id:
            messagebox.showerror("Error", "Please enter a User ID.")
            return
 
        qr = qrcode.QRCode(version=1, box_size=10, border=5)
        qr.add_data(user_id)
        qr.make(fit=True)
 
        img = qr.make_image(fill="black", back_color="white")
        img_path = f"{user_id}_qr.png"
        img.save(img_path)
 
        messagebox.showinfo("Success", f"QR Code generated and saved as {img_path}.")
 
    def scan_qr_code(self):
        """Scan a QR code to mark attendance."""
        cap = cv2.VideoCapture(0)
        detector = cv2.QRCodeDetector()
 
        while True:
            ret, frame = cap.read()
            if not ret:
                break
 
            data, bbox, _ = detector.detectAndDecode(frame)
            if data:
                timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                messagebox.showinfo("Attendance Marked", f"User ID: {data}\nTime: {timestamp}")
                break
 
            cv2.imshow("QR Code Scanner", frame)
            if cv2.waitKey(1) & 0xFF == ord("q"):
                break
 
        cap.release()
        cv2.destroyAllWindows()
 
 
def main():
    root = Tk()
    app = QRCodeAttendanceSystem(root)
    root.mainloop()
 
 
if __name__ == "__main__":
    main()
 
qr_code_attendance_system.py
"""
QR Code Attendance System
 
A Python application that generates QR codes for users and scans them to mark attendance. Features include:
- Generating QR codes for user identification.
- Scanning QR codes to mark attendance.
"""
 
import qrcode
import cv2
from tkinter import Tk, Label, Entry, Button, messagebox
from datetime import datetime
 
 
class QRCodeAttendanceSystem:
    def __init__(self, root):
        self.root = root
        self.root.title("QR Code Attendance System")
 
        Label(root, text="Enter User ID:").grid(row=0, column=0, padx=10, pady=10)
        self.user_id_entry = Entry(root, width=30)
        self.user_id_entry.grid(row=0, column=1, padx=10, pady=10)
 
        Button(root, text="Generate QR Code", command=self.generate_qr_code).grid(row=1, column=0, columnspan=2, pady=10)
        Button(root, text="Scan QR Code", command=self.scan_qr_code).grid(row=2, column=0, columnspan=2, pady=10)
 
    def generate_qr_code(self):
        """Generate a QR code for the entered user ID."""
        user_id = self.user_id_entry.get()
        if not user_id:
            messagebox.showerror("Error", "Please enter a User ID.")
            return
 
        qr = qrcode.QRCode(version=1, box_size=10, border=5)
        qr.add_data(user_id)
        qr.make(fit=True)
 
        img = qr.make_image(fill="black", back_color="white")
        img_path = f"{user_id}_qr.png"
        img.save(img_path)
 
        messagebox.showinfo("Success", f"QR Code generated and saved as {img_path}.")
 
    def scan_qr_code(self):
        """Scan a QR code to mark attendance."""
        cap = cv2.VideoCapture(0)
        detector = cv2.QRCodeDetector()
 
        while True:
            ret, frame = cap.read()
            if not ret:
                break
 
            data, bbox, _ = detector.detectAndDecode(frame)
            if data:
                timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                messagebox.showinfo("Attendance Marked", f"User ID: {data}\nTime: {timestamp}")
                break
 
            cv2.imshow("QR Code Scanner", frame)
            if cv2.waitKey(1) & 0xFF == ord("q"):
                break
 
        cap.release()
        cv2.destroyAllWindows()
 
 
def main():
    root = Tk()
    app = QRCodeAttendanceSystem(root)
    root.mainloop()
 
 
if __name__ == "__main__":
    main()
 

Run it

command
C:\Users\Your Name\qr-attendance> python qr_code_attendance_system.py
# Enter a User ID and "Generate QR Code" -> saves <id>_qr.png
# "Scan QR Code" opens the webcam; show the code to mark attendance.
command
C:\Users\Your Name\qr-attendance> python qr_code_attendance_system.py
# Enter a User ID and "Generate QR Code" -> saves <id>_qr.png
# "Scan QR Code" opens the webcam; show the code to mark attendance.

Step-by-Step Explanation

1. Generating a QR code

qr_code_attendance_system.py
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(user_id)
qr.make(fit=True)
img = qr.make_image(fill="black", back_color="white")
img.save(f"{user_id}_qr.png")
qr_code_attendance_system.py
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(user_id)
qr.make(fit=True)
img = qr.make_image(fill="black", back_color="white")
img.save(f"{user_id}_qr.png")

A QR code is just text encoded as a grid. versionversion sets capacity (1 = smallest), box_sizebox_size is pixels per module, borderborder is the quiet zone around it. fit=Truefit=True auto-grows the version if the data doesn’t fit. Here the encoded data is simply the user ID.

2. Opening the camera

qr_code_attendance_system.py
cap = cv2.VideoCapture(0)          # 0 = default webcam
detector = cv2.QRCodeDetector()
qr_code_attendance_system.py
cap = cv2.VideoCapture(0)          # 0 = default webcam
detector = cv2.QRCodeDetector()

VideoCapture(0)VideoCapture(0) grabs the first camera. QRCodeDetectorQRCodeDetector is OpenCV’s built-in QR reader — no extra library needed.

3. The scan loop

qr_code_attendance_system.py
while True:
    ret, frame = cap.read()
    if not ret:
        break
    data, bbox, _ = detector.detectAndDecode(frame)
    if data:
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        messagebox.showinfo("Attendance Marked", f"User ID: {data}\nTime: {timestamp}")
        break
    cv2.imshow("QR Code Scanner", frame)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break
cap.release()
cv2.destroyAllWindows()
qr_code_attendance_system.py
while True:
    ret, frame = cap.read()
    if not ret:
        break
    data, bbox, _ = detector.detectAndDecode(frame)
    if data:
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        messagebox.showinfo("Attendance Marked", f"User ID: {data}\nTime: {timestamp}")
        break
    cv2.imshow("QR Code Scanner", frame)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break
cap.release()
cv2.destroyAllWindows()

Each loop reads a frame, tries to decode a QR code, and on success stamps the time and stops. cv2.imshowcv2.imshow shows the live feed; waitKey(1)waitKey(1) keeps it responsive and lets qq quit. Always release()release() the camera and destroyAllWindows()destroyAllWindows() — skip it and the webcam stays locked.

The Core Problem: Nothing Is Actually Stored

The naive version pops a message and forgets. A real system needs a record. Add SQLite:

db.py
import sqlite3
 
conn = sqlite3.connect("attendance.db")
conn.execute("""CREATE TABLE IF NOT EXISTS attendance (
    user_id TEXT, timestamp TEXT
)""")
 
def mark(user_id):
    ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    conn.execute("INSERT INTO attendance VALUES (?, ?)", (user_id, ts))
    conn.commit()
db.py
import sqlite3
 
conn = sqlite3.connect("attendance.db")
conn.execute("""CREATE TABLE IF NOT EXISTS attendance (
    user_id TEXT, timestamp TEXT
)""")
 
def mark(user_id):
    ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    conn.execute("INSERT INTO attendance VALUES (?, ?)", (user_id, ts))
    conn.commit()

Note ?? placeholders — never f-string user input into SQL (injection risk).

Prevent Duplicate Scans

A code lingering in view marks attendance dozens of times. Check today’s log first:

dedupe.py
def already_marked_today(user_id):
    today = datetime.now().strftime("%Y-%m-%d")
    row = conn.execute(
        "SELECT 1 FROM attendance WHERE user_id=? AND timestamp LIKE ?",
        (user_id, f"{today}%")).fetchone()
    return row is not None
dedupe.py
def already_marked_today(user_id):
    today = datetime.now().strftime("%Y-%m-%d")
    row = conn.execute(
        "SELECT 1 FROM attendance WHERE user_id=? AND timestamp LIKE ?",
        (user_id, f"{today}%")).fetchone()
    return row is not None

Mark only if not already_marked_today(data)not already_marked_today(data).

Draw a Live Overlay

Show the detected box and ID on the video so the user knows it worked:

overlay.py
if data and bbox is not None:
    pts = bbox.astype(int).reshape(-1, 2)
    for i in range(len(pts)):
        cv2.line(frame, tuple(pts[i]), tuple(pts[(i + 1) % len(pts)]), (0, 255, 0), 2)
    cv2.putText(frame, data, tuple(pts[0]), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
overlay.py
if data and bbox is not None:
    pts = bbox.astype(int).reshape(-1, 2)
    for i in range(len(pts)):
        cv2.line(frame, tuple(pts[i]), tuple(pts[(i + 1) % len(pts)]), (0, 255, 0), 2)
    cv2.putText(frame, data, tuple(pts[0]), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)

Export to CSV

For reports, dump the table:

export.py
import csv
def export():
    rows = conn.execute("SELECT user_id, timestamp FROM attendance").fetchall()
    with open("attendance.csv", "w", newline="") as f:
        csv.writer(f).writerows([("User ID", "Time"), *rows])
export.py
import csv
def export():
    rows = conn.execute("SELECT user_id, timestamp FROM attendance").fetchall()
    with open("attendance.csv", "w", newline="") as f:
        csv.writer(f).writerows([("User ID", "Time"), *rows])

Common Mistakes

ProblemCauseFix
Camera stays on / lockedForgot release()release()Always cap.release()cap.release() + destroyAllWindows()destroyAllWindows()
Same person marked many timesNo duplicate checkSkip if already marked today
Attendance vanishes on exitNothing persistedStore in SQLite/CSV
Black window, no feedWrong camera index or in-useTry VideoCapture(1)VideoCapture(1); close other apps
QR won’t scanToo small / poor lighting / glareLarger box_sizebox_size, better light, hold steady
SQL breaks on odd IDsString-formatted queryUse ?? parameterized queries

Variations to Try

  1. User registry — generate codes for a whole roster from a CSV.
  2. Check-in / check-out — track both, compute hours present.
  3. Web dashboard — live attendance via Flask (see Simple Blog with Flask).
  4. Encrypted payloads — sign the QR data so codes can’t be forged.
  5. Email/SMS confirmation — notify on successful check-in.
  6. Photo capture — save a webcam snapshot with each scan.
  7. Mobile scanning — a companion phone app posting to your API.

Real-World Applications

  • Events & conferences — ticketed entry and session check-in.
  • Schools & offices — class and workplace attendance.
  • Gyms & memberships — access logging.
  • Inventory & logistics — scanning items in and out.

Educational Value

  • QR generation & decoding — encoding data, the qrcodeqrcode and OpenCV APIs.
  • Computer vision basics — the capture/detect/release loop.
  • Database design — records, queries, and safe parameterization.
  • Data integrity — duplicate prevention and exports.

Next Steps

  • Persist scans to SQLite with duplicate prevention.
  • Add a live overlay showing the decoded ID.
  • Support check-in/check-out and CSV export.
  • Generate codes for a whole roster at once.

Conclusion

You combined qrcodeqrcode and OpenCV into a scan-to-log attendance system, then closed the gap that makes the naive version a toy: it now remembers, refuses duplicates, and exports reports. Generate-then-scan plus a database is the exact recipe behind ticketing, access control, and inventory systems everywhere. Full source on GitHub. Explore more computer-vision projects on Python Central Hub.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did