飞道的博客

openCV实战项目--人脸考勤

315人阅读  评论(0)

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

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

  • 人脸注册:将人脸特征存储进数据库,这里用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改为链接成数据库等等,后续代码优化完成后就可以部署然后和室友**了。


转载:https://blog.csdn.net/suic009/article/details/127382811
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场