Skip to content

Commit 6aafd08

Browse files
committed
Implemented liveness detection functionality within Docker
1 parent 8ba5304 commit 6aafd08

File tree

7 files changed

+294
-0
lines changed

7 files changed

+294
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/data

app.py

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
import sys
2+
sys.path.append('.')
3+
4+
import os
5+
import numpy as np
6+
import base64
7+
import io
8+
9+
from PIL import Image
10+
from flask import Flask, request, jsonify
11+
from facesdk import getMachineCode
12+
from facesdk import setActivation
13+
from facesdk import faceDetection
14+
from facesdk import initSDK
15+
from facebox import FaceBox
16+
17+
livenessThreshold = 0.7
18+
yawThreshold = 10
19+
pitchThreshold = 10
20+
rollThreshold = 10
21+
occlusionThreshold = 0.9
22+
eyeClosureThreshold = 0.7
23+
mouthOpeningThreshold = 0.5
24+
borderRate = 0.05
25+
smallFaceThreshold = 100
26+
lowQualityThreshold = 0.3
27+
hightQualityThreshold = 0.7
28+
luminanceDarkThreshold = 50
29+
luminanceLightThreshold = 200
30+
31+
maxFaceCount = 10
32+
33+
licensePath = "license.txt"
34+
license = ""
35+
36+
machineCode = getMachineCode()
37+
print("machineCode: ", machineCode.decode('utf-8'))
38+
39+
try:
40+
with open(licensePath, 'r') as file:
41+
license = file.read()
42+
except IOError as exc:
43+
print("failed to open license.txt: ", exc.errno)
44+
print("license: ", license)
45+
46+
ret = setActivation(license.encode('utf-8'))
47+
print("activation: ", ret)
48+
49+
ret = initSDK("data".encode('utf-8'))
50+
print("init: ", ret)
51+
52+
app = Flask(__name__)
53+
54+
@app.route('/check_liveness', methods=['POST'])
55+
def check_liveness():
56+
file = request.files['file']
57+
image = Image.open(file)
58+
image_np = np.asarray(image)
59+
60+
faceBoxes = (FaceBox * maxFaceCount)()
61+
faceCount = faceDetection(image_np, image_np.shape[1], image_np.shape[0], faceBoxes, maxFaceCount)
62+
63+
i = 0
64+
faces = []
65+
while (i < faceCount):
66+
j = 0
67+
landmark_68 = []
68+
while(j < 68):
69+
landmark_68.append({"x": faceBoxes[i].landmark_68[j * 2], "y": faceBoxes[i].landmark_68[j * 2 + 1]})
70+
j = j + 1
71+
faces.append({"x1": faceBoxes[i].x1, "y1": faceBoxes[i].y1, "x2": faceBoxes[i].x2, "y2": faceBoxes[i].y2,
72+
"liveness": faceBoxes[i].liveness,
73+
"yaw": faceBoxes[i].yaw, "roll": faceBoxes[i].roll, "pitch": faceBoxes[i].pitch,
74+
"face_quality": faceBoxes[i].face_quality, "face_luminance": faceBoxes[i].face_luminance, "eye_dist": faceBoxes[i].eye_dist,
75+
"left_eye_closed": faceBoxes[i].left_eye_closed, "right_eye_closed": faceBoxes[i].right_eye_closed,
76+
"face_occlusion": faceBoxes[i].face_occlusion, "mouth_opened": faceBoxes[i].mouth_opened,
77+
"landmark_68": landmark_68})
78+
i = i + 1
79+
80+
result = ""
81+
if faceCount == 0:
82+
result = "No face"
83+
elif faceCount > 1:
84+
result = "Multiple face"
85+
else:
86+
if faceBoxes[0].liveness > livenessThreshold:
87+
result = "Real"
88+
else:
89+
result = "Spoof"
90+
91+
isNotFront = True
92+
isOcclusion = False
93+
isEyeClosure = False
94+
isMouthOpening = False
95+
isBoundary = False
96+
isSmall = False
97+
quality = "Low"
98+
luminance = "Dark"
99+
if abs(faceBoxes[0].yaw) < yawThreshold and abs(faceBoxes[0].roll) < rollThreshold and abs(faceBoxes[0].pitch) < pitchThreshold:
100+
isNotFront = False
101+
102+
if faceBoxes[0].face_occlusion > occlusionThreshold:
103+
isOcclusion = True
104+
105+
if faceBoxes[0].left_eye_closed > eyeClosureThreshold or faceBoxes[0].right_eye_closed > eyeClosureThreshold:
106+
isEyeClosure = True
107+
108+
if faceBoxes[0].mouth_opened > mouthOpeningThreshold:
109+
isMouthOpening = True
110+
111+
if (faceBoxes[0].x1 < image_np.shape[1] * borderRate or
112+
faceBoxes[0].y1 < image_np.shape[0] * borderRate or
113+
faceBoxes[0].x1 > image_np.shape[1] - image_np.shape[1] * borderRate or
114+
faceBoxes[0].x1 > image_np.shape[0] - image_np.shape[0] * borderRate):
115+
isBoundary = True
116+
117+
if faceBoxes[0].eye_dist < smallFaceThreshold:
118+
isSmall = True
119+
120+
if faceBoxes[0].face_quality < lowQualityThreshold:
121+
quality = "Low"
122+
elif faceBoxes[0].face_quality < hightQualityThreshold:
123+
quality = "Medium"
124+
else:
125+
quality = "High"
126+
127+
if faceBoxes[0].face_luminance < luminanceDarkThreshold:
128+
luminance = "Dark"
129+
elif faceBoxes[0].face_luminance < luminanceLightThreshold:
130+
luminance = "Normal"
131+
else:
132+
luminance = "Light"
133+
134+
status = "ok"
135+
faceState = {"is_not_front": isNotFront, "is_occluded": isOcclusion, "eye_closed": isEyeClosure, "mouth_opened": isMouthOpening,
136+
"is_boundary_face": isBoundary, "is_small": isSmall, "quality": quality, "luminance": luminance, "result": result}
137+
response = jsonify({"face_state": faceState, "faces": faces})
138+
139+
response.status_code = 200
140+
response.headers["Content-Type"] = "application/json; charset=utf-8"
141+
return response
142+
143+
status = "ok"
144+
response = jsonify({"face_state": {"result": result}, "faces": faces})
145+
146+
response.status_code = 200
147+
response.headers["Content-Type"] = "application/json; charset=utf-8"
148+
return response
149+
150+
@app.route('/check_liveness_base64', methods=['POST'])
151+
def check_liveness_base64():
152+
content = request.get_json()
153+
imageBase64 = content['base64']
154+
image_data = base64.b64decode(imageBase64)
155+
156+
image = Image.open(io.BytesIO(image_data))
157+
image_np = np.asarray(image)
158+
159+
faceBoxes = (FaceBox * maxFaceCount)()
160+
faceCount = faceDetection(image_np, image_np.shape[1], image_np.shape[0], faceBoxes, maxFaceCount)
161+
162+
i = 0
163+
faces = []
164+
while (i < faceCount):
165+
j = 0
166+
landmark_68 = []
167+
while(j < 68):
168+
landmark_68.append({"x": faceBoxes[i].landmark_68[j * 2], "y": faceBoxes[i].landmark_68[j * 2 + 1]})
169+
j = j + 1
170+
faces.append({"x1": faceBoxes[i].x1, "y1": faceBoxes[i].y1, "x2": faceBoxes[i].x2, "y2": faceBoxes[i].y2,
171+
"liveness": faceBoxes[i].liveness,
172+
"yaw": faceBoxes[i].yaw, "roll": faceBoxes[i].roll, "pitch": faceBoxes[i].pitch,
173+
"face_quality": faceBoxes[i].face_quality, "face_luminance": faceBoxes[i].face_luminance, "eye_dist": faceBoxes[i].eye_dist,
174+
"left_eye_closed": faceBoxes[i].left_eye_closed, "right_eye_closed": faceBoxes[i].right_eye_closed,
175+
"face_occlusion": faceBoxes[i].face_occlusion, "mouth_opened": faceBoxes[i].mouth_opened,
176+
"landmark_68": landmark_68})
177+
i = i + 1
178+
179+
result = ""
180+
if faceCount == 0:
181+
result = "No face"
182+
elif faceCount > 1:
183+
result = "Multiple face"
184+
else:
185+
if faceBoxes[0].liveness > livenessThreshold:
186+
result = "Real"
187+
else:
188+
result = "Spoof"
189+
190+
isNotFront = True
191+
isOcclusion = False
192+
isEyeClosure = False
193+
isMouthOpening = False
194+
isBoundary = False
195+
isSmall = False
196+
quality = "Low"
197+
luminance = "Dark"
198+
if abs(faceBoxes[0].yaw) < yawThreshold and abs(faceBoxes[0].roll) < rollThreshold and abs(faceBoxes[0].pitch) < pitchThreshold:
199+
isNotFront = False
200+
201+
if faceBoxes[0].face_occlusion > occlusionThreshold:
202+
isOcclusion = True
203+
204+
if faceBoxes[0].left_eye_closed > eyeClosureThreshold or faceBoxes[0].right_eye_closed > eyeClosureThreshold:
205+
isEyeClosure = True
206+
207+
if faceBoxes[0].mouth_opened > mouthOpeningThreshold:
208+
isMouthOpening = True
209+
210+
if (faceBoxes[0].x1 < image_np.shape[1] * borderRate or
211+
faceBoxes[0].y1 < image_np.shape[0] * borderRate or
212+
faceBoxes[0].x1 > image_np.shape[1] - image_np.shape[1] * borderRate or
213+
faceBoxes[0].x1 > image_np.shape[0] - image_np.shape[0] * borderRate):
214+
isBoundary = True
215+
216+
if faceBoxes[0].eye_dist < smallFaceThreshold:
217+
isSmall = True
218+
219+
if faceBoxes[0].face_quality < lowQualityThreshold:
220+
quality = "Low"
221+
elif faceBoxes[0].face_quality < hightQualityThreshold:
222+
quality = "Medium"
223+
else:
224+
quality = "High"
225+
226+
if faceBoxes[0].face_luminance < luminanceDarkThreshold:
227+
luminance = "Dark"
228+
elif faceBoxes[0].face_luminance < luminanceLightThreshold:
229+
luminance = "Normal"
230+
else:
231+
luminance = "Light"
232+
233+
status = "ok"
234+
faceState = {"is_not_front": isNotFront, "is_occluded": isOcclusion, "eye_closed": isEyeClosure, "mouth_opened": isMouthOpening,
235+
"is_boundary_face": isBoundary, "is_small": isSmall, "quality": quality, "luminance": luminance, "result": result}
236+
response = jsonify({"face_state": faceState, "faces": faces})
237+
238+
response.status_code = 200
239+
response.headers["Content-Type"] = "application/json; charset=utf-8"
240+
return response
241+
242+
status = "ok"
243+
response = jsonify({"face_state": {"result": result}, "faces": faces})
244+
245+
response.status_code = 200
246+
response.headers["Content-Type"] = "application/json; charset=utf-8"
247+
return response
248+
249+
250+
if __name__ == '__main__':
251+
port = int(os.environ.get("PORT", 8080))
252+
app.run(host='0.0.0.0', port=port)

