leonelhs commited on
Commit
7dcb9e0
·
1 Parent(s): 9363291

init space code

Browse files
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ .idea/
2
+ __pycache__/
3
+ models/__pycache__/
4
+ utils/__pycache__/
5
+ playground.py
.idea/.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
.idea/FaceAnalysis.iml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="PYTHON_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$" />
5
+ <orderEntry type="jdk" jdkName="Torcho" jdkType="Python SDK" />
6
+ <orderEntry type="sourceFolder" forTests="false" />
7
+ </component>
8
+ </module>
.idea/inspectionProfiles/Project_Default.xml ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
5
+ <option name="ignoredPackages">
6
+ <value>
7
+ <list size="78">
8
+ <item index="0" class="java.lang.String" itemvalue="human-pose-estimator" />
9
+ <item index="1" class="java.lang.String" itemvalue="torch" />
10
+ <item index="2" class="java.lang.String" itemvalue="numpy" />
11
+ <item index="3" class="java.lang.String" itemvalue="gradio" />
12
+ <item index="4" class="java.lang.String" itemvalue="opencv-python-headless" />
13
+ <item index="5" class="java.lang.String" itemvalue="pymatting" />
14
+ <item index="6" class="java.lang.String" itemvalue="scipy" />
15
+ <item index="7" class="java.lang.String" itemvalue="pillow" />
16
+ <item index="8" class="java.lang.String" itemvalue="rembg" />
17
+ <item index="9" class="java.lang.String" itemvalue="carvekit" />
18
+ <item index="10" class="java.lang.String" itemvalue="scikit-image" />
19
+ <item index="11" class="java.lang.String" itemvalue="paddlepaddle" />
20
+ <item index="12" class="java.lang.String" itemvalue="paddlehub" />
21
+ <item index="13" class="java.lang.String" itemvalue="bisnet" />
22
+ <item index="14" class="java.lang.String" itemvalue="torchvision" />
23
+ <item index="15" class="java.lang.String" itemvalue="realesrgan" />
24
+ <item index="16" class="java.lang.String" itemvalue="huggingface-hub" />
25
+ <item index="17" class="java.lang.String" itemvalue="opencv-python" />
26
+ <item index="18" class="java.lang.String" itemvalue="yapf" />
27
+ <item index="19" class="java.lang.String" itemvalue="gfpgan" />
28
+ <item index="20" class="java.lang.String" itemvalue="lmdb" />
29
+ <item index="21" class="java.lang.String" itemvalue="tqdm" />
30
+ <item index="22" class="java.lang.String" itemvalue="filetype" />
31
+ <item index="23" class="java.lang.String" itemvalue="onnxruntime" />
32
+ <item index="24" class="java.lang.String" itemvalue="basicsr" />
33
+ <item index="25" class="java.lang.String" itemvalue="onnx" />
34
+ <item index="26" class="java.lang.String" itemvalue="psutil" />
35
+ <item index="27" class="java.lang.String" itemvalue="torchaudio" />
36
+ <item index="28" class="java.lang.String" itemvalue="timm" />
37
+ <item index="29" class="java.lang.String" itemvalue="gradio_imageslider" />
38
+ <item index="30" class="java.lang.String" itemvalue="transformers" />
39
+ <item index="31" class="java.lang.String" itemvalue="pydantic" />
40
+ <item index="32" class="java.lang.String" itemvalue="huggingface_hub" />
41
+ <item index="33" class="java.lang.String" itemvalue="monotonic-align" />
42
+ <item index="34" class="java.lang.String" itemvalue="einops-exts" />
43
+ <item index="35" class="java.lang.String" itemvalue="phonemizer" />
44
+ <item index="36" class="java.lang.String" itemvalue="txtsplit" />
45
+ <item index="37" class="java.lang.String" itemvalue="nltk" />
46
+ <item index="38" class="java.lang.String" itemvalue="SoundFile" />
47
+ <item index="39" class="java.lang.String" itemvalue="librosa" />
48
+ <item index="40" class="java.lang.String" itemvalue="gruut" />
49
+ <item index="41" class="java.lang.String" itemvalue="munch" />
50
+ <item index="42" class="java.lang.String" itemvalue="cached-path" />
51
+ <item index="43" class="java.lang.String" itemvalue="gdown" />
52
+ <item index="44" class="java.lang.String" itemvalue="diffusers" />
53
+ <item index="45" class="java.lang.String" itemvalue="matplotlib" />
54
+ <item index="46" class="java.lang.String" itemvalue="skimage" />
55
+ <item index="47" class="java.lang.String" itemvalue="ultralytics" />
56
+ <item index="48" class="java.lang.String" itemvalue="Pillow" />
57
+ <item index="49" class="java.lang.String" itemvalue="dis_bg_remover" />
58
+ <item index="50" class="java.lang.String" itemvalue="kiui" />
59
+ <item index="51" class="java.lang.String" itemvalue="xatlas" />
60
+ <item index="52" class="java.lang.String" itemvalue="open-clip-torch" />
61
+ <item index="53" class="java.lang.String" itemvalue="nvdiffrast" />
62
+ <item index="54" class="java.lang.String" itemvalue="pymeshlab" />
63
+ <item index="55" class="java.lang.String" itemvalue="opencv-contrib-python-headless" />
64
+ <item index="56" class="java.lang.String" itemvalue="xformers" />
65
+ <item index="57" class="java.lang.String" itemvalue="pygltflib" />
66
+ <item index="58" class="java.lang.String" itemvalue="einops" />
67
+ <item index="59" class="java.lang.String" itemvalue="trimesh" />
68
+ <item index="60" class="java.lang.String" itemvalue="deepface" />
69
+ <item index="61" class="java.lang.String" itemvalue="pyside6" />
70
+ <item index="62" class="java.lang.String" itemvalue="dlib" />
71
+ <item index="63" class="java.lang.String" itemvalue="qtawesome" />
72
+ <item index="64" class="java.lang.String" itemvalue="tensorflow" />
73
+ <item index="65" class="java.lang.String" itemvalue="tensorboard" />
74
+ <item index="66" class="java.lang.String" itemvalue="tensorboardX" />
75
+ <item index="67" class="java.lang.String" itemvalue="imgaug" />
76
+ <item index="68" class="java.lang.String" itemvalue="torchsde" />
77
+ <item index="69" class="java.lang.String" itemvalue="onnxruntime-gpu" />
78
+ <item index="70" class="java.lang.String" itemvalue="alembic" />
79
+ <item index="71" class="java.lang.String" itemvalue="comfyui-embedded-docs" />
80
+ <item index="72" class="java.lang.String" itemvalue="comfyui-frontend-package" />
81
+ <item index="73" class="java.lang.String" itemvalue="av" />
82
+ <item index="74" class="java.lang.String" itemvalue="SQLAlchemy" />
83
+ <item index="75" class="java.lang.String" itemvalue="spandrel" />
84
+ <item index="76" class="java.lang.String" itemvalue="segment_anything" />
85
+ <item index="77" class="java.lang.String" itemvalue="comfyui-workflow-templates" />
86
+ </list>
87
+ </value>
88
+ </option>
89
+ </inspection_tool>
90
+ <inspection_tool class="PyPep8Inspection" enabled="true" level="INFORMATION" enabled_by_default="true">
91
+ <option name="ignoredErrors">
92
+ <list>
93
+ <option value="E301" />
94
+ </list>
95
+ </option>
96
+ </inspection_tool>
97
+ <inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
98
+ <option name="ignoredErrors">
99
+ <list>
100
+ <option value="N802" />
101
+ <option value="N803" />
102
+ </list>
103
+ </option>
104
+ </inspection_tool>
105
+ </profile>
106
+ </component>
.idea/inspectionProfiles/profiles_settings.xml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="USE_PROJECT_PROFILE" value="false" />
4
+ <version value="1.0" />
5
+ </settings>
6
+ </component>
.idea/misc.xml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Black">
4
+ <option name="sdkName" value="$USER_HOME$/miniconda3" />
5
+ </component>
6
+ <component name="ProjectRootManager" version="2" project-jdk-name="Torcho" project-jdk-type="Python SDK" />
7
+ </project>
.idea/modules.xml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/FaceAnalysis.iml" filepath="$PROJECT_DIR$/.idea/FaceAnalysis.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
README.md CHANGED
@@ -11,4 +11,17 @@ license: mit
11
  short_description: extracts face features, gender, age, landmarks, ...
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  short_description: extracts face features, gender, age, landmarks, ...
12
  ---
