feat: 增加人脸识别相关功能

This commit is contained in:
caoqianming 2023-09-11 10:42:34 +08:00
parent addf8e771a
commit e71d5af84a
8 changed files with 201 additions and 20 deletions

View File

@ -29,3 +29,7 @@ class PwResetSerializer(serializers.Serializer):
class SecretLoginSerializer(serializers.Serializer): class SecretLoginSerializer(serializers.Serializer):
username = serializers.CharField(label="用户名") username = serializers.CharField(label="用户名")
secret = serializers.CharField(label="密钥") secret = serializers.CharField(label="密钥")
class FaceLoginSerializer(serializers.Serializer):
base64 = serializers.CharField()

View File

@ -3,7 +3,7 @@ from django.urls import path
from rest_framework_simplejwt.views import TokenRefreshView from rest_framework_simplejwt.views import TokenRefreshView
from apps.auth1.views import (CodeLogin, LoginView, LogoutView, PwResetView, from apps.auth1.views import (CodeLogin, LoginView, LogoutView, PwResetView,
SecretLogin, SendCode, TokenBlackView, WxLogin, WxmpLogin, TokenLoginView) SecretLogin, SendCode, TokenBlackView, WxLogin, WxmpLogin, TokenLoginView, FaceLoginView)
API_BASE_URL = 'api/auth/' API_BASE_URL = 'api/auth/'
urlpatterns = [ urlpatterns = [
@ -18,4 +18,5 @@ urlpatterns = [
path(API_BASE_URL + 'sms_code/', SendCode.as_view(), name='sms_code_send'), path(API_BASE_URL + 'sms_code/', SendCode.as_view(), name='sms_code_send'),
path(API_BASE_URL + 'logout/', LogoutView.as_view(), name='session_logout'), path(API_BASE_URL + 'logout/', LogoutView.as_view(), name='session_logout'),
path(API_BASE_URL + 'reset_password/', PwResetView.as_view(), name='reset_password'), path(API_BASE_URL + 'reset_password/', PwResetView.as_view(), name='reset_password'),
path(API_BASE_URL + 'login_face/', FaceLoginView.as_view(), name='face_login')
] ]

View File

@ -17,6 +17,9 @@ from apps.utils.wx import wxClient
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
from django.db.models import Q from django.db.models import Q
from apps.auth1.services import validate_password from apps.auth1.services import validate_password
import base64
from apps.utils.tools import tran64
from apps.auth1.serializers import FaceLoginSerializer
from apps.auth1.serializers import (CodeLoginSerializer, LoginSerializer, from apps.auth1.serializers import (CodeLoginSerializer, LoginSerializer,
@ -244,3 +247,49 @@ class PwResetView(CreateAPIView):
user.save() user.save()
return Response() return Response()
raise ParseError('账户不存在或已禁用') raise ParseError('账户不存在或已禁用')
class FaceLoginView(CreateAPIView):
"""人脸识别登录
人脸识别登录
"""
authentication_classes = []
permission_classes = []
serializer_class = FaceLoginSerializer
def create(self, request, *args, **kwargs):
"""
人脸识别登录
"""
from apps.hrm.services import HrmService
base64_data = base64.urlsafe_b64decode(tran64(request.data.get('base64').replace(' ', '+')))
ep, msg = HrmService.face_find_from_base64(base64_data)
user = ep.user
if user:
refresh = RefreshToken.for_user(ep.user)
# # 可设为在岗
# now = timezone.now()
# now_local = timezone.localtime()
# if 8<=now_local.hour<=17:
# ins, created = ClockRecord.objects.get_or_create(
# create_by = user, create_time__hour__range = [8,18],
# create_time__year=now_local.year, create_time__month=now_local.month,
# create_time__day=now_local.day,
# defaults={
# 'type':ClockRecord.ClOCK_WORK1,
# 'create_by':user,
# 'create_time':now
# })
# # 设为在岗
# if created:
# Employee.objects.filter(user=user).update(is_atwork=True, last_check_time=now)
return Response({
'refresh': str(refresh),
'access': str(refresh.access_token),
'username':user.username,
'name':user.name
})
return Response(msg, status=400)

View File

@ -68,6 +68,9 @@ class EmployeeCreateUpdateSerializer(CustomModelSerializer):
old_job_state = instance.job_state old_job_state = instance.job_state
old_name = instance.name old_name = instance.name
instance = super().update(instance, validated_data) instance = super().update(instance, validated_data)
if old_photo != instance.photo: # 如果照片有变动,需要更新人脸库
from apps.hrm.tasks import delete_face_pkl
delete_face_pkl.delay(instance.id)
if instance.user and instance != old_name: if instance.user and instance != old_name:
instance.user.name = instance.name instance.user.name = instance.name
instance.user.save() instance.user.save()

View File

@ -1,17 +1,21 @@
from rest_framework.exceptions import ParseError, APIException
import logging import logging
import os
import time import time
import uuid
from datetime import datetime, timedelta
from dateutil import tz
from django.conf import settings
from django.utils import timezone
from django.core.cache import cache
from apps.system.models import User
from apps.hrm.models import ClockRecord, Employee from apps.hrm.models import ClockRecord, Employee
from apps.third.errors import DH_REQUEST_ERROR from apps.third.dahua import dhClient
from apps.third.models import TDevice from apps.third.models import TDevice
from apps.third.tapis import dhapis from apps.third.tapis import dhapis
from apps.third.dahua import dhClient
from apps.utils.tools import rannum, ranstr from apps.utils.tools import rannum, ranstr
from datetime import datetime, timedelta
from django.utils import timezone
from dateutil import tz
from threading import Thread
from django.conf import settings
myLogger = logging.getLogger('log') myLogger = logging.getLogger('log')
@ -254,8 +258,8 @@ class HrmService:
@classmethod @classmethod
def swipe_next(cls, nodeCode: str, id_number: str, swip_time: str, e_type:int, detail:dict): def swipe_next(cls, nodeCode: str, id_number: str, swip_time: str, e_type:int, detail:dict):
from apps.vm.models import Visit
from apps.rpm.models import Rpj from apps.rpm.models import Rpj
from apps.vm.models import Visit
# 进行相关方/访客项目更新 # 进行相关方/访客项目更新
Visit.objects.filter(state=Visit.V_ENTER, visitors__employee__id_number=id_number).update( Visit.objects.filter(state=Visit.V_ENTER, visitors__employee__id_number=id_number).update(
@ -354,3 +358,78 @@ class HrmService:
# 此处可触发安全帽事件逻辑 # 此处可触发安全帽事件逻辑
@classmethod
def get_facedata_from_img(cls, img_path):
import face_recognition
try:
photo_path = settings.BASE_DIR + img_path
picture_of_me = face_recognition.load_image_file(photo_path)
my_face_encoding = face_recognition.face_encodings(picture_of_me, num_jitters=2)[0]
face_data_list = my_face_encoding.tolist()
return face_data_list, ''
except:
return None, '人脸数据获取失败请重新上传图片'
@classmethod
def face_compare_from_base64(cls, base64_data):
import face_recognition
filename = str(uuid.uuid4())
filepath = settings.BASE_DIR +'/temp/face_' + filename +'.png'
with open(filepath, 'wb') as f:
f.write(base64_data)
try:
unknown_picture = face_recognition.load_image_file(filepath)
unknown_face_encoding = face_recognition.face_encodings(unknown_picture, num_jitters=2)[0]
os.remove(filepath)
except:
os.remove(filepath)
return None, '识别失败,请调整位置'
# 匹配人脸库
face_datas = cache.get('face_datas', None)
if face_datas is None:
from apps.hrm.tasks import update_all_facedata_cache
update_all_facedata_cache()
face_datas = cache.get('face_datas')
results = face_recognition.compare_faces(face_datas['datas'],
unknown_face_encoding, tolerance=0.45)
for index, value in enumerate(results):
if value:
# 识别成功
user = User.objects.get(id=face_datas['users'][index])
return user, ''
return None, '人脸未匹配,请调整位置'
@classmethod
def face_find_from_base64(cls, base64_data):
from deepface import DeepFace
img_name = str(uuid.uuid4())
img_path = settings.BASE_DIR +'/temp/face_' + img_name +'.png'
with open(img_path, 'wb') as f:
f.write(base64_data)
db_path = os.path.join(settings.BASE_DIR, 'media/face')
df = DeepFace.find(img_path=img_path, db_path=db_path, model_name='Facenet512')
if df.shape[0] > 0:
matched = df.iloc[0].identity
epId = matched.split('/')[-1].split('.')[0]
return Employee.objects.get(id=epId), ''
else:
return None, '人脸未匹配,请调整位置'
# def VGGFaceloadModel(weight_path):
# from deepface.basemodels import VGGFace
# from keras.src.engine.training import Model
# model = VGGFace.baseModel()
# model.load_weights(weight_path)
# vgg_face_descriptor = Model(inputs=model.layers[0].input, outputs=model.layers[-2].output)
# return vgg_face_descriptor
# def Facenet512loadModel(weight_path):
# from deepface.basemodels import Facenet
# model = Facenet.InceptionResNetV2(dimension=512)
# model.load_weights(weight_path)
# return model

View File

@ -1,16 +1,20 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from celery import shared_task import time
from apps.hrm.models import Employee, ClockRecord
from dateutil import tz
from datetime import datetime, timedelta from datetime import datetime, timedelta
from celery import shared_task
from dateutil import tz
from django.core.cache import cache
from apps.hrm.models import Employee
from apps.hrm.services import HrmService
from apps.third.dahua import dhClient from apps.third.dahua import dhClient
from apps.third.tapis import dhapis from apps.third.tapis import dhapis
from apps.hrm.services import HrmService
from django.utils import timezone
from apps.utils.tasks import CustomTask from apps.utils.tasks import CustomTask
import time import os
from django.conf import settings
import shutil
@shared_task @shared_task
def update_all_employee_not_atwork(): def update_all_employee_not_atwork():
@ -86,3 +90,38 @@ def correct_card_time():
HrmService.save(ep, {'dh_face_card_start': start_time_str, 'dh_face_card_end': end_time_new_str}) HrmService.save(ep, {'dh_face_card_start': start_time_str, 'dh_face_card_end': end_time_new_str})
print('已更新-' + ep.name + '-' + dh_face_card + '-' + end_time_new_str) print('已更新-' + ep.name + '-' + dh_face_card + '-' + end_time_new_str)
time.sleep(1) time.sleep(1)
@shared_task
def update_all_facedata_cache():
"""
更新人脸数据缓存
"""
facedata_queyset = Employee.objects.filter(face_data__isnull=False,
user__is_active=True).values('user', 'face_data')
face_users = []
face_datas = []
for i in facedata_queyset:
face_users.append(i['user'])
face_datas.append(i['face_data'])
face_data_dict = {"users": face_users, "datas": face_datas}
cache.set('face_datas', face_data_dict)
@shared_task(base=CustomTask)
def delete_face_pkl(epId):
"""
尝试删除人脸库缓存文件
"""
if epId:
ep = Employee.objects.get(id=epId)
photo_path = settings.BASE_DIR + ep.photo
face_path = os.path.join(settings.FACE_PATH, f'{epId}.jpg')
shutil.copy(photo_path, face_path)
file_path = os.path.join(settings.BASE_DIR, 'media/face/representations_facenet512.pkl')
if os.path.exists(file_path):
try:
os.remove(file_path)
except Exception as e:
delete_face_pkl.apply_async(countdown=5)

View File

@ -9,6 +9,12 @@ import requests
from io import BytesIO from io import BytesIO
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
def tran64(s):
missing_padding = len(s) % 4
if missing_padding != 0:
s = s+'='* (4 - missing_padding)
return s
def singleton(cls): def singleton(cls):
_instance = {} _instance = {}