feat: 增加人脸识别相关功能
This commit is contained in:
parent
addf8e771a
commit
e71d5af84a
|
@ -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()
|
|
@ -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')
|
||||||
]
|
]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 = {}
|
||||||
|
|
Loading…
Reference in New Issue