facebox.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from ctypes import *
2+
3+
class FaceBox(Structure):
4+
_fields_ = [("x1", c_int32), ("y1", c_int32), ("x2", c_int32), ("y2", c_int32),
5+
("liveness", c_float),
6+
("yaw", c_float), ("roll", c_float), ("pitch", c_float),
7+
("face_quality", c_float), ("face_luminance", c_float), ("eye_dist", c_float),
8+
("left_eye_closed", c_float), ("right_eye_closed", c_float),
9+
("face_occlusion", c_float), ("mouth_opened", c_float),
10+
("landmark_68", c_float * 136)
11+
]

facesdk.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import os
2+
3+
from ctypes import *
4+
from numpy.ctypeslib import ndpointer
5+
from facebox import FaceBox
6+
7+
libPath = os.path.abspath(os.path.dirname(__file__)) + '/libfacesdk1.so'
8+
facesdk = cdll.LoadLibrary(libPath)
9+
10+
getMachineCode = facesdk.getMachineCode
11+
getMachineCode.argtypes = []
12+
getMachineCode.restype = c_char_p
13+
14+
setActivation = facesdk.setActivation
15+
setActivation.argtypes = [c_char_p]
16+
setActivation.restype = c_int32
17+
18+
initSDK = facesdk.initSDK
19+
initSDK.argtypes = [c_char_p]
20+
initSDK.restype = c_int32
21+
22+
faceDetection = facesdk.faceDetection
23+
faceDetection.argtypes = [ndpointer(c_ubyte, flags='C_CONTIGUOUS'), c_int32, c_int32, POINTER(FaceBox), c_int32]
24+
faceDetection.restype = c_int32
25+

libfacesdk1.so

4.69 MB
Binary file not shown.

license.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
None

requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
flask
2+
flask-cors
3+
Pillow
4+
numpy

0 commit comments

Comments
 (0)