13
 
14
+ ## Unofficial FaceAnalysis Implementation
15
+
16
+ This lightweight FaceAnalysis implementation contains only the core components
17
+ required for quick deployment or integration into other projects.
18
+
19
+ ## Acknowledgments
20
+
21
+ This work preserves key functionality from the original authors:
22
+ - [DeepInsight](https://github.com/deepinsight/insightface)
23
+
24
+ ## Contact
25
+
26
+ For questions, comments, or feedback, please contact:
27
+ 📧 **leonelhs@gmail.com**
app.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #######################################################################################
2
+ #
3
+ # MIT License
4
+ #
5
+ # Copyright (c) [2025] [leonelhs@gmail.com]
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # of this software and associated documentation files (the "Software"), to deal
9
+ # in the Software without restriction, including without limitation the rights
10
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ # copies of the Software, and to permit persons to whom the Software is
12
+ # furnished to do so, subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included in all
15
+ # copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ # SOFTWARE.
24
+ #
25
+ #######################################################################################
26
+ #
27
+ # - [Demo] - [https://huggingface.co/spaces/leonelhs/FaceAnalysis]
28
+ #
29
+ # Source code is based on or inspired by several projects.
30
+ # For more details and proper attribution, please refer to the following resources:
31
+ #
32
+ # - [Deepinsight] - [https://github.com/deepinsight/insightface]
33
+ # - [FaceFusion] - [https://github.com/facefusion/facefusion]
34
+ #
35
+ from itertools import islice
36
+
37
+ import gradio as gr
38
+ from huggingface_hub import hf_hub_download
39
+
40
+ from face_analysis import FaceAnalysis
41
+
42
+ REPO_ID = "leonelhs/insightface"
43
+ model_inswapper_path = hf_hub_download(repo_id=REPO_ID, filename="inswapper_128.onnx")
44
+
45
+ face_analyser = FaceAnalysis()
46
+
47
+ def predict(image_path):
48
+
49
+ faces = face_analyser.get(image_path)
50
+ sections = []
51
+
52
+ if len(faces) > 0:
53
+ for face in faces:
54
+ box = face.bbox
55
+ label = f"Gender {face.sex} Age {face.age}"
56
+ sections.append(((int(box[0]), int(box[1]), int(box[2]), int(box[3])), label))
57
+ return image_path, sections
58
+ else:
59
+ raise gr.Error("No faces were found!")
60
+
61
+ with gr.Blocks(title="FaceAnalyser") as app:
62
+ navbar = gr.Navbar(visible=True, main_page_name="Workspace")
63
+ gr.Markdown("## Face Analyser")
64
+ with gr.Row():
65
+ with gr.Column(scale=1):
66
+ with gr.Row():
67
+ source_image = gr.Image(type="filepath", label="Face image")
68
+ image_btn = gr.Button("Analyze face")
69
+ with gr.Column(scale=1):
70
+ with gr.Row():
71
+ output_image = gr.AnnotatedImage(label="Faces detected")
72
+ image_btn.click(
73
+ fn=predict,
74
+ inputs=[source_image],
75
+ outputs=output_image,
76
+ )
77
+
78
+ with app.route("Readme", "/readme"):
79
+ with open("README.md") as f:
80
+ for line in islice(f, 12, None):
81
+ gr.Markdown(line.strip())
82
+
83
+ app.launch(share=False, debug=True, show_error=True, mcp_server=True, pwa=True)
84
+ app.queue()
85
+
86
+
87
+
face_analysis.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FaceAnalysis is the core library used for facial region detection and extraction.
2
+ # Future contributors and maintainers should review the official or reference
3
+ # implementations for details and updates:
4
+ # https://github.com/deepinsight/insightface/blob/master/python-package/insightface/app/face_analysis.py
5
+ #
6
+ # Demo: https://huggingface.co/spaces/leonelhs/FaceAnalysis
7
+
8
+
9
+ # -*- coding: utf-8 -*-
10
+ # @Organization : insightface.ai
11
+ # @Author : Jia Guo
12
+ # @Time : 2021-05-04
13
+ # @Function :
14
+
15
+
16
+ from __future__ import division
17
+
18
+ import cv2
19
+ import onnxruntime
20
+
21
+ __all__ = ['FaceAnalysis']
22
+
23
+ from utils.common import Face
24
+ from models.arcface_onnx import ArcFaceONNX
25
+ from models.attribute import Attribute
26
+ from models.landmark import Landmark
27
+ from models.retinaface import RetinaFace
28
+ from huggingface_hub import hf_hub_download
29
+
30
+ REPO_ID = "leonelhs/insightface"
31
+
32
+ model_detector_path = hf_hub_download(repo_id=REPO_ID, filename="det_10g.onnx")
33
+ model_landmark_3d_68_path = hf_hub_download(repo_id=REPO_ID, filename="1k3d68.onnx")
34
+ model_landmark_2d_106_path = hf_hub_download(repo_id=REPO_ID, filename="2d106det.onnx")
35
+ model_genderage_path = hf_hub_download(repo_id=REPO_ID, filename="genderage.onnx")
36
+ model_recognition_path = hf_hub_download(repo_id=REPO_ID, filename="w600k_r50.onnx")
37
+
38
+ class FaceAnalysis:
39
+ def __init__(self):
40
+ onnxruntime.set_default_logger_severity(3)
41
+
42
+ self.detector = RetinaFace(model_file=model_detector_path, input_size=(640, 640), det_thresh=0.5)
43
+ self.landmark_3d_68 = Landmark(model_file=model_landmark_3d_68_path)
44
+ self.landmark_2d_106 = Landmark(model_file=model_landmark_2d_106_path)
45
+ self.genderage = Attribute(model_file=model_genderage_path)
46
+ self.recognition = ArcFaceONNX(model_file=model_recognition_path)
47
+
48
+ def get(self, image_path, max_num=0):
49
+ # FIXME: The gender/age detection model expects images in BGR format (as used by OpenCV).
50
+ # Using RGB input significantly reduces prediction accuracy.
51
+ # To maintain reliable results, all image reads must use OpenCV's `cv2.imread`,
52
+ # which loads images in BGR by default.
53
+ img = cv2.imread(image_path, cv2.IMREAD_COLOR)
54
+ bboxes, kpss = self.detector.detect(img, max_num=max_num, metric='default')
55
+ if bboxes.shape[0] == 0:
56
+ return []
57
+ ret = []
58
+ for i in range(bboxes.shape[0]):
59
+ bbox = bboxes[i, 0:4]
60
+ det_score = bboxes[i, 4]
61
+ kps = None
62
+ if kpss is not None:
63
+ kps = kpss[i]
64
+ face = Face(bbox=bbox, kps=kps, det_score=det_score)
65
+ self.landmark_3d_68.get(img, face)
66
+ self.landmark_2d_106.get(img, face)
67
+ self.genderage.get(img, face)
68
+ self.recognition.get(img, face)
69
+ ret.append(face)
70
+ return ret
meanshape_68.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:39ffecf84ba73f0d0d7e49380833ba88713c9fcdec51df4f7ac45a48b8f4cc51
3
+ size 974
models/arcface_onnx.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # https://github.com/deepinsight/insightface/blob/master/python-package/insightface/model_zoo/arcface_onnx.py
2
+
3
+ # -*- coding: utf-8 -*-
4
+ # @Organization : insightface.ai
5
+ # @Author : Jia Guo
6
+ # @Time : 2021-05-04
7
+ # @Function :
8
+
9
+ from __future__ import division
10
+ import numpy as np
11
+ import cv2
12
+ import onnx
13
+ import onnxruntime
14
+ from utils import face_align
15
+
16
+ __all__ = [
17
+ 'ArcFaceONNX',
18
+ ]
19
+
20
+
21
+ class ArcFaceONNX:
22
+ def __init__(self, model_file=None, session=None, ctx_id=0, **kwargs):
23
+ assert model_file is not None
24
+ self.model_file = model_file
25
+ self.session = session
26
+ self.taskname = 'recognition'
27
+ find_sub = False
28
+ find_mul = False
29
+ model = onnx.load(self.model_file)
30
+ graph = model.graph
31
+ for nid, node in enumerate(graph.node[:8]):
32
+ #print(nid, node.name)
33
+ if node.name.startswith('Sub') or node.name.startswith('_minus'):
34
+ find_sub = True
35
+ if node.name.startswith('Mul') or node.name.startswith('_mul'):
36
+ find_mul = True
37
+ if find_sub and find_mul:
38
+ #mxnet arcface model
39
+ input_mean = 0.0
40
+ input_std = 1.0
41
+ else:
42
+ input_mean = 127.5
43
+ input_std = 127.5
44
+ self.input_mean = input_mean
45
+ self.input_std = input_std
46
+ #print('input mean and std:', self.input_mean, self.input_std)
47
+ if self.session is None:
48
+ self.session = onnxruntime.InferenceSession(self.model_file, None)
49
+ input_cfg = self.session.get_inputs()[0]
50
+ input_shape = input_cfg.shape
51
+ input_name = input_cfg.name
52
+ self.input_size = tuple(input_shape[2:4][::-1])
53
+ self.input_shape = input_shape
54
+ outputs = self.session.get_outputs()
55
+ output_names = []
56
+ for out in outputs:
57
+ output_names.append(out.name)
58
+ self.input_name = input_name
59
+ self.output_names = output_names
60
+ assert len(self.output_names)==1
61
+ self.output_shape = outputs[0].shape
62
+
63
+ if ctx_id<0:
64
+ self.session.set_providers(['CPUExecutionProvider'])
65
+
66
+ def get(self, img, face):
67
+ aimg = face_align.norm_crop(img, landmark=face.kps, image_size=self.input_size[0])
68
+ face.embedding = self.get_feat(aimg).flatten()
69
+ return face.embedding
70
+
71
+ def compute_sim(self, feat1, feat2):
72
+ from numpy.linalg import norm
73
+ feat1 = feat1.ravel()
74
+ feat2 = feat2.ravel()
75
+ sim = np.dot(feat1, feat2) / (norm(feat1) * norm(feat2))
76
+ return sim
77
+
78
+ def get_feat(self, imgs):
79
+ if not isinstance(imgs, list):
80
+ imgs = [imgs]
81
+ input_size = self.input_size
82
+
83
+ blob = cv2.dnn.blobFromImages(imgs, 1.0 / self.input_std, input_size,
84
+ (self.input_mean, self.input_mean, self.input_mean), swapRB=True)
85
+ net_out = self.session.run(self.output_names, {self.input_name: blob})[0]
86
+ return net_out
87
+
88
+ def forward(self, batch_data):
89
+ blob = (batch_data - self.input_mean) / self.input_std
90
+ net_out = self.session.run(self.output_names, {self.input_name: blob})[0]
91
+ return net_out
92
+
93
+
models/attribute.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # https://github.com/deepinsight/insightface/blob/master/python-package/insightface/model_zoo/attribute.py
2
+
3
+ # -*- coding: utf-8 -*-
4
+ # @Organization : insightface.ai
5
+ # @Author : Jia Guo
6
+ # @Time : 2021-06-19
7
+ # @Function :
8
+
9
+ from __future__ import division
10
+ import numpy as np
11
+ import cv2
12
+ import onnx
13
+ import onnxruntime
14
+ from utils import face_align
15
+
16
+ __all__ = [
17
+ 'Attribute',
18
+ ]
19
+
20
+
21
+ class Attribute:
22
+ def __init__(self, model_file=None, session=None, ctx_id=0, **kwargs):
23
+ assert model_file is not None
24
+ self.model_file = model_file
25
+ self.session = session
26
+ find_sub = False
27
+ find_mul = False
28
+ model = onnx.load(self.model_file)
29
+ graph = model.graph
30
+ for nid, node in enumerate(graph.node[:8]):
31
+ #print(nid, node.name)
32
+ if node.name.startswith('Sub') or node.name.startswith('_minus'):
33
+ find_sub = True
34
+ if node.name.startswith('Mul') or node.name.startswith('_mul'):
35
+ find_mul = True
36
+ if nid<3 and node.name=='bn_data':
37
+ find_sub = True
38
+ find_mul = True
39
+ if find_sub and find_mul:
40
+ #mxnet arcface model
41
+ input_mean = 0.0
42
+ input_std = 1.0
43
+ else:
44
+ input_mean = 127.5
45
+ input_std = 128.0
46
+ self.input_mean = input_mean
47
+ self.input_std = input_std
48
+ #print('input mean and std:', model_file, self.input_mean, self.input_std)
49
+ if self.session is None:
50
+ self.session = onnxruntime.InferenceSession(self.model_file, None)
51
+ input_cfg = self.session.get_inputs()[0]
52
+ input_shape = input_cfg.shape
53
+ input_name = input_cfg.name
54
+ self.input_size = tuple(input_shape[2:4][::-1])
55
+ self.input_shape = input_shape
56
+ outputs = self.session.get_outputs()
57
+ output_names = []
58
+ for out in outputs:
59
+ output_names.append(out.name)
60
+ self.input_name = input_name
61
+ self.output_names = output_names
62
+ assert len(self.output_names)==1
63
+ output_shape = outputs[0].shape
64
+ #print('init output_shape:', output_shape)
65
+ if output_shape[1]==3:
66
+ self.taskname = 'genderage'
67
+ else:
68
+ self.taskname = 'attribute_%d'%output_shape[1]
69
+
70
+ if ctx_id<0:
71
+ self.session.set_providers(['CPUExecutionProvider'])
72
+
73
+ def get(self, img, face):
74
+ bbox = face.bbox
75
+ w, h = (bbox[2] - bbox[0]), (bbox[3] - bbox[1])
76
+ center = (bbox[2] + bbox[0]) / 2, (bbox[3] + bbox[1]) / 2
77
+ rotate = 0
78
+ _scale = self.input_size[0] / (max(w, h)*1.5)
79
+ #print('param:', img.shape, bbox, center, self.input_size, _scale, rotate)
80
+ aimg, M = face_align.transform(img, center, self.input_size[0], _scale, rotate)
81
+ input_size = tuple(aimg.shape[0:2][::-1])
82
+ #assert input_size==self.input_size
83
+ blob = cv2.dnn.blobFromImage(aimg, 1.0/self.input_std, input_size, (self.input_mean, self.input_mean, self.input_mean), swapRB=True)
84
+ pred = self.session.run(self.output_names, {self.input_name : blob})[0][0]
85
+ if self.taskname=='genderage':
86
+ assert len(pred)==3
87
+ gender = np.argmax(pred[:2])
88
+ age = int(np.round(pred[2]*100))
89
+ face['gender'] = gender
90
+ face['age'] = age
91
+ return gender, age
92
+ else:
93
+ return pred
94
+
95
+
models/landmark.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # https://github.com/deepinsight/insightface/blob/master/python-package/insightface/model_zoo/landmark.py
2
+
3
+ # -*- coding: utf-8 -*-
4
+ # @Organization : insightface.ai
5
+ # @Author : Jia Guo
6
+ # @Time : 2021-05-04
7
+ # @Function :
8
+
9
+ from __future__ import division
10
+
11
+ import pickle
12
+
13
+ import cv2
14
+ import numpy as np
15
+ import onnx
16
+ import onnxruntime
17
+
18
+ from utils import face_align
19
+ from utils import transform
20
+
21
+ __all__ = [
22
+ 'Landmark',
23
+ ]
24
+
25
+
26
+ class Landmark:
27
+ def __init__(self, model_file=None, session=None, ctx_id=0, **kwargs):
28
+ assert model_file is not None
29
+ self.model_file = model_file
30
+ self.session = session
31
+ find_sub = False
32
+ find_mul = False
33
+ model = onnx.load(self.model_file)
34
+ graph = model.graph
35
+ for nid, node in enumerate(graph.node[:8]):
36
+ #print(nid, node.name)
37
+ if node.name.startswith('Sub') or node.name.startswith('_minus'):
38
+ find_sub = True
39
+ if node.name.startswith('Mul') or node.name.startswith('_mul'):
40
+ find_mul = True
41
+ if nid<3 and node.name=='bn_data':
42
+ find_sub = True
43
+ find_mul = True
44
+ if find_sub and find_mul:
45
+ #mxnet arcface model
46
+ input_mean = 0.0
47
+ input_std = 1.0
48
+ else:
49
+ input_mean = 127.5
50
+ input_std = 128.0
51
+ self.input_mean = input_mean
52
+ self.input_std = input_std
53
+ #print('input mean and std:', model_file, self.input_mean, self.input_std)
54
+ if self.session is None:
55
+ self.session = onnxruntime.InferenceSession(self.model_file, None)
56
+ input_cfg = self.session.get_inputs()[0]
57
+ input_shape = input_cfg.shape
58
+ input_name = input_cfg.name
59
+ self.input_size = tuple(input_shape[2:4][::-1])
60
+ self.input_shape = input_shape
61
+ outputs = self.session.get_outputs()
62
+ output_names = []
63
+ for out in outputs:
64
+ output_names.append(out.name)
65
+ self.input_name = input_name
66
+ self.output_names = output_names
67
+ assert len(self.output_names)==1
68
+ output_shape = outputs[0].shape
69
+ self.require_pose = False
70
+ #print('init output_shape:', output_shape)
71
+ if output_shape[1]==3309:
72
+ self.lmk_dim = 3
73
+ self.lmk_num = 68
74
+ with open("meanshape_68.pkl", 'rb') as f:
75
+ self.mean_lmk = pickle.load(f)
76
+ self.require_pose = True
77
+ else:
78
+ self.lmk_dim = 2
79
+ self.lmk_num = output_shape[1]//self.lmk_dim
80
+ self.taskname = 'landmark_%dd_%d'%(self.lmk_dim, self.lmk_num)
81
+
82
+ if ctx_id<0:
83
+ self.session.set_providers(['CPUExecutionProvider'])
84
+
85
+ def get(self, img, face):
86
+ bbox = face.bbox
87
+ w, h = (bbox[2] - bbox[0]), (bbox[3] - bbox[1])
88
+ center = (bbox[2] + bbox[0]) / 2, (bbox[3] + bbox[1]) / 2
89
+ rotate = 0
90
+ _scale = self.input_size[0] / (max(w, h)*1.5)
91
+ #print('param:', img.shape, bbox, center, self.input_size, _scale, rotate)
92
+ aimg, M = face_align.transform(img, center, self.input_size[0], _scale, rotate)
93
+ input_size = tuple(aimg.shape[0:2][::-1])
94
+ #assert input_size==self.input_size
95
+ blob = cv2.dnn.blobFromImage(aimg, 1.0/self.input_std, input_size, (self.input_mean, self.input_mean, self.input_mean), swapRB=True)
96
+ pred = self.session.run(self.output_names, {self.input_name : blob})[0][0]
97
+ if pred.shape[0] >= 3000:
98
+ pred = pred.reshape((-1, 3))
99
+ else:
100
+ pred = pred.reshape((-1, 2))
101
+ if self.lmk_num < pred.shape[0]:
102
+ pred = pred[self.lmk_num*-1:,:]
103
+ pred[:, 0:2] += 1
104
+ pred[:, 0:2] *= (self.input_size[0] // 2)
105
+ if pred.shape[1] == 3:
106
+ pred[:, 2] *= (self.input_size[0] // 2)
107
+
108
+ IM = cv2.invertAffineTransform(M)
109
+ pred = face_align.trans_points(pred, IM)
110
+ face[self.taskname] = pred
111
+ if self.require_pose:
112
+ P = transform.estimate_affine_matrix_3d23d(self.mean_lmk, pred)
113
+ s, R, t = transform.P2sRt(P)
114
+ rx, ry, rz = transform.matrix2angle(R)
115
+ pose = np.array( [rx, ry, rz], dtype=np.float32 )
116
+ face['pose'] = pose #pitch, yaw, roll
117
+ return pred
118
+
119
+
models/retinaface.py ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # https://github.com/deepinsight/insightface/blob/master/python-package/insightface/model_zoo/retinaface.py
2
+
3
+ # -*- coding: utf-8 -*-
4
+ # @Organization : insightface.ai
5
+ # @Author : Jia Guo
6
+ # @Time : 2021-09-18
7
+ # @Function :
8
+
9
+ from __future__ import division
10
+
11
+ import os.path as osp
12
+
13
+ import cv2
14
+ import numpy as np
15
+ import onnxruntime
16
+
17
+
18
+ def softmax(z):
19
+ assert len(z.shape) == 2
20
+ s = np.max(z, axis=1)
21
+ s = s[:, np.newaxis] # necessary step to do broadcasting
22
+ e_x = np.exp(z - s)
23
+ div = np.sum(e_x, axis=1)
24
+ div = div[:, np.newaxis] # dito
25
+ return e_x / div
26
+
27
+ def distance2bbox(points, distance, max_shape=None):
28
+ """Decode distance prediction to bounding box.
29
+
30
+ Args:
31
+ points (Tensor): Shape (n, 2), [x, y].
32
+ distance (Tensor): Distance from the given point to 4
33
+ boundaries (left, top, right, bottom).
34
+ max_shape (tuple): Shape of the image.
35
+
36
+ Returns:
37
+ Tensor: Decoded bboxes.
38
+ """
39
+ x1 = points[:, 0] - distance[:, 0]
40
+ y1 = points[:, 1] - distance[:, 1]
41
+ x2 = points[:, 0] + distance[:, 2]
42
+ y2 = points[:, 1] + distance[:, 3]
43
+ if max_shape is not None:
44
+ x1 = x1.clamp(min=0, max=max_shape[1])
45
+ y1 = y1.clamp(min=0, max=max_shape[0])
46
+ x2 = x2.clamp(min=0, max=max_shape[1])
47
+ y2 = y2.clamp(min=0, max=max_shape[0])
48
+ return np.stack([x1, y1, x2, y2], axis=-1)
49
+
50
+ def distance2kps(points, distance, max_shape=None):
51
+ """Decode distance prediction to bounding box.
52
+
53
+ Args:
54
+ points (Tensor): Shape (n, 2), [x, y].
55
+ distance (Tensor): Distance from the given point to 4
56
+ boundaries (left, top, right, bottom).
57
+ max_shape (tuple): Shape of the image.
58
+
59
+ Returns:
60
+ Tensor: Decoded bboxes.
61
+ """
62
+ preds = []
63
+ for i in range(0, distance.shape[1], 2):
64
+ px = points[:, i%2] + distance[:, i]
65
+ py = points[:, i%2+1] + distance[:, i+1]
66
+ if max_shape is not None:
67
+ px = px.clamp(min=0, max=max_shape[1])
68
+ py = py.clamp(min=0, max=max_shape[0])
69
+ preds.append(px)
70
+ preds.append(py)
71
+ return np.stack(preds, axis=-1)
72
+
73
+ class RetinaFace:
74
+ def __init__(self, model_file=None, session=None, ctx_id=0, **kwargs):
75
+ self.input_size = None
76
+ self.model_file = model_file
77
+ self.session = session
78
+ self.taskname = 'detection'
79
+ if self.session is None:
80
+ assert self.model_file is not None
81
+ assert osp.exists(self.model_file)
82
+ self.session = onnxruntime.InferenceSession(self.model_file, None)
83
+ self.center_cache = {}
84
+ self.nms_thresh = 0.4
85
+ self.det_thresh = 0.5
86
+ self._init_vars()
87
+
88
+ if ctx_id<0:
89
+ self.session.set_providers(['CPUExecutionProvider'])
90
+ nms_thresh = kwargs.get('nms_thresh', None)
91
+ if nms_thresh is not None:
92
+ self.nms_thresh = nms_thresh
93
+ det_thresh = kwargs.get('det_thresh', None)
94
+ if det_thresh is not None:
95
+ self.det_thresh = det_thresh
96
+ input_size = kwargs.get('input_size', None)
97
+ if input_size is not None:
98
+ if self.input_size is not None:
99
+ print('warning: det_size is already set in detection model, ignore')
100
+ else:
101
+ self.input_size = input_size
102
+
103
+ def _init_vars(self):
104
+ input_cfg = self.session.get_inputs()[0]
105
+ input_shape = input_cfg.shape
106
+ #print(input_shape)
107
+ if isinstance(input_shape[2], str):
108
+ self.input_size = None
109
+ else:
110
+ self.input_size = tuple(input_shape[2:4][::-1])
111
+ #print('image_size:', self.image_size)
112
+ input_name = input_cfg.name
113
+ self.input_shape = input_shape
114
+ outputs = self.session.get_outputs()
115
+ output_names = []
116
+ for o in outputs:
117
+ output_names.append(o.name)
118
+ self.input_name = input_name
119
+ self.output_names = output_names
120
+ self.input_mean = 127.5
121
+ self.input_std = 128.0
122
+ #print(self.output_names)
123
+ #assert len(outputs)==10 or len(outputs)==15
124
+ self.use_kps = False
125
+ self._anchor_ratio = 1.0
126
+ self._num_anchors = 1
127
+ if len(outputs)==6:
128
+ self.fmc = 3
129
+ self._feat_stride_fpn = [8, 16, 32]
130
+ self._num_anchors = 2
131
+ elif len(outputs)==9:
132
+ self.fmc = 3
133
+ self._feat_stride_fpn = [8, 16, 32]
134
+ self._num_anchors = 2
135
+ self.use_kps = True
136
+ elif len(outputs)==10:
137
+ self.fmc = 5
138
+ self._feat_stride_fpn = [8, 16, 32, 64, 128]
139
+ self._num_anchors = 1
140
+ elif len(outputs)==15:
141
+ self.fmc = 5
142
+ self._feat_stride_fpn = [8, 16, 32, 64, 128]
143
+ self._num_anchors = 1
144
+ self.use_kps = True
145
+
146
+ def forward(self, img, threshold):
147
+ scores_list = []
148
+ bboxes_list = []
149
+ kpss_list = []
150
+ input_size = tuple(img.shape[0:2][::-1])
151
+ blob = cv2.dnn.blobFromImage(img, 1.0/self.input_std, input_size, (self.input_mean, self.input_mean, self.input_mean), swapRB=True)
152
+ net_outs = self.session.run(self.output_names, {self.input_name : blob})
153
+
154
+ input_height = blob.shape[2]
155
+ input_width = blob.shape[3]
156
+ fmc = self.fmc
157
+ for idx, stride in enumerate(self._feat_stride_fpn):
158
+ scores = net_outs[idx]
159
+ bbox_preds = net_outs[idx+fmc]
160
+ bbox_preds = bbox_preds * stride
161
+ if self.use_kps:
162
+ kps_preds = net_outs[idx+fmc*2] * stride
163
+ height = input_height // stride
164
+ width = input_width // stride
165
+ K = height * width
166
+ key = (height, width, stride)
167
+ if key in self.center_cache:
168
+ anchor_centers = self.center_cache[key]
169
+ else:
170
+ #solution-1, c style:
171
+ #anchor_centers = np.zeros( (height, width, 2), dtype=np.float32 )
172
+ #for i in range(height):
173
+ # anchor_centers[i, :, 1] = i
174
+ #for i in range(width):
175
+ # anchor_centers[:, i, 0] = i
176
+
177
+ #solution-2:
178
+ #ax = np.arange(width, dtype=np.float32)
179
+ #ay = np.arange(height, dtype=np.float32)
180
+ #xv, yv = np.meshgrid(np.arange(width), np.arange(height))
181
+ #anchor_centers = np.stack([xv, yv], axis=-1).astype(np.float32)
182
+
183
+ #solution-3:
184
+ anchor_centers = np.stack(np.mgrid[:height, :width][::-1], axis=-1).astype(np.float32)
185
+ #print(anchor_centers.shape)
186
+
187
+ anchor_centers = (anchor_centers * stride).reshape( (-1, 2) )
188
+ if self._num_anchors>1:
189
+ anchor_centers = np.stack([anchor_centers]*self._num_anchors, axis=1).reshape( (-1,2) )
190
+ if len(self.center_cache)<100:
191
+ self.center_cache[key] = anchor_centers
192
+
193
+ pos_inds = np.where(scores>=threshold)[0]
194
+ bboxes = distance2bbox(anchor_centers, bbox_preds)
195
+ pos_scores = scores[pos_inds]
196
+ pos_bboxes = bboxes[pos_inds]
197
+ scores_list.append(pos_scores)
198
+ bboxes_list.append(pos_bboxes)
199
+ if self.use_kps:
200
+ kpss = distance2kps(anchor_centers, kps_preds)
201
+ #kpss = kps_preds
202
+ kpss = kpss.reshape( (kpss.shape[0], -1, 2) )
203
+ pos_kpss = kpss[pos_inds]
204
+ kpss_list.append(pos_kpss)
205
+ return scores_list, bboxes_list, kpss_list
206
+
207
+ def detect(self, img, input_size = None, max_num=0, metric='default'):
208
+ assert input_size is not None or self.input_size is not None
209
+ input_size = self.input_size if input_size is None else input_size
210
+
211
+ im_ratio = float(img.shape[0]) / img.shape[1]
212
+ model_ratio = float(input_size[1]) / input_size[0]
213
+ if im_ratio>model_ratio:
214
+ new_height = input_size[1]
215
+ new_width = int(new_height / im_ratio)
216
+ else:
217
+ new_width = input_size[0]
218
+ new_height = int(new_width * im_ratio)
219
+ det_scale = float(new_height) / img.shape[0]
220
+ resized_img = cv2.resize(img, (new_width, new_height))
221
+ det_img = np.zeros( (input_size[1], input_size[0], 3), dtype=np.uint8 )
222
+ det_img[:new_height, :new_width, :] = resized_img
223
+
224
+ scores_list, bboxes_list, kpss_list = self.forward(det_img, self.det_thresh)
225
+
226
+ scores = np.vstack(scores_list)
227
+ scores_ravel = scores.ravel()
228
+ order = scores_ravel.argsort()[::-1]
229
+ bboxes = np.vstack(bboxes_list) / det_scale
230
+ if self.use_kps:
231
+ kpss = np.vstack(kpss_list) / det_scale
232
+ pre_det = np.hstack((bboxes, scores)).astype(np.float32, copy=False)
233
+ pre_det = pre_det[order, :]
234
+ keep = self.nms(pre_det)
235
+ det = pre_det[keep, :]
236
+ if self.use_kps:
237
+ kpss = kpss[order,:,:]
238
+ kpss = kpss[keep,:,:]
239
+ else:
240
+ kpss = None
241
+ if max_num > 0 and det.shape[0] > max_num:
242
+ area = (det[:, 2] - det[:, 0]) * (det[:, 3] -
243
+ det[:, 1])
244
+ img_center = img.shape[0] // 2, img.shape[1] // 2
245
+ offsets = np.vstack([
246
+ (det[:, 0] + det[:, 2]) / 2 - img_center[1],
247
+ (det[:, 1] + det[:, 3]) / 2 - img_center[0]
248
+ ])
249
+ offset_dist_squared = np.sum(np.power(offsets, 2.0), 0)
250
+ if metric=='max':
251
+ values = area
252
+ else:
253
+ values = area - offset_dist_squared * 2.0 # some extra weight on the centering
254
+ bindex = np.argsort(
255
+ values)[::-1] # some extra weight on the centering
256
+ bindex = bindex[0:max_num]
257
+ det = det[bindex, :]
258
+ if kpss is not None:
259
+ kpss = kpss[bindex, :]
260
+ return det, kpss
261
+
262
+ def nms(self, dets):
263
+ thresh = self.nms_thresh
264
+ x1 = dets[:, 0]
265
+ y1 = dets[:, 1]
266
+ x2 = dets[:, 2]
267
+ y2 = dets[:, 3]
268
+ scores = dets[:, 4]
269
+
270
+ areas = (x2 - x1 + 1) * (y2 - y1 + 1)
271
+ order = scores.argsort()[::-1]
272
+
273
+ keep = []
274
+ while order.size > 0:
275
+ i = order[0]
276
+ keep.append(i)
277
+ xx1 = np.maximum(x1[i], x1[order[1:]])
278
+ yy1 = np.maximum(y1[i], y1[order[1:]])
279
+ xx2 = np.minimum(x2[i], x2[order[1:]])
280
+ yy2 = np.minimum(y2[i], y2[order[1:]])
281
+
282
+ w = np.maximum(0.0, xx2 - xx1 + 1)
283
+ h = np.maximum(0.0, yy2 - yy1 + 1)
284
+ inter = w * h
285
+ ovr = inter / (areas[i] + areas[order[1:]] - inter)
286
+
287
+ inds = np.where(ovr <= thresh)[0]
288
+ order = order[inds + 1]
289
+
290
+ return keep
playground.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+
3
+ from face_analysis import FaceAnalysis
4
+
5
+ face_analyser = FaceAnalysis()
6
+
7
+ if __name__ == "__main__":
8
+ image_path = "/home/leonel/Pictures/lowres512.jpg"
9
+ src_img = img = cv2.imread(image_path, cv2.IMREAD_COLOR)
10
+ faces = face_analyser.get(src_img)
11
+
12
+ print(faces[0])
13
+
utils/common.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from numpy.linalg import norm as l2norm
2
+
3
+ class Face(dict):
4
+
5
+ def __init__(self, d=None, **kwargs):
6
+ super().__init__()
7
+ if d is None:
8
+ d = {}
9
+ if kwargs:
10
+ d.update(**kwargs)
11
+ for k, v in d.items():
12
+ setattr(self, k, v)
13
+
14
+ def __setattr__(self, name, value):
15
+ if isinstance(value, (list, tuple)):
16
+ value = [self.__class__(x)
17
+ if isinstance(x, dict) else x for x in value]
18
+ elif isinstance(value, dict) and not isinstance(value, self.__class__):
19
+ value = self.__class__(value)
20
+ super(Face, self).__setattr__(name, value)
21
+ super(Face, self).__setitem__(name, value)
22
+
23
+ __setitem__ = __setattr__
24
+
25
+ def __getattr__(self, name):
26
+ return None
27
+
28
+ @property
29
+ def embedding_norm(self):
30
+ if self.embedding is None:
31
+ return None
32
+ return l2norm(self.embedding)
33
+
34
+ @property
35
+ def normed_embedding(self):
36
+ if self.embedding is None:
37
+ return None
38
+ return self.embedding / self.embedding_norm
39
+
40
+ @property
41
+ def sex(self):
42
+ if self.gender is None:
43
+ return None
44
+ return 'M' if self.gender==1 else 'F'
utils/face_align.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ from skimage import transform as trans
4
+
5
+
6
+ arcface_dst = np.array(
7
+ [[38.2946, 51.6963], [73.5318, 51.5014], [56.0252, 71.7366],
8
+ [41.5493, 92.3655], [70.7299, 92.2041]],
9
+ dtype=np.float32)
10
+
11
+ def estimate_norm(lmk, image_size=112,mode='arcface'):
12
+ assert lmk.shape == (5, 2)
13
+ assert image_size%112==0 or image_size%128==0
14
+ if image_size%112==0:
15
+ ratio = float(image_size)/112.0
16
+ diff_x = 0
17
+ else:
18
+ ratio = float(image_size)/128.0
19
+ diff_x = 8.0*ratio
20
+ dst = arcface_dst * ratio
21
+ dst[:,0] += diff_x
22
+ tform = trans.SimilarityTransform()
23
+ tform.estimate(lmk, dst)
24
+ M = tform.params[0:2, :]
25
+ return M
26
+
27
+ def norm_crop(img, landmark, image_size=112, mode='arcface'):
28
+ M = estimate_norm(landmark, image_size, mode)
29
+ warped = cv2.warpAffine(img, M, (image_size, image_size), borderValue=0.0)
30
+ return warped
31
+
32
+ def norm_crop2(img, landmark, image_size=112, mode='arcface'):
33
+ M = estimate_norm(landmark, image_size, mode)
34
+ warped = cv2.warpAffine(img, M, (image_size, image_size), borderValue=0.0)
35
+ return warped, M
36
+
37
+ def square_crop(im, S):
38
+ if im.shape[0] > im.shape[1]:
39
+ height = S
40
+ width = int(float(im.shape[1]) / im.shape[0] * S)
41
+ scale = float(S) / im.shape[0]
42
+ else:
43
+ width = S
44
+ height = int(float(im.shape[0]) / im.shape[1] * S)
45
+ scale = float(S) / im.shape[1]
46
+ resized_im = cv2.resize(im, (width, height))
47
+ det_im = np.zeros((S, S, 3), dtype=np.uint8)
48
+ det_im[:resized_im.shape[0], :resized_im.shape[1], :] = resized_im
49
+ return det_im, scale
50
+
51
+
52
+ def transform(data, center, output_size, scale, rotation):
53
+ scale_ratio = scale
54
+ rot = float(rotation) * np.pi / 180.0
55
+ #translation = (output_size/2-center[0]*scale_ratio, output_size/2-center[1]*scale_ratio)
56
+ t1 = trans.SimilarityTransform(scale=scale_ratio)
57
+ cx = center[0] * scale_ratio
58
+ cy = center[1] * scale_ratio
59
+ t2 = trans.SimilarityTransform(translation=(-1 * cx, -1 * cy))
60
+ t3 = trans.SimilarityTransform(rotation=rot)
61
+ t4 = trans.SimilarityTransform(translation=(output_size / 2,
62
+ output_size / 2))
63
+ t = t1 + t2 + t3 + t4
64
+ M = t.params[0:2]
65
+ cropped = cv2.warpAffine(data,
66
+ M, (output_size, output_size),
67
+ borderValue=0.0)
68
+ return cropped, M
69
+
70
+
71
+ def trans_points2d(pts, M):
72
+ new_pts = np.zeros(shape=pts.shape, dtype=np.float32)
73
+ for i in range(pts.shape[0]):
74
+ pt = pts[i]
75
+ new_pt = np.array([pt[0], pt[1], 1.], dtype=np.float32)
76
+ new_pt = np.dot(M, new_pt)
77
+ #print('new_pt', new_pt.shape, new_pt)
78
+ new_pts[i] = new_pt[0:2]
79
+
80
+ return new_pts
81
+
82
+
83
+ def trans_points3d(pts, M):
84
+ scale = np.sqrt(M[0][0] * M[0][0] + M[0][1] * M[0][1])
85
+ #print(scale)
86
+ new_pts = np.zeros(shape=pts.shape, dtype=np.float32)
87
+ for i in range(pts.shape[0]):
88
+ pt = pts[i]
89
+ new_pt = np.array([pt[0], pt[1], 1.], dtype=np.float32)
90
+ new_pt = np.dot(M, new_pt)
91
+ #print('new_pt', new_pt.shape, new_pt)
92
+ new_pts[i][0:2] = new_pt[0:2]
93
+ new_pts[i][2] = pts[i][2] * scale
94
+
95
+ return new_pts
96
+
97
+
98
+ def trans_points(pts, M):
99
+ if pts.shape[1] == 2:
100
+ return trans_points2d(pts, M)
101
+ else:
102
+ return trans_points3d(pts, M)
103
+
utils/transform.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import math
3
+ import numpy as np
4
+ from skimage import transform as trans
5
+
6
+
7
+ def transform(data, center, output_size, scale, rotation):
8
+ scale_ratio = scale
9
+ rot = float(rotation) * np.pi / 180.0
10
+ #translation = (output_size/2-center[0]*scale_ratio, output_size/2-center[1]*scale_ratio)
11
+ t1 = trans.SimilarityTransform(scale=scale_ratio)
12
+ cx = center[0] * scale_ratio
13
+ cy = center[1] * scale_ratio
14
+ t2 = trans.SimilarityTransform(translation=(-1 * cx, -1 * cy))
15
+ t3 = trans.SimilarityTransform(rotation=rot)
16
+ t4 = trans.SimilarityTransform(translation=(output_size / 2,
17
+ output_size / 2))
18
+ t = t1 + t2 + t3 + t4
19
+ M = t.params[0:2]
20
+ cropped = cv2.warpAffine(data,
21
+ M, (output_size, output_size),
22
+ borderValue=0.0)
23
+ return cropped, M
24
+
25
+
26
+ def trans_points2d(pts, M):
27
+ new_pts = np.zeros(shape=pts.shape, dtype=np.float32)
28
+ for i in range(pts.shape[0]):
29
+ pt = pts[i]
30
+ new_pt = np.array([pt[0], pt[1], 1.], dtype=np.float32)
31
+ new_pt = np.dot(M, new_pt)
32
+ #print('new_pt', new_pt.shape, new_pt)
33
+ new_pts[i] = new_pt[0:2]
34
+
35
+ return new_pts
36
+
37
+
38
+ def trans_points3d(pts, M):
39
+ scale = np.sqrt(M[0][0] * M[0][0] + M[0][1] * M[0][1])
40
+ #print(scale)
41
+ new_pts = np.zeros(shape=pts.shape, dtype=np.float32)
42
+ for i in range(pts.shape[0]):
43
+ pt = pts[i]
44
+ new_pt = np.array([pt[0], pt[1], 1.], dtype=np.float32)
45
+ new_pt = np.dot(M, new_pt)
46
+ #print('new_pt', new_pt.shape, new_pt)
47
+ new_pts[i][0:2] = new_pt[0:2]
48
+ new_pts[i][2] = pts[i][2] * scale
49
+
50
+ return new_pts
51
+
52
+
53
+ def trans_points(pts, M):
54
+ if pts.shape[1] == 2:
55
+ return trans_points2d(pts, M)
56
+ else:
57
+ return trans_points3d(pts, M)
58
+
59
+ def estimate_affine_matrix_3d23d(X, Y):
60
+ ''' Using least-squares solution
61
+ Args:
62
+ X: [n, 3]. 3d points(fixed)
63
+ Y: [n, 3]. corresponding 3d points(moving). Y = PX
64
+ Returns:
65
+ P_Affine: (3, 4). Affine camera matrix (the third row is [0, 0, 0, 1]).
66
+ '''
67
+ X_homo = np.hstack((X, np.ones([X.shape[0],1]))) #n x 4
68
+ P = np.linalg.lstsq(X_homo, Y)[0].T # Affine matrix. 3 x 4
69
+ return P
70
+
71
+ def P2sRt(P):
72
+ ''' decompositing camera matrix P
73
+ Args:
74
+ P: (3, 4). Affine Camera Matrix.
75
+ Returns:
76
+ s: scale factor.
77
+ R: (3, 3). rotation matrix.
78
+ t: (3,). translation.
79
+ '''
80
+ t = P[:, 3]
81
+ R1 = P[0:1, :3]
82
+ R2 = P[1:2, :3]
83
+ s = (np.linalg.norm(R1) + np.linalg.norm(R2))/2.0
84
+ r1 = R1/np.linalg.norm(R1)
85
+ r2 = R2/np.linalg.norm(R2)
86
+ r3 = np.cross(r1, r2)
87
+
88
+ R = np.concatenate((r1, r2, r3), 0)
89
+ return s, R, t
90
+
91
+ def matrix2angle(R):
92
+ ''' get three Euler angles from Rotation Matrix
93
+ Args:
94
+ R: (3,3). rotation matrix
95
+ Returns:
96
+ x: pitch
97
+ y: yaw
98
+ z: roll
99
+ '''
100
+ sy = math.sqrt(R[0,0] * R[0,0] + R[1,0] * R[1,0])
101
+
102
+ singular = sy < 1e-6
103
+
104
+ if not singular :
105
+ x = math.atan2(R[2,1] , R[2,2])
106
+ y = math.atan2(-R[2,0], sy)
107
+ z = math.atan2(R[1,0], R[0,0])
108
+ else :
109
+ x = math.atan2(-R[1,2], R[1,1])
110
+ y = math.atan2(-R[2,0], sy)
111
+ z = 0
112
+
113
+ # rx, ry, rz = np.rad2deg(x), np.rad2deg(y), np.rad2deg(z)
114
+ rx, ry, rz = x*180/np.pi, y*180/np.pi, z*180/np.pi
115
+ return rx, ry, rz
116
+