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
qrcodeqrcoderenders 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
- Create a folder named
qr-attendanceqr-attendance. - Inside it, create
qr_code_attendance_system.pyqr_code_attendance_system.py. - Install dependencies:
pip install qrcode[pil] opencv-pythonpip install qrcode[pil] opencv-python.
Write the code
qr_code_attendance_system.py
Source"""
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
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
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.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 = 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 = 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
cap = cv2.VideoCapture(0) # 0 = default webcam
detector = cv2.QRCodeDetector()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
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()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:
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()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:
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 Nonedef 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 NoneMark 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:
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)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:
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])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
| Problem | Cause | Fix |
|---|---|---|
| Camera stays on / locked | Forgot release()release() | Always cap.release()cap.release() + destroyAllWindows()destroyAllWindows() |
| Same person marked many times | No duplicate check | Skip if already marked today |
| Attendance vanishes on exit | Nothing persisted | Store in SQLite/CSV |
| Black window, no feed | Wrong camera index or in-use | Try VideoCapture(1)VideoCapture(1); close other apps |
| QR won’t scan | Too small / poor lighting / glare | Larger box_sizebox_size, better light, hold steady |
| SQL breaks on odd IDs | String-formatted query | Use ?? parameterized queries |
Variations to Try
- User registry — generate codes for a whole roster from a CSV.
- Check-in / check-out — track both, compute hours present.
- Web dashboard — live attendance via Flask (see Simple Blog with Flask).
- Encrypted payloads — sign the QR data so codes can’t be forged.
- Email/SMS confirmation — notify on successful check-in.
- Photo capture — save a webcam snapshot with each scan.
- 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
qrcodeqrcodeand 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 coffeeWas this page helpful?
Let us know how we did
