• openCV实战项目--人脸考勤


    人脸任务在计算机视觉领域中十分重要,本项目主要使用了两类技术:人脸检测+人脸识别

    代码分为两部分内容:人脸注册人脸识别

    • 人脸注册:将人脸特征存储进数据库,这里用feature.csv代替
    • 人脸识别:将人脸特征与CSV文件中人脸特征进行比较,如果成功匹配则写入考勤文件attendance.csv

    文章前半部分为一步步实现流程介绍,最后会有整理过后的完整项目代码。

    一、项目实现

    A. 注册: 

    导入相关包

    1. import cv2
    2. import numpy as np
    3. import dlib
    4. import time
    5. import csv
    6. # from argparse import ArgumentParser
    7. from PIL import Image, ImageDraw, ImageFont

    设计注册功能

    注册过程我们需要完成的事:

    • 打开摄像头获取画面图片
    • 在图片中检测并获取人脸位置
    • 根据人脸位置获取68个关键点
    • 根据68个关键点生成特征描述符
    • 保存
    • (优化)展示界面,加入注册时成功提示等

    1、基本步骤

    我们首先进行前三步

    1. # 检测人脸,获取68个关键点,获取特征描述符
    2. def faceRegister(faceId=1, userName='default', interval=3, faceCount=3, resize_w=700, resize_h=400):
    3. '''
    4. faceId:人脸ID
    5. userName: 人脸姓名
    6. faceCount: 采集该人脸图片的数量
    7. interval: 采集间隔
    8. '''
    9. cap = cv2.VideoCapture(0)
    10. # 人脸检测模型
    11. hog_face_detector = dlib.get_frontal_face_detector()
    12. # 关键点 检测模型
    13. shape_detector = dlib.shape_predictor('./weights/shape_predictor_68_face_landmarks.dat')
    14. # resnet模型
    15. face_descriptor_extractor = dlib.face_recognition_model_v1('./weights/dlib_face_recognition_resnet_model_v1.dat')
    16. while True:
    17. ret, frame = cap.read()
    18. # 镜像
    19. frame = cv2.flip(frame,1)
    20. # 转为灰度图
    21. frame_gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
    22. # 检测人脸
    23. detections = hog_face_detector(frame,1)
    24. for face in detections:
    25. # 人脸框坐标 左上和右下
    26. l, t, r, b = face.left(), face.top(), face.right(), face.bottom()
    27. # 获取68个关键点
    28. points = shape_detector(frame,face)
    29. # 绘制关键点
    30. for point in points.parts():
    31. cv2.circle(frame,(point.x,point.y),2,(0,255,0),1)
    32. # 绘制矩形框
    33. cv2.rectangle(frame,(l,t),(r,b),(0,255,0),2)
    34. cv2.imshow("face",frame)
    35. if cv2.waitKey(10) & 0xFF == ord('q'):
    36. break
    37. cap.release()
    38. cv2.destroyAllWindows
    39. faceRegister()

    此时一张帅脸如下:

    2、描述符的采集

    之后,我们根据参数,即faceCount 和 Interval 进行描述符的生成和采集

    (这里我默认是faceCount=3,Interval=3,即每3秒采集一次,共3次)

    1. def faceRegister(faceId=1, userName='default', interval=3, faceCount=3, resize_w=700, resize_h=400):
    2. '''
    3. faceId:人脸ID
    4. userName: 人脸姓名
    5. faceCount: 采集该人脸图片的数量
    6. interval: 采集间隔
    7. '''
    8. cap = cv2.VideoCapture(0)
    9. # 人脸检测模型
    10. hog_face_detector = dlib.get_frontal_face_detector()
    11. # 关键点 检测模型
    12. shape_detector = dlib.shape_predictor('./weights/shape_predictor_68_face_landmarks.dat')
    13. # resnet模型
    14. face_descriptor_extractor = dlib.face_recognition_model_v1('./weights/dlib_face_recognition_resnet_model_v1.dat')
    15. # 开始时间
    16. start_time = time.time()
    17. # 执行次数
    18. collect_times = 0
    19. while True:
    20. ret, frame = cap.read()
    21. # 镜像
    22. frame = cv2.flip(frame,1)
    23. # 转为灰度图
    24. frame_gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
    25. # 检测人脸
    26. detections = hog_face_detector(frame,1)
    27. for face in detections:
    28. # 人脸框坐标 左上和右下
    29. l, t, r, b = face.left(), face.top(), face.right(), face.bottom()
    30. # 获取68个关键点
    31. points = shape_detector(frame,face)
    32. # 绘制人脸关键点
    33. for point in points.parts():
    34. cv2.circle(frame, (point.x, point.y), 2, (0, 255, 0), 1)
    35. # 绘制矩形框
    36. cv2.rectangle(frame, (l, t), (r, b), (0, 255, 0), 2)
    37. # 采集:
    38. if collect_times < faceCount:
    39. # 获取当前时间
    40. now = time.time()
    41. # 时间限制
    42. if now - start_time > interval:
    43. # 获取特征描述符
    44. face_descriptor = face_descriptor_extractor.compute_face_descriptor(frame,points)
    45. # dlib格式转为数组
    46. face_descriptor = [f for f in face_descriptor]
    47. collect_times += 1
    48. start_time = now
    49. print("成功采集{}次".format(collect_times))
    50. else:
    51. # 时间间隔不到interval
    52. print("等待进行下一次采集")
    53. pass
    54. else:
    55. # 已经成功采集完3次了
    56. print("采集完毕")
    57. cap.release()
    58. cv2.destroyAllWindows()
    59. return
    60. cv2.imshow("face",frame)
    61. if cv2.waitKey(10) & 0xFF == ord('q'):
    62. break
    63. cap.release()
    64. cv2.destroyAllWindows()
    65. faceRegister()
    等待进行下一次采集
    ...
    成功采集1次
    等待进行下一次采集
    ...
    成功采集2次
    等待进行下一次采集
    ...
    成功采集3次
    采集完毕

    3、完整的注册

    最后就是写入csv文件

    这里加入了注册成功等的提示,且把一些变量放到了全局,因为后面人脸识别打卡时也会用到。

    1. # 加载人脸检测器
    2. hog_face_detector = dlib.get_frontal_face_detector()
    3. cnn_detector = dlib.cnn_face_detection_model_v1('./weights/mmod_human_face_detector.dat')
    4. haar_face_detector = cv2.CascadeClassifier('./weights/haarcascade_frontalface_default.xml')
    5. # 加载关键点检测器
    6. points_detector = dlib.shape_predictor('./weights/shape_predictor_68_face_landmarks.dat')
    7. # 加载resnet模型
    8. face_descriptor_extractor = dlib.face_recognition_model_v1('./weights/dlib_face_recognition_resnet_model_v1.dat')
    1. # 绘制中文
    2. def cv2AddChineseText(img, text, position, textColor=(0, 255, 0), textSize=30):
    3. if (isinstance(img, np.ndarray)): # 判断是否OpenCV图片类型
    4. img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    5. # 创建一个可以在给定图像上绘图的对象
    6. draw = ImageDraw.Draw(img)
    7. # 字体的格式
    8. fontStyle = ImageFont.truetype(
    9. "./fonts/songti.ttc", textSize, encoding="utf-8")
    10. # 绘制文本
    11. draw.text(position, text, textColor, font=fontStyle)
    12. # 转换回OpenCV格式
    13. return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)
    1. # 绘制左侧信息
    2. def drawLeftInfo(frame, fpsText, mode="Reg", detector='haar', person=1, count=1):
    3. # 帧率
    4. cv2.putText(frame, "FPS: " + str(round(fpsText, 2)), (30, 50), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)
    5. # 模式:注册、识别
    6. cv2.putText(frame, "Mode: " + str(mode), (30, 80), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)
    7. if mode == 'Recog':
    8. # 检测器
    9. cv2.putText(frame, "Detector: " + detector, (30, 110), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)
    10. # 人数
    11. cv2.putText(frame, "Person: " + str(person), (30, 140), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)
    12. # 总人数
    13. cv2.putText(frame, "Count: " + str(count), (30, 170), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)
    1. # 注册人脸
    2. def faceRegiser(faceId=1, userName='default', interval=3, faceCount=3, resize_w=700, resize_h=400):
    3. # 计数
    4. count = 0
    5. # 开始注册时间
    6. startTime = time.time()
    7. # 视频时间
    8. frameTime = startTime
    9. # 控制显示打卡成功的时长
    10. show_time = (startTime - 10)
    11. # 打开文件
    12. f = open('./data/feature.csv', 'a', newline='')
    13. csv_writer = csv.writer(f)
    14. cap = cv2.VideoCapture(0)
    15. while True:
    16. ret, frame = cap.read()
    17. frame = cv2.resize(frame, (resize_w, resize_h))
    18. frame = cv2.flip(frame, 1)
    19. # 检测
    20. face_detetion = hog_face_detector(frame, 1)
    21. for face in face_detetion:
    22. # 识别68个关键点
    23. points = points_detector(frame, face)
    24. # 绘制人脸关键点
    25. for point in points.parts():
    26. cv2.circle(frame, (point.x, point.y), 2, (0, 255, 0), 1)
    27. # 绘制框框
    28. l, t, r, b = face.left(), face.top(), face.right(), face.bottom()
    29. cv2.rectangle(frame, (l, t), (r, b), (0, 255, 0), 2)
    30. now = time.time()
    31. if (now - show_time) < 0.5:
    32. frame = cv2AddChineseText(frame,
    33. "注册成功 {count}/{faceCount}".format(count=(count + 1), faceCount=faceCount),
    34. (l, b + 30), textColor=(255, 0, 255), textSize=30)
    35. # 检查次数
    36. if count < faceCount:
    37. # 检查时间
    38. if now - startTime > interval:
    39. # 特征描述符
    40. face_descriptor = face_descriptor_extractor.compute_face_descriptor(frame, points)
    41. face_descriptor = [f for f in face_descriptor]
    42. # 描述符增加进data文件
    43. line = [faceId, userName, face_descriptor]
    44. # 写入
    45. csv_writer.writerow(line)
    46. # 保存照片样本
    47. print('人脸注册成功 {count}/{faceCount},faceId:{faceId},userName:{userName}'.format(count=(count + 1),
    48. faceCount=faceCount,
    49. faceId=faceId,
    50. userName=userName))
    51. frame = cv2AddChineseText(frame,
    52. "注册成功 {count}/{faceCount}".format(count=(count + 1), faceCount=faceCount),
    53. (l, b + 30), textColor=(255, 0, 255), textSize=30)
    54. show_time = time.time()
    55. # 时间重置
    56. startTime = now
    57. # 次数加一
    58. count += 1
    59. else:
    60. print('人脸注册完毕')
    61. f.close()
    62. cap.release()
    63. cv2.destroyAllWindows()
    64. return
    65. now = time.time()
    66. fpsText = 1 / (now - frameTime)
    67. frameTime = now
    68. # 绘制
    69. drawLeftInfo(frame, fpsText, 'Register')
    70. cv2.imshow('Face Attendance Demo: Register', frame)
    71. if cv2.waitKey(10) & 0xFF == ord('q'):
    72. break
    73. f.close()
    74. cap.release()
    75. cv2.destroyAllWindows()

    此时执行:

    faceRegiser(3,"用户B")

    人脸注册成功 1/3,faceId:3,userName:用户B
    人脸注册成功 2/3,faceId:3,userName:用户B
    人脸注册成功 3/3,faceId:3,userName:用户B
    人脸注册完毕

    其features文件:

    B. 识别、打卡

    识别步骤如下:

    • 打开摄像头获取画面
    • 根据画面中的图片获取里面的人脸特征描述符
    • 根据特征描述符将其与feature.csv文件里特征做距离判断
    • 获取ID、NAME
    • 考勤记录写入attendance.csv里

    这里与上面流程相似,不过是加了一个对比功能,距离小于阈值,则表示匹配成功。就加快速度不一步步来了,代码如下:

    1. # 刷新右侧考勤信息
    2. def updateRightInfo(frame, face_info_list, face_img_list):
    3. # 重新绘制逻辑:从列表中每隔3个取一批显示,新增人脸放在最前面
    4. # 如果有更新,重新绘制
    5. # 如果没有,定时往后移动
    6. left_x = 30
    7. left_y = 20
    8. resize_w = 80
    9. offset_y = 120
    10. index = 0
    11. frame_h = frame.shape[0]
    12. frame_w = frame.shape[1]
    13. for face in face_info_list[:3]:
    14. name = face[0]
    15. time = face[1]
    16. face_img = face_img_list[index]
    17. # print(face_img.shape)
    18. face_img = cv2.resize(face_img, (resize_w, resize_w))
    19. offset_y_value = offset_y * index
    20. frame[(left_y + offset_y_value):(left_y + resize_w + offset_y_value), -(left_x + resize_w):-left_x] = face_img
    21. cv2.putText(frame, name, ((frame_w - (left_x + resize_w)), (left_y + resize_w) + 15 + offset_y_value),
    22. cv2.FONT_ITALIC, 0.5, (0, 255, 0), 1)
    23. cv2.putText(frame, time, ((frame_w - (left_x + resize_w)), (left_y + resize_w) + 30 + offset_y_value),
    24. cv2.FONT_ITALIC, 0.5, (0, 255, 0), 1)
    25. index += 1
    26. return frame
    1. # 返回DLIB格式的face
    2. def getDlibRect(detector='hog', face=None):
    3. l, t, r, b = None, None, None, None
    4. if detector == 'hog':
    5. l, t, r, b = face.left(), face.top(), face.right(), face.bottom()
    6. if detector == 'cnn':
    7. l = face.rect.left()
    8. t = face.rect.top()
    9. r = face.rect.right()
    10. b = face.rect.bottom()
    11. if detector == 'haar':
    12. l = face[0]
    13. t = face[1]
    14. r = face[0] + face[2]
    15. b = face[1] + face[3]
    16. nonnegative = lambda x: x if x >= 0 else 0
    17. return map(nonnegative, (l, t, r, b))
    1. # 获取CSV中信息
    2. def getFeatList():
    3. print('加载注册的人脸特征')
    4. feature_list = None
    5. label_list = []
    6. name_list = []
    7. # 加载保存的特征样本
    8. with open('./data/feature.csv', 'r') as f:
    9. csv_reader = csv.reader(f)
    10. for line in csv_reader:
    11. # 重新加载数据
    12. faceId = line[0]
    13. userName = line[1]
    14. face_descriptor = eval(line[2])
    15. label_list.append(faceId)
    16. name_list.append(userName)
    17. # 转为numpy格式
    18. face_descriptor = np.asarray(face_descriptor, dtype=np.float64)
    19. # 转为二维矩阵,拼接
    20. face_descriptor = np.reshape(face_descriptor, (1, -1))
    21. # 初始化
    22. if feature_list is None:
    23. feature_list = face_descriptor
    24. else:
    25. # 拼接
    26. feature_list = np.concatenate((feature_list, face_descriptor), axis=0)
    27. print("特征加载完毕")
    28. return feature_list, label_list, name_list
    1. # 人脸识别
    2. def faceRecognize(detector='haar', threshold=0.5, write_video=False, resize_w=700, resize_h=400):
    3. # 视频时间
    4. frameTime = time.time()
    5. # 加载特征
    6. feature_list, label_list, name_list = getFeatList()
    7. face_time_dict = {}
    8. # 保存name,time人脸信息
    9. face_info_list = []
    10. # numpy格式人脸图像数据
    11. face_img_list = []
    12. # 侦测人数
    13. person_detect = 0
    14. # 统计人脸数
    15. face_count = 0
    16. # 控制显示打卡成功的时长
    17. show_time = (frameTime - 10)
    18. # 考勤记录
    19. f = open('./data/attendance.csv', 'a')
    20. csv_writer = csv.writer(f)
    21. cap = cv2.VideoCapture(0)
    22. # resize_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))//2
    23. # resize_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) //2
    24. videoWriter = cv2.VideoWriter('./record_video/out' + str(time.time()) + '.mp4', cv2.VideoWriter_fourcc(*'MP4V'), 15,
    25. (resize_w, resize_h))
    26. while True:
    27. ret, frame = cap.read()
    28. frame = cv2.resize(frame, (resize_w, resize_h))
    29. frame = cv2.flip(frame, 1)
    30. # 切换人脸检测器
    31. if detector == 'hog':
    32. face_detetion = hog_face_detector(frame, 1)
    33. if detector == 'cnn':
    34. face_detetion = cnn_detector(frame, 1)
    35. if detector == 'haar':
    36. frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    37. face_detetion = haar_face_detector.detectMultiScale(frame_gray, minNeighbors=7, minSize=(100, 100))
    38. person_detect = len(face_detetion)
    39. for face in face_detetion:
    40. l, t, r, b = getDlibRect(detector, face)
    41. face = dlib.rectangle(l, t, r, b)
    42. # 识别68个关键点
    43. points = points_detector(frame, face)
    44. cv2.rectangle(frame, (l, t), (r, b), (0, 255, 0), 2)
    45. # 人脸区域
    46. face_crop = frame[t:b, l:r]
    47. # 特征
    48. face_descriptor = face_descriptor_extractor.compute_face_descriptor(frame, points)
    49. face_descriptor = [f for f in face_descriptor]
    50. face_descriptor = np.asarray(face_descriptor, dtype=np.float64)
    51. # 计算距离
    52. distance = np.linalg.norm((face_descriptor - feature_list), axis=1)
    53. # 最小距离索引
    54. min_index = np.argmin(distance)
    55. # 最小距离
    56. min_distance = distance[min_index]
    57. predict_name = "Not recog"
    58. if min_distance < threshold:
    59. # 距离小于阈值,表示匹配
    60. predict_id = label_list[min_index]
    61. predict_name = name_list[min_index]
    62. # 判断是否新增记录:如果一个人距上次检测时间>3秒,或者换了一个人,将这条记录插入
    63. need_insert = False
    64. now = time.time()
    65. if predict_name in face_time_dict:
    66. if (now - face_time_dict[predict_name]) > 3:
    67. # 刷新时间
    68. face_time_dict[predict_name] = now
    69. need_insert = True
    70. else:
    71. # 还是上次人脸
    72. need_insert = False
    73. else:
    74. # 新增数据记录
    75. face_time_dict[predict_name] = now
    76. need_insert = True
    77. if (now - show_time) < 1:
    78. frame = cv2AddChineseText(frame, "打卡成功", (l, b + 30), textColor=(0, 255, 0), textSize=40)
    79. if need_insert:
    80. # 连续显示打卡成功1s
    81. frame = cv2AddChineseText(frame, "打卡成功", (l, b + 30), textColor=(0, 255, 0), textSize=40)
    82. show_time = time.time()
    83. time_local = time.localtime(face_time_dict[predict_name])
    84. # 转换成新的时间格式(2016-05-05 20:28:54)
    85. face_time = time.strftime("%H:%M:%S", time_local)
    86. face_time_full = time.strftime("%Y-%m-%d %H:%M:%S", time_local)
    87. # 开始位置增加
    88. face_info_list.insert(0, [predict_name, face_time])
    89. face_img_list.insert(0, face_crop)
    90. # 写入考勤表
    91. line = [predict_id, predict_name, min_distance, face_time_full]
    92. csv_writer.writerow(line)
    93. face_count += 1
    94. # 绘制人脸点
    95. cv2.putText(frame, predict_name + " " + str(round(min_distance, 2)), (l, b + 30), cv2.FONT_ITALIC, 0.8,
    96. (0, 255, 0), 2)
    97. # 处理下一张脸
    98. now = time.time()
    99. fpsText = 1 / (now - frameTime)
    100. frameTime = now
    101. # 绘制
    102. drawLeftInfo(frame, fpsText, 'Recog', detector=detector, person=person_detect, count=face_count)
    103. # 舍弃face_img_list、face_info_list后部分,节约内存
    104. if len(face_info_list) > 10:
    105. face_info_list = face_info_list[:9]
    106. face_img_list = face_img_list[:9]
    107. frame = updateRightInfo(frame, face_info_list, face_img_list)
    108. if write_video:
    109. videoWriter.write(frame)
    110. cv2.imshow('Face Attendance Demo: Recognition', frame)
    111. if cv2.waitKey(10) & 0xFF == ord('q'):
    112. break
    113. f.close()
    114. videoWriter.release()
    115. cap.release()
    116. cv2.destroyAllWindows()

    然后效果就和我们宿舍楼下差不多了~ 

    我年轻的时候,我大概比现在帅个几百倍吧,哎。

    二、总代码

    上文其实把登录和注册最后一部分代码放在一起就是了,这里就不再复制粘贴了,相关权重文件下载链接:opencv/data at master · opencv/opencv · GitHub

    当然本项目还有很多需要优化的地方,比如设置用户不能重复、考勤打卡每天只能一次、把csv改为链接成数据库等等,后续代码优化完成后就可以部署然后和室友**了。

  • 相关阅读:
    PAT(Advanced Level) Practice(with python)——1118 Birds in Forest
    使用Tokeniser估算GPT和LLM服务的查询成本
    【如何学习CAN总线测试】——AUTOSAR网络管理测试
    国际结算重点完整版
    qt笔记之qml下拉标签组合框增加发送按钮发送标签内容
    MacOS ventura跳过配置锁
    Materials Studio8.0
    02129 信息资源建设《信息资源管理(第2版) 电子工业出版社 肖明著》考点整理
    Android移动安全攻防实战 第二章
    Flink CDC 2.3 发布,持续优化性能,更多连接器支持增量快照,新增 Db2 支持
  • 原文地址:https://blog.csdn.net/suic009/article/details/127382811