Add demo for spoof detection
Browse files- .gitignore +160 -0
- app.py +30 -0
- auto_rotate.py +75 -0
- box_utils_numpy.py +74 -0
- models/slim-facedetect.onnx +3 -0
- models/slim-facelandmark.onnx +3 -0
- models/spoof.onnx +3 -0
- requirements.txt +4 -0
- spoofynet.py +134 -0
.gitignore
ADDED
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Byte-compiled / optimized / DLL files
|
2 |
+
__pycache__/
|
3 |
+
*.py[cod]
|
4 |
+
*$py.class
|
5 |
+
|
6 |
+
# C extensions
|
7 |
+
*.so
|
8 |
+
|
9 |
+
# Distribution / packaging
|
10 |
+
.Python
|
11 |
+
build/
|
12 |
+
develop-eggs/
|
13 |
+
dist/
|
14 |
+
downloads/
|
15 |
+
eggs/
|
16 |
+
.eggs/
|
17 |
+
lib/
|
18 |
+
lib64/
|
19 |
+
parts/
|
20 |
+
sdist/
|
21 |
+
var/
|
22 |
+
wheels/
|
23 |
+
share/python-wheels/
|
24 |
+
*.egg-info/
|
25 |
+
.installed.cfg
|
26 |
+
*.egg
|
27 |
+
MANIFEST
|
28 |
+
|
29 |
+
# PyInstaller
|
30 |
+
# Usually these files are written by a python script from a template
|
31 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
32 |
+
*.manifest
|
33 |
+
*.spec
|
34 |
+
|
35 |
+
# Installer logs
|
36 |
+
pip-log.txt
|
37 |
+
pip-delete-this-directory.txt
|
38 |
+
|
39 |
+
# Unit test / coverage reports
|
40 |
+
htmlcov/
|
41 |
+
.tox/
|
42 |
+
.nox/
|
43 |
+
.coverage
|
44 |
+
.coverage.*
|
45 |
+
.cache
|
46 |
+
nosetests.xml
|
47 |
+
coverage.xml
|
48 |
+
*.cover
|
49 |
+
*.py,cover
|
50 |
+
.hypothesis/
|
51 |
+
.pytest_cache/
|
52 |
+
cover/
|
53 |
+
|
54 |
+
# Translations
|
55 |
+
*.mo
|
56 |
+
*.pot
|
57 |
+
|
58 |
+
# Django stuff:
|
59 |
+
*.log
|
60 |
+
local_settings.py
|
61 |
+
db.sqlite3
|
62 |
+
db.sqlite3-journal
|
63 |
+
|
64 |
+
# Flask stuff:
|
65 |
+
instance/
|
66 |
+
.webassets-cache
|
67 |
+
|
68 |
+
# Scrapy stuff:
|
69 |
+
.scrapy
|
70 |
+
|
71 |
+
# Sphinx documentation
|
72 |
+
docs/_build/
|
73 |
+
|
74 |
+
# PyBuilder
|
75 |
+
.pybuilder/
|
76 |
+
target/
|
77 |
+
|
78 |
+
# Jupyter Notebook
|
79 |
+
.ipynb_checkpoints
|
80 |
+
|
81 |
+
# IPython
|
82 |
+
profile_default/
|
83 |
+
ipython_config.py
|
84 |
+
|
85 |
+
# pyenv
|
86 |
+
# For a library or package, you might want to ignore these files since the code is
|
87 |
+
# intended to run in multiple environments; otherwise, check them in:
|
88 |
+
# .python-version
|
89 |
+
|
90 |
+
# pipenv
|
91 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
92 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
93 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
94 |
+
# install all needed dependencies.
|
95 |
+
#Pipfile.lock
|
96 |
+
|
97 |
+
# poetry
|
98 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
99 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
100 |
+
# commonly ignored for libraries.
|
101 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
102 |
+
#poetry.lock
|
103 |
+
|
104 |
+
# pdm
|
105 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
106 |
+
#pdm.lock
|
107 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
108 |
+
# in version control.
|
109 |
+
# https://pdm.fming.dev/#use-with-ide
|
110 |
+
.pdm.toml
|
111 |
+
|
112 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
113 |
+
__pypackages__/
|
114 |
+
|
115 |
+
# Celery stuff
|
116 |
+
celerybeat-schedule
|
117 |
+
celerybeat.pid
|
118 |
+
|
119 |
+
# SageMath parsed files
|
120 |
+
*.sage.py
|
121 |
+
|
122 |
+
# Environments
|
123 |
+
.env
|
124 |
+
.venv
|
125 |
+
env/
|
126 |
+
venv/
|
127 |
+
ENV/
|
128 |
+
env.bak/
|
129 |
+
venv.bak/
|
130 |
+
|
131 |
+
# Spyder project settings
|
132 |
+
.spyderproject
|
133 |
+
.spyproject
|
134 |
+
|
135 |
+
# Rope project settings
|
136 |
+
.ropeproject
|
137 |
+
|
138 |
+
# mkdocs documentation
|
139 |
+
/site
|
140 |
+
|
141 |
+
# mypy
|
142 |
+
.mypy_cache/
|
143 |
+
.dmypy.json
|
144 |
+
dmypy.json
|
145 |
+
|
146 |
+
# Pyre type checker
|
147 |
+
.pyre/
|
148 |
+
|
149 |
+
# pytype static type analyzer
|
150 |
+
.pytype/
|
151 |
+
|
152 |
+
# Cython debug symbols
|
153 |
+
cython_debug/
|
154 |
+
|
155 |
+
# PyCharm
|
156 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
157 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
158 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
159 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
160 |
+
#.idea/
|
app.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import gradio as gr
|
3 |
+
|
4 |
+
from spoofynet import SpoofyNet
|
5 |
+
|
6 |
+
spoofynet = SpoofyNet()
|
7 |
+
|
8 |
+
|
9 |
+
def find_spoofs(input_img):
|
10 |
+
spoofs = spoofynet.find_spoof(input_img)
|
11 |
+
for spoof in spoofs:
|
12 |
+
(startX, startY, endX, endY) = spoof["coords"]
|
13 |
+
label = "Real" if spoof["is_real"] else "Spoofed"
|
14 |
+
color = (0, 255, 0) if spoof["is_real"] else (0, 0, 255)
|
15 |
+
cv2.putText(
|
16 |
+
input_img,
|
17 |
+
f"{label}: {spoof['probs']:.2f}",
|
18 |
+
(startX, startY - 10),
|
19 |
+
cv2.FONT_HERSHEY_SIMPLEX,
|
20 |
+
0.5,
|
21 |
+
color,
|
22 |
+
2,
|
23 |
+
)
|
24 |
+
|
25 |
+
cv2.rectangle(input_img, (startX, startY), (endX, endY), color, 4)
|
26 |
+
return input_img
|
27 |
+
|
28 |
+
|
29 |
+
demo = gr.Interface(find_spoofs, gr.Image(), "image")
|
30 |
+
demo.launch()
|
auto_rotate.py
ADDED
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import math
|
2 |
+
|
3 |
+
import cv2
|
4 |
+
import numpy as np
|
5 |
+
import onnxruntime
|
6 |
+
from PIL import Image
|
7 |
+
|
8 |
+
session = onnxruntime.InferenceSession("models/slim-facelandmark.onnx")
|
9 |
+
|
10 |
+
|
11 |
+
def EuclideanDistance(source_representation, test_representation):
|
12 |
+
euclidean_distance = source_representation - test_representation
|
13 |
+
euclidean_distance = np.sum(np.multiply(euclidean_distance, euclidean_distance))
|
14 |
+
euclidean_distance = np.sqrt(euclidean_distance)
|
15 |
+
return euclidean_distance
|
16 |
+
|
17 |
+
|
18 |
+
def alignment_procedure(img, left_eye, right_eye):
|
19 |
+
left_eye_x, left_eye_y = left_eye
|
20 |
+
right_eye_x, right_eye_y = right_eye
|
21 |
+
|
22 |
+
if left_eye_y > right_eye_y:
|
23 |
+
point_3rd = (right_eye_x, left_eye_y)
|
24 |
+
direction = -1 # rotate same direction to clock
|
25 |
+
else:
|
26 |
+
point_3rd = (left_eye_x, right_eye_y)
|
27 |
+
direction = 1 # rotate inverse direction of clock
|
28 |
+
|
29 |
+
a = EuclideanDistance(np.array(left_eye), np.array(point_3rd))
|
30 |
+
b = EuclideanDistance(np.array(right_eye), np.array(point_3rd))
|
31 |
+
c = EuclideanDistance(np.array(right_eye), np.array(left_eye))
|
32 |
+
|
33 |
+
if (
|
34 |
+
b != 0 and c != 0
|
35 |
+
): # this multiplication causes division by zero in cos_a calculation
|
36 |
+
|
37 |
+
cos_a = (b * b + c * c - a * a) / (2 * b * c)
|
38 |
+
angle = np.arccos(cos_a) # angle in radian
|
39 |
+
angle = (angle * 180) / math.pi # radian to degree
|
40 |
+
|
41 |
+
# -----------------------
|
42 |
+
# rotate base image
|
43 |
+
|
44 |
+
if direction == -1:
|
45 |
+
angle = 90 - angle
|
46 |
+
|
47 |
+
img = Image.fromarray(img)
|
48 |
+
img = np.array(img.rotate(direction * angle))
|
49 |
+
|
50 |
+
# -----------------------
|
51 |
+
|
52 |
+
return img # return img anyway
|
53 |
+
|
54 |
+
|
55 |
+
def align_face(image):
|
56 |
+
inputs = cv2.resize(image, (112, 112))
|
57 |
+
inputs = cv2.cvtColor(inputs, cv2.COLOR_BGR2RGB)
|
58 |
+
inputs = inputs.transpose(2, 0, 1).astype(np.float32)
|
59 |
+
inputs = inputs / 255.0
|
60 |
+
inputs = np.expand_dims(inputs, axis=0)
|
61 |
+
landmarks = session.run(None, {"input": inputs})[0]
|
62 |
+
pre_landmark = landmarks[0]
|
63 |
+
pre_landmark = pre_landmark.reshape(-1, 2)
|
64 |
+
left_eyex, left_eyey = pre_landmark[96]
|
65 |
+
right_eyex, right_eyey = pre_landmark[97]
|
66 |
+
|
67 |
+
img = alignment_procedure(image, (left_eyex, left_eyey), (right_eyex, right_eyey))
|
68 |
+
return img
|
69 |
+
|
70 |
+
|
71 |
+
if __name__ == "__main__":
|
72 |
+
img = cv2.imread("obamaface.jpg")
|
73 |
+
img = align_face(img)
|
74 |
+
cv2.imshow("img", img)
|
75 |
+
cv2.waitKey(0)
|
box_utils_numpy.py
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# MIT LICENSE Copyright (c) 2019 linzai
|
2 |
+
# Originally comes from https://github.com/Linzaer/Ultra-Light-Fast-Generic-Face-Detector-1MB
|
3 |
+
|
4 |
+
import numpy as np
|
5 |
+
|
6 |
+
|
7 |
+
def area_of(left_top, right_bottom):
|
8 |
+
"""Compute the areas of rectangles given two corners.
|
9 |
+
|
10 |
+
Args:
|
11 |
+
left_top (N, 2): left top corner.
|
12 |
+
right_bottom (N, 2): right bottom corner.
|
13 |
+
|
14 |
+
Returns:
|
15 |
+
area (N): return the area.
|
16 |
+
"""
|
17 |
+
hw = np.clip(right_bottom - left_top, 0.0, None)
|
18 |
+
return hw[..., 0] * hw[..., 1]
|
19 |
+
|
20 |
+
|
21 |
+
def iou_of(boxes0, boxes1, eps=1e-5):
|
22 |
+
"""Return intersection-over-union (Jaccard index) of boxes.
|
23 |
+
|
24 |
+
Args:
|
25 |
+
boxes0 (N, 4): ground truth boxes.
|
26 |
+
boxes1 (N or 1, 4): predicted boxes.
|
27 |
+
eps: a small number to avoid 0 as denominator.
|
28 |
+
Returns:
|
29 |
+
iou (N): IoU values.
|
30 |
+
"""
|
31 |
+
overlap_left_top = np.maximum(boxes0[..., :2], boxes1[..., :2])
|
32 |
+
overlap_right_bottom = np.minimum(boxes0[..., 2:], boxes1[..., 2:])
|
33 |
+
|
34 |
+
overlap_area = area_of(overlap_left_top, overlap_right_bottom)
|
35 |
+
area0 = area_of(boxes0[..., :2], boxes0[..., 2:])
|
36 |
+
area1 = area_of(boxes1[..., :2], boxes1[..., 2:])
|
37 |
+
return overlap_area / (area0 + area1 - overlap_area + eps)
|
38 |
+
|
39 |
+
|
40 |
+
def hard_nms(box_scores, iou_threshold, top_k=-1, candidate_size=200):
|
41 |
+
"""
|
42 |
+
|
43 |
+
Args:
|
44 |
+
box_scores (N, 5): boxes in corner-form and probabilities.
|
45 |
+
iou_threshold: intersection over union threshold.
|
46 |
+
top_k: keep top_k results. If k <= 0, keep all the results.
|
47 |
+
candidate_size: only consider the candidates with the highest scores.
|
48 |
+
Returns:
|
49 |
+
picked: a list of indexes of the kept boxes
|
50 |
+
"""
|
51 |
+
scores = box_scores[:, -1]
|
52 |
+
boxes = box_scores[:, :-1]
|
53 |
+
picked = []
|
54 |
+
# _, indexes = scores.sort(descending=True)
|
55 |
+
indexes = np.argsort(scores)
|
56 |
+
# indexes = indexes[:candidate_size]
|
57 |
+
indexes = indexes[-candidate_size:]
|
58 |
+
while len(indexes) > 0:
|
59 |
+
# current = indexes[0]
|
60 |
+
current = indexes[-1]
|
61 |
+
picked.append(current)
|
62 |
+
if 0 < top_k == len(picked) or len(indexes) == 1:
|
63 |
+
break
|
64 |
+
current_box = boxes[current, :]
|
65 |
+
# indexes = indexes[1:]
|
66 |
+
indexes = indexes[:-1]
|
67 |
+
rest_boxes = boxes[indexes, :]
|
68 |
+
iou = iou_of(
|
69 |
+
rest_boxes,
|
70 |
+
np.expand_dims(current_box, axis=0),
|
71 |
+
)
|
72 |
+
indexes = indexes[iou <= iou_threshold]
|
73 |
+
|
74 |
+
return box_scores[picked, :]
|
models/slim-facedetect.onnx
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:dd7b4b72b9572f51df26812112ec1b38cb3ff0ef1caad9977ce5e2bf950efbd9
|
3 |
+
size 419735
|
models/slim-facelandmark.onnx
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:70a984e0fff8c8bc8acbbfa8671b6159e9fdb7e141aa47d3441a2f777459970e
|
3 |
+
size 427876
|
models/spoof.onnx
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:ba5b74238233864eab57be35223076f03b6ea2f6231b50516c4531d2e900e1e6
|
3 |
+
size 16808373
|
requirements.txt
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Pillow
|
2 |
+
numpy
|
3 |
+
opencv-python-headless
|
4 |
+
onnxruntime
|
spoofynet.py
ADDED
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import numpy as np
|
3 |
+
from onnxruntime import InferenceSession
|
4 |
+
|
5 |
+
import box_utils_numpy
|
6 |
+
from auto_rotate import align_face
|
7 |
+
|
8 |
+
|
9 |
+
def softmax(x):
|
10 |
+
e_x = np.exp(x - np.max(x))
|
11 |
+
return e_x / e_x.sum(axis=0)
|
12 |
+
|
13 |
+
|
14 |
+
def crop_square(img, size, interpolation=cv2.INTER_AREA):
|
15 |
+
h, w = img.shape[:2]
|
16 |
+
min_size = np.amin([h, w])
|
17 |
+
|
18 |
+
# Centralize and crop
|
19 |
+
crop_img = img[
|
20 |
+
int(h / 2 - min_size / 2) : int(h / 2 + min_size / 2),
|
21 |
+
int(w / 2 - min_size / 2) : int(w / 2 + min_size / 2),
|
22 |
+
]
|
23 |
+
resized = cv2.resize(crop_img, (size, size), interpolation=interpolation)
|
24 |
+
|
25 |
+
return resized
|
26 |
+
|
27 |
+
|
28 |
+
class SpoofyNet:
|
29 |
+
def __init__(self):
|
30 |
+
self.face_model = InferenceSession("models/slim-facedetect.onnx")
|
31 |
+
self.face_inputname = self.face_model.get_inputs()[0].name
|
32 |
+
self.classifier = InferenceSession("models/spoof.onnx")
|
33 |
+
|
34 |
+
def find_boxes(
|
35 |
+
self,
|
36 |
+
width,
|
37 |
+
height,
|
38 |
+
confidences,
|
39 |
+
boxes,
|
40 |
+
prob_threshold,
|
41 |
+
iou_threshold=0.3,
|
42 |
+
top_k=-1,
|
43 |
+
):
|
44 |
+
boxes = boxes[0]
|
45 |
+
confidences = confidences[0]
|
46 |
+
picked_box_probs = []
|
47 |
+
picked_labels = []
|
48 |
+
for class_index in range(1, confidences.shape[1]):
|
49 |
+
probs = confidences[:, class_index]
|
50 |
+
mask = probs > prob_threshold
|
51 |
+
probs = probs[mask]
|
52 |
+
if probs.shape[0] == 0:
|
53 |
+
continue
|
54 |
+
subset_boxes = boxes[mask, :]
|
55 |
+
box_probs = np.concatenate([subset_boxes, probs.reshape(-1, 1)], axis=1)
|
56 |
+
box_probs = box_utils_numpy.hard_nms(
|
57 |
+
box_probs,
|
58 |
+
iou_threshold=iou_threshold,
|
59 |
+
top_k=top_k,
|
60 |
+
)
|
61 |
+
picked_box_probs.append(box_probs)
|
62 |
+
picked_labels.extend([class_index] * box_probs.shape[0])
|
63 |
+
if not picked_box_probs:
|
64 |
+
return np.array([]), np.array([]), np.array([])
|
65 |
+
picked_box_probs = np.concatenate(picked_box_probs)
|
66 |
+
picked_box_probs[:, 0] *= width
|
67 |
+
picked_box_probs[:, 1] *= height
|
68 |
+
picked_box_probs[:, 2] *= width
|
69 |
+
picked_box_probs[:, 3] *= height
|
70 |
+
return (
|
71 |
+
picked_box_probs[:, :4].astype(np.int32),
|
72 |
+
np.array(picked_labels),
|
73 |
+
picked_box_probs[:, 4],
|
74 |
+
)
|
75 |
+
|
76 |
+
def tta(self, src):
|
77 |
+
horizontal_rot = cv2.rotate(src, cv2.ROTATE_180)
|
78 |
+
grayscale = cv2.cvtColor(src, cv2.COLOR_RGB2GRAY)
|
79 |
+
grayscale = cv2.cvtColor(grayscale, cv2.COLOR_GRAY2RGB)
|
80 |
+
return [src, horizontal_rot, grayscale]
|
81 |
+
|
82 |
+
def find_spoof(self, img):
|
83 |
+
ret = []
|
84 |
+
threshold = 0.6
|
85 |
+
image_mean = np.array([127, 127, 127])
|
86 |
+
|
87 |
+
image = cv2.resize(img, (320, 240))
|
88 |
+
image = (image - image_mean) / 128
|
89 |
+
image = np.transpose(image, [2, 0, 1])
|
90 |
+
image = np.expand_dims(image, axis=0)
|
91 |
+
image = image.astype(np.float32)
|
92 |
+
|
93 |
+
confidences, boxes = self.face_model.run(None, {self.face_inputname: image})
|
94 |
+
boxes, _, _ = self.find_boxes(
|
95 |
+
img.shape[1], img.shape[0], confidences, boxes, threshold
|
96 |
+
)
|
97 |
+
|
98 |
+
classify_mean, classify_std = [0.485, 0.456, 0.406], [0.229, 0.224, 0.225]
|
99 |
+
for i in range(boxes.shape[0]):
|
100 |
+
(startX, startY, endX, endY) = boxes[i, :]
|
101 |
+
|
102 |
+
face = img[startY:endY, startX:endX]
|
103 |
+
if face.size == 0:
|
104 |
+
continue
|
105 |
+
face = cv2.cvtColor(face, cv2.COLOR_BGR2RGB)
|
106 |
+
|
107 |
+
# Preprocess
|
108 |
+
face = align_face(face)
|
109 |
+
face = crop_square(face, 256)
|
110 |
+
|
111 |
+
probs_all = []
|
112 |
+
for face in self.tta(face):
|
113 |
+
# Normalize
|
114 |
+
face = face / 255.0
|
115 |
+
face = (face - classify_mean) / classify_std
|
116 |
+
face = np.transpose(face, [2, 0, 1])
|
117 |
+
face = np.expand_dims(face, axis=0)
|
118 |
+
face = face.astype(np.float32)
|
119 |
+
|
120 |
+
predicted = self.classifier.run(None, {"input": face})
|
121 |
+
predicted_id = np.argmax(predicted)
|
122 |
+
probs = softmax(predicted[0][0])
|
123 |
+
probs_all.append(probs)
|
124 |
+
|
125 |
+
final_probs = np.mean(probs_all, axis=0)
|
126 |
+
predicted_id = np.argmax(final_probs)
|
127 |
+
ret.append(
|
128 |
+
{
|
129 |
+
"coords": (startX, startY, endX, endY),
|
130 |
+
"is_real": bool(predicted_id),
|
131 |
+
"probs": final_probs[predicted_id],
|
132 |
+
}
|
133 |
+
)
|
134 |
+
return ret
|