feat: 增加人脸识别相关功能
This commit is contained in:
parent
addf8e771a
commit
e71d5af84a
|
@ -29,3 +29,7 @@ class PwResetSerializer(serializers.Serializer):
|
|||
class SecretLoginSerializer(serializers.Serializer):
|
||||
username = serializers.CharField(label="用户名")
|
||||
secret = serializers.CharField(label="密钥")
|
||||
|
||||
|
||||
class FaceLoginSerializer(serializers.Serializer):
|
||||
base64 = serializers.CharField()
|
|
@ -21,4 +21,4 @@ def validate_password(password):
|
|||
if re.match(pattern, password):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
|
|
|
@ -3,7 +3,7 @@ from django.urls import path
|
|||
from rest_framework_simplejwt.views import TokenRefreshView
|
||||
|
||||
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/'
|
||||
urlpatterns = [
|
||||
|
@ -18,4 +18,5 @@ urlpatterns = [
|
|||
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 + 'reset_password/', PwResetView.as_view(), name='reset_password'),
|
||||
path(API_BASE_URL + 'login_face/', FaceLoginView.as_view(), name='face_login')
|
||||
]
|
||||
|
|
|
@ -17,6 +17,9 @@ from apps.utils.wx import wxClient
|
|||
from django.contrib.auth.hashers import make_password
|
||||
from django.db.models import Q
|
||||
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,
|
||||
|
@ -244,3 +247,49 @@ class PwResetView(CreateAPIView):
|
|||
user.save()
|
||||
return Response()
|
||||
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)
|
||||
|
|
|
@ -68,6 +68,9 @@ class EmployeeCreateUpdateSerializer(CustomModelSerializer):
|
|||
old_job_state = instance.job_state
|
||||
old_name = instance.name
|
||||
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:
|
||||
instance.user.name = instance.name
|
||||
instance.user.save()
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
from rest_framework.exceptions import ParseError, APIException
|
||||
import logging
|
||||
import os
|
||||
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.third.errors import DH_REQUEST_ERROR
|
||||
from apps.third.dahua import dhClient
|
||||
from apps.third.models import TDevice
|
||||
from apps.third.tapis import dhapis
|
||||
from apps.third.dahua import dhClient
|
||||
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')
|
||||
|
||||
|
||||
|
@ -254,8 +258,8 @@ class HrmService:
|
|||
|
||||
@classmethod
|
||||
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.vm.models import Visit
|
||||
|
||||
# 进行相关方/访客项目更新
|
||||
Visit.objects.filter(state=Visit.V_ENTER, visitors__employee__id_number=id_number).update(
|
||||
|
@ -353,4 +357,79 @@ 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
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from celery import shared_task
|
||||
from apps.hrm.models import Employee, ClockRecord
|
||||
from dateutil import tz
|
||||
import time
|
||||
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.tapis import dhapis
|
||||
from apps.hrm.services import HrmService
|
||||
from django.utils import timezone
|
||||
from apps.utils.tasks import CustomTask
|
||||
import time
|
||||
|
||||
import os
|
||||
from django.conf import settings
|
||||
import shutil
|
||||
|
||||
@shared_task
|
||||
def update_all_employee_not_atwork():
|
||||
|
@ -85,4 +89,39 @@ def correct_card_time():
|
|||
_, res = dhClient.request(**dhapis['card_update'], json=json_data)
|
||||
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)
|
||||
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)
|
||||
|
|
@ -9,6 +9,12 @@ import requests
|
|||
from io import BytesIO
|
||||
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):
|
||||
_instance = {}
|
||||
|
|
Loading…
Reference in New Issue