From 96ac80361e8ca85ce66c70b4fa11c682c5039732 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 11 Sep 2023 15:21:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BA=BA=E8=84=B8=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E5=9C=A8=E6=95=B0=E6=8D=AE=E5=BA=93=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hrm/apps.py | 7 +- .../0012_employee_facenet512_data.py | 18 ++++ apps/hrm/models.py | 2 +- apps/hrm/serializers.py | 12 ++- apps/hrm/services.py | 34 +++---- apps/hrm/tasks.py | 12 ++- apps/utils/face.py | 90 +++++++++++++++++++ server/settings.py | 2 +- 8 files changed, 150 insertions(+), 27 deletions(-) create mode 100644 apps/hrm/migrations/0012_employee_facenet512_data.py create mode 100644 apps/utils/face.py diff --git a/apps/hrm/apps.py b/apps/hrm/apps.py index 5128c915..4fd510b3 100755 --- a/apps/hrm/apps.py +++ b/apps/hrm/apps.py @@ -1,4 +1,5 @@ from django.apps import AppConfig +from django.core.cache import cache class HrmConfig(AppConfig): @@ -6,4 +7,8 @@ class HrmConfig(AppConfig): verbose_name = '人力资源管理' def ready(self): - import apps.hrm.signals + if cache.get('update_global_face_pd_task', True): + from apps.hrm.tasks import update_global_face_pd + update_global_face_pd.delay() + cache.set('update_global_face_pd_task', False, timeout=30) + return super().ready() diff --git a/apps/hrm/migrations/0012_employee_facenet512_data.py b/apps/hrm/migrations/0012_employee_facenet512_data.py new file mode 100644 index 00000000..fa320232 --- /dev/null +++ b/apps/hrm/migrations/0012_employee_facenet512_data.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2023-09-11 07:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hrm', '0011_alter_certificate_number'), + ] + + operations = [ + migrations.AddField( + model_name='employee', + name='facenet512_data', + field=models.JSONField(blank=True, null=True, verbose_name='人脸数据'), + ), + ] diff --git a/apps/hrm/models.py b/apps/hrm/models.py index 5118b11c..6bab5c49 100755 --- a/apps/hrm/models.py +++ b/apps/hrm/models.py @@ -44,7 +44,7 @@ class Employee(CommonBModel): not_work_remark = models.CharField('当前未打卡说明', null=True, blank=True, max_length=200) third_info = models.JSONField('三方信息', default=dict, null=False, blank=True) # 主要是定位卡信息 post = models.ForeignKey(Post, verbose_name='所属岗位', on_delete=models.SET_NULL, null=True, blank=True) - + facenet512_data = models.JSONField('人脸数据', null=True, blank=True) class Meta: verbose_name = '员工补充信息' verbose_name_plural = verbose_name diff --git a/apps/hrm/serializers.py b/apps/hrm/serializers.py index 77684dbc..5e6f95ef 100755 --- a/apps/hrm/serializers.py +++ b/apps/hrm/serializers.py @@ -13,6 +13,8 @@ from apps.system.serializers import DeptSimpleSerializer, UserSimpleSerializer from django.db import transaction from django.core.cache import cache from apps.utils.tools import check_id_number_e, get_info_from_id +from rest_framework.exceptions import ParseError +from django.conf import settings class EmployeeSimpleSerializer(CustomModelSerializer): @@ -69,8 +71,14 @@ class EmployeeCreateUpdateSerializer(CustomModelSerializer): 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) + face_data, msg = HrmService.get_facedata_from_img(settings.BASE_DIR + instance.photo) + if face_data: + instance.facenet512_data = face_data + instance.save() + else: + raise ParseError(msg) + from apps.hrm.tasks import update_global_face_pd + update_global_face_pd.delay() if instance.user and instance != old_name: instance.user.name = instance.name instance.user.save() diff --git a/apps/hrm/services.py b/apps/hrm/services.py index e583c36a..8b1838a0 100755 --- a/apps/hrm/services.py +++ b/apps/hrm/services.py @@ -15,9 +15,11 @@ from apps.third.dahua import dhClient from apps.third.models import TDevice from apps.third.tapis import dhapis from apps.utils.tools import rannum, ranstr +from apps.utils.face import face_find myLogger = logging.getLogger('log') +global_face_df = None #全局人脸库dataframe class HrmService: @@ -404,33 +406,25 @@ class HrmService: @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 +'.jpg' with open(img_path, 'wb') as f: f.write(base64_data) - db_path = os.path.join(settings.BASE_DIR, 'media/face') - dfs = DeepFace.find(img_path=img_path, db_path=db_path, model_name='Facenet512') + # db_path = os.path.join(settings.BASE_DIR, 'media/face') + # cache_face_db = cache + dfs = face_find(img_path=img_path, global_df=global_face_df, model_name='Facenet512') df = dfs[0] if not df.empty: matched = df.iloc[0].identity - epId = matched.split('/')[-1].split('.')[0] - return Employee.objects.get(id=epId), '' + return Employee.objects.get(id=matched), '' 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 + @classmethod + def get_facedata_from_img(cls, img_path): + try: + from deepface import DeepFace + embedding_objs = DeepFace.represent(img_path=img_path, model_name='Facenet512') + return embedding_objs[0]["embedding"], '' + except Exception as e: + return None, '人脸数据获取失败请重新上传图片' \ No newline at end of file diff --git a/apps/hrm/tasks.py b/apps/hrm/tasks.py index e950e9c3..27f9ce64 100755 --- a/apps/hrm/tasks.py +++ b/apps/hrm/tasks.py @@ -8,7 +8,7 @@ from dateutil import tz from django.core.cache import cache from apps.hrm.models import Employee -from apps.hrm.services import HrmService +from apps.hrm.services import HrmService, global_face_df from apps.third.dahua import dhClient from apps.third.tapis import dhapis from apps.utils.tasks import CustomTask @@ -124,4 +124,12 @@ def delete_face_pkl(epId): os.remove(file_path) except Exception as e: delete_face_pkl.apply_async(countdown=5) - \ No newline at end of file + + +@shared_task(base=CustomTask) +def update_global_face_pd(): + import pandas as pd + facedata = Employee.objects.filter(facenet512_data__isnull=False, + user__is_active=True).values_list('id', 'facenet512_data', flat=True) + cache.set('global_face_data', facedata, timeout=None) + global_face_df = pd.DataFrame(list(facedata), columns=["identity", "Facenet512_representation"]) diff --git a/apps/utils/face.py b/apps/utils/face.py new file mode 100644 index 00000000..cead337f --- /dev/null +++ b/apps/utils/face.py @@ -0,0 +1,90 @@ +from deepface import DeepFace +from deepface.commons import functions, distance as dst +import pandas as pd +import time + +def face_find( + img_path, + global_df, + model_name="Facenet512", + distance_metric="cosine", + enforce_detection=True, + detector_backend="opencv", + align=True, + normalization="base", + silent=False, +): + tic = time.time() + target_size = functions.find_target_size(model_name=model_name) + # now, we got representations for facial database + df = global_df + + # img path might have more than once face + target_objs = functions.extract_faces( + img=img_path, + target_size=target_size, + detector_backend=detector_backend, + grayscale=False, + enforce_detection=enforce_detection, + align=align, + ) + + resp_obj = [] + + for target_img, target_region, _ in target_objs: + target_embedding_obj = DeepFace.represent( + img_path=target_img, + model_name=model_name, + enforce_detection=enforce_detection, + detector_backend="skip", + align=align, + normalization=normalization, + ) + + target_representation = target_embedding_obj[0]["embedding"] + + result_df = df.copy() # df will be filtered in each img + result_df["source_x"] = target_region["x"] + result_df["source_y"] = target_region["y"] + result_df["source_w"] = target_region["w"] + result_df["source_h"] = target_region["h"] + + distances = [] + for index, instance in df.iterrows(): + source_representation = instance[f"{model_name}_representation"] + + if distance_metric == "cosine": + distance = dst.findCosineDistance(source_representation, target_representation) + elif distance_metric == "euclidean": + distance = dst.findEuclideanDistance(source_representation, target_representation) + elif distance_metric == "euclidean_l2": + distance = dst.findEuclideanDistance( + dst.l2_normalize(source_representation), + dst.l2_normalize(target_representation), + ) + else: + raise ValueError(f"invalid distance metric passes - {distance_metric}") + + distances.append(distance) + + # --------------------------- + + result_df[f"{model_name}_{distance_metric}"] = distances + + threshold = dst.findThreshold(model_name, distance_metric) + result_df = result_df.drop(columns=[f"{model_name}_representation"]) + result_df = result_df[result_df[f"{model_name}_{distance_metric}"] <= threshold] + result_df = result_df.sort_values( + by=[f"{model_name}_{distance_metric}"], ascending=True + ).reset_index(drop=True) + + resp_obj.append(result_df) + + # ----------------------------------- + + toc = time.time() + + if not silent: + print("find function lasts ", toc - tic, " seconds") + + return resp_obj \ No newline at end of file diff --git a/server/settings.py b/server/settings.py index 3a9c6725..bc2aa858 100755 --- a/server/settings.py +++ b/server/settings.py @@ -202,7 +202,7 @@ MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 人脸库配置 -# 如果地址不存在,则自动创建 +# 如果地址不存在,则自动创建/现在直接存库可不用 FACE_PATH= os.path.join(BASE_DIR, 'media/face') if not os.path.exists(FACE_PATH): os.makedirs(FACE_PATH)