
本文针对基于OpenCV和face_recognition库构建的人脸识别考勤系统,解决了在摄像头持续识别人脸时,重复将考勤记录写入CSV文件的问题。通过调整代码逻辑,确保每个人只记录一次考勤信息,并提供优化建议,提高程序效率。
在构建人脸识别考勤系统时,一个常见的挑战是避免重复记录考勤信息。以下将详细介绍如何修改代码,确保每个人只记录一次考勤,并提供一些优化建议。
问题分析
原始代码在主循环中,每次检测到人脸匹配时,都会调用 markAttendance 函数。由于摄像头帧率很高,即使人脸只出现一次,也会被多次检测到,导致重复写入 CSV 文件。
解决方案
核心思路是:在将姓名写入文件之前,先检查该姓名是否已经存在于已记录的姓名列表中。如果存在,则跳过写入操作;否则,才进行写入。以下是修改后的 markAttendance 函数:
from datetime import datetime
def markAttendance(name):
with open('Attendance.csv', 'r+') as f:
myDataList = f.readlines()
nameList = []
for line in myDataList:
entry = line.split(',')
nameList.append(entry[0])
if name not in nameList:
now = datetime.now()
dtString = now.strftime('%H:%M:%S')
f.writelines(f'\n{name},{dtString}')代码解释:
- 读取现有数据: f.readlines() 读取整个文件的所有行,并将其存储在 myDataList 列表中。
- 提取已记录的姓名: 循环遍历 myDataList,将每一行按照逗号分隔,提取出姓名,并将其添加到 nameList 列表中。
- 检查姓名是否已存在: 使用 if name not in nameList: 判断当前检测到的姓名是否已经存在于 nameList 中。
- 写入考勤记录: 如果姓名不存在,则获取当前时间,格式化为字符串,并将姓名和时间写入 CSV 文件。
优化建议
虽然上述代码可以解决重复写入的问题,但每次调用 markAttendance 函数时都读取整个 CSV 文件效率较低。可以进行以下优化:
- 在程序启动时读取姓名列表: 在程序启动时,一次性读取 CSV 文件中的所有姓名,并将其存储在内存中。
- 更新内存中的姓名列表: 每次成功写入新的考勤记录后,立即将该姓名添加到内存中的姓名列表中。
- 使用 "a" 模式打开文件: 在markAttendance函数中使用追加模式 "a" 打开文件,而不是 "r+" 模式,简化写入操作。
以下是优化后的代码示例:
def readNames():
try:
with open('Attendance.csv', 'r') as f:
nameList = [line.split(',')[0] for line in f]
except FileNotFoundError:
# 如果文件不存在,创建一个空文件并返回一个空列表
open('Attendance.csv', 'w').close()
nameList = []
return nameList
def markAttendance(name, nameList):
if name not in nameList:
nameList.append(name)
with open('Attendance.csv', 'a') as f:
dt = datetime.now().strftime('%H:%M:%S')
f.writelines(f'\n{name},{dt}')
# --- 主程序 ---
nameList = readNames() # 在程序启动时读取姓名列表
cap = cv2.VideoCapture(0)
while True:
# ... (人脸识别代码) ...
for encodeFace, faceLoc in zip(encodesCurFrame, facesCurFrame):
matches = face_recognition.compare_faces(encodeListKnown, encodeFace)
faceDis = face_recognition.face_distance(encodeListKnown, encodeFace)
matchIndex = np.argmin(faceDis)
if matches[matchIndex]:
name = classNames[matchIndex].upper()
# ... (绘制矩形框和文字) ...
markAttendance(name, nameList) # 传递 nameList代码解释:
- readNames() 函数: 在程序启动时调用,读取 CSV 文件中的所有姓名,并将其存储在 nameList 列表中。增加了异常处理,如果文件不存在则创建。
- markAttendance() 函数: 接收 nameList 作为参数,直接在内存中进行姓名检查,并将新的姓名添加到 nameList 中。使用 "a" 模式打开文件,进行追加写入。
- 主程序: 在主循环中,将 nameList 传递给 markAttendance() 函数。
注意事项
- 确保 CSV 文件存在,并且具有正确的格式(姓名,时间)。如果文件不存在,readNames() 函数会创建一个空文件。
- 根据实际情况调整人脸识别的灵敏度,避免误识别导致错误的考勤记录。
- 可以考虑使用数据库存储考勤数据,以便进行更复杂的查询和分析。
总结
通过在写入 CSV 文件之前进行姓名检查,可以有效地避免重复写入考勤记录。通过在程序启动时读取姓名列表,并将其存储在内存中,可以提高程序的效率。根据实际情况选择合适的优化方案,可以构建一个稳定、高效的人脸识别考勤系统。










