Spaces:
Running
Running
Working API Added
Browse files- .gitattributes +3 -0
- .gitignore +3 -0
- api/__init__.py +0 -0
- api/main.py +31 -0
- data/actor_embedding.json +0 -0
- src/image_tagger/__init__.py +0 -0
- src/image_tagger/components.py +61 -0
- src/image_tagger/config.py +15 -0
- src/image_tagger/main.py +21 -0
- src/image_tagger/utility.py +74 -0
- weights/face_detection_onnx_32.onnx +3 -0
- weights/keypoint_68_weights.dat +3 -0
- weights/siamese_onnx_32.onnx +3 -0
.gitattributes
CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
weights/face_detection_onnx_32.onnx filter=lfs diff=lfs merge=lfs -text
|
37 |
+
weights/siamese_onnx_32.onnx filter=lfs diff=lfs merge=lfs -text
|
38 |
+
weights/keypoint_68_weights.dat filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
__pycache__
|
2 |
+
*/__pycache__
|
3 |
+
*/*/__pycache__
|
api/__init__.py
ADDED
File without changes
|
api/main.py
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
import os
|
3 |
+
from PIL import Image
|
4 |
+
import numpy as np
|
5 |
+
from fastapi import FastAPI, UploadFile
|
6 |
+
|
7 |
+
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
|
8 |
+
from image_tagger.main import tagger
|
9 |
+
|
10 |
+
app = FastAPI(title="Face Recognition")
|
11 |
+
|
12 |
+
|
13 |
+
@app.get("/")
|
14 |
+
def display():
|
15 |
+
return "Welcome to Image Tagger Api"
|
16 |
+
|
17 |
+
|
18 |
+
@app.post("/predict")
|
19 |
+
def predict(file: UploadFile):
|
20 |
+
img = Image.open(file.file)
|
21 |
+
img = np.array(img)
|
22 |
+
img = np.transpose(img, (2, 0, 1)).astype(dtype=np.float32)
|
23 |
+
img /= 255.0
|
24 |
+
boxes, matrixs, keypoints, results = tagger(img)
|
25 |
+
|
26 |
+
return {
|
27 |
+
"predictions": results,
|
28 |
+
"boxes": boxes.tolist(),
|
29 |
+
"matrixs": [matrix.tolist() for matrix in matrixs],
|
30 |
+
"keypoints": [keypoint.tolist() for keypoint in keypoints],
|
31 |
+
}
|
data/actor_embedding.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
src/image_tagger/__init__.py
ADDED
File without changes
|
src/image_tagger/components.py
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import dlib
|
3 |
+
import numpy as np
|
4 |
+
import onnxruntime as ort
|
5 |
+
from image_tagger import config
|
6 |
+
from image_tagger.utility import FaceAligner
|
7 |
+
from image_tagger.utility import face_similarity
|
8 |
+
|
9 |
+
|
10 |
+
def face_recognition(aligned):
|
11 |
+
print(config.FACE_EMBEDDING)
|
12 |
+
with open(config.FACE_EMBEDDING, "r") as file:
|
13 |
+
actor_embeddings = json.load(file)
|
14 |
+
|
15 |
+
face_identified = []
|
16 |
+
for face in aligned:
|
17 |
+
embedding = face_embedding(face)
|
18 |
+
identity = []
|
19 |
+
for actor in sorted(actor_embeddings.keys()):
|
20 |
+
similarity = face_similarity(embedding, actor_embeddings[actor])
|
21 |
+
identity.append((actor, similarity))
|
22 |
+
identity = [
|
23 |
+
n
|
24 |
+
for i, (n, similar) in enumerate(sorted(identity, key=lambda x: x[1]))
|
25 |
+
if i < 3
|
26 |
+
]
|
27 |
+
face_identified.append(identity)
|
28 |
+
return face_identified
|
29 |
+
|
30 |
+
|
31 |
+
def face_detection(img):
|
32 |
+
session = ort.InferenceSession(
|
33 |
+
config.FACE_DETECTION_ONNX_WEIGHTS, providers=["CPUExecutionProvider"]
|
34 |
+
)
|
35 |
+
output = session.run([], {"input": img[np.newaxis, ...]})
|
36 |
+
return output
|
37 |
+
|
38 |
+
|
39 |
+
def face_alignment(img, boxes):
|
40 |
+
predictor = dlib.shape_predictor(config.FACE_KEYPOINT_DLIB_WEIGHTS)
|
41 |
+
aligner = FaceAligner(predictor)
|
42 |
+
matrix = []
|
43 |
+
aligned = []
|
44 |
+
facial_keypoints = []
|
45 |
+
|
46 |
+
for box in boxes:
|
47 |
+
align_image, align_matrix, face_keypoint = aligner.align(img, box)
|
48 |
+
matrix.append(align_matrix)
|
49 |
+
aligned.append(align_image)
|
50 |
+
facial_keypoints.append(face_keypoint)
|
51 |
+
|
52 |
+
return (aligned, matrix, facial_keypoints)
|
53 |
+
|
54 |
+
|
55 |
+
def face_embedding(img):
|
56 |
+
img = (img / 255.0 - config.MEAN) / config.STD
|
57 |
+
img = np.transpose(img, (2, 0, 1)).astype(np.float32)
|
58 |
+
img = img[np.newaxis, ...]
|
59 |
+
session = ort.InferenceSession(config.FACE_EMBEDDING_ONNX_WEIGHTS)
|
60 |
+
output = session.run([], {"input": img})[0][0].tolist()
|
61 |
+
return output
|
src/image_tagger/config.py
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import os
|
3 |
+
|
4 |
+
PROJECT_DIR=os.getcwd()
|
5 |
+
WEIGHT_DIR=os.path.join(PROJECT_DIR,"weights")
|
6 |
+
DATA_DIR=os.path.join(PROJECT_DIR,"data")
|
7 |
+
FACE_DETECTION_ONNX_WEIGHTS=os.path.join(WEIGHT_DIR,"face_detection_onnx_32.onnx")
|
8 |
+
FACE_KEYPOINT_DLIB_WEIGHTS=os.path.join(WEIGHT_DIR,"keypoint_68_weights.dat")
|
9 |
+
FACE_EMBEDDING_ONNX_WEIGHTS=os.path.join(WEIGHT_DIR,"siamese_onnx_32.onnx")
|
10 |
+
FACE_EMBEDDING=os.path.join(DATA_DIR,"actor_embedding.json")
|
11 |
+
MEAN=np.array([0.485,0.456,0.406])
|
12 |
+
STD=np.array([0.229,0.224,0.225])
|
13 |
+
THRESHOLD=10.50
|
14 |
+
PADDING=15
|
15 |
+
SIZE=224
|
src/image_tagger/main.py
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import argparse
|
2 |
+
from image_tagger.utility import preprocess_img
|
3 |
+
from image_tagger.components import face_detection
|
4 |
+
from image_tagger.components import face_alignment
|
5 |
+
from image_tagger.components import face_recognition
|
6 |
+
|
7 |
+
def tagger(img):
|
8 |
+
boxes,labels,scores=face_detection(img)
|
9 |
+
aligned,matrixs,keypoints=face_alignment(img, boxes)
|
10 |
+
results=face_recognition(aligned)
|
11 |
+
|
12 |
+
return (boxes, matrixs, keypoints, results)
|
13 |
+
|
14 |
+
if __name__ == '__main__':
|
15 |
+
parser=argparse.ArgumentParser()
|
16 |
+
parser.add_argument("-p","--path",required=True)
|
17 |
+
args=vars(parser.parse_args())
|
18 |
+
|
19 |
+
img=preprocess_img(args["path"])
|
20 |
+
boxes, matrixs, results = tagger(img)
|
21 |
+
|
src/image_tagger/utility.py
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import numpy as np
|
3 |
+
import dlib
|
4 |
+
from image_tagger import config
|
5 |
+
|
6 |
+
def preprocess_img(img_pth):
|
7 |
+
img=cv2.imread(img_pth)
|
8 |
+
img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
|
9 |
+
img=(np.transpose(img,(2,0,1)).astype(dtype=np.float32))
|
10 |
+
img/=255.0
|
11 |
+
|
12 |
+
return img
|
13 |
+
|
14 |
+
def face_similarity(embed_1,embed_2):
|
15 |
+
return np.sqrt(np.sum(np.square(np.array(embed_1)-np.array(embed_2))))
|
16 |
+
|
17 |
+
class FaceAligner():
|
18 |
+
def __init__(self,predictor,desired_left_eye=(0.35,0.35),desired_face_width=config.SIZE, desired_face_height=None):
|
19 |
+
self.predictor=predictor
|
20 |
+
self.desired_left_eye=desired_left_eye
|
21 |
+
self.desired_right_eye=tuple([1-val for val in desired_left_eye])
|
22 |
+
self.desired_face_width=desired_face_width
|
23 |
+
self.desired_face_height=desired_face_height
|
24 |
+
|
25 |
+
if self.desired_face_height is None:
|
26 |
+
self.desired_face_height=self.desired_face_width
|
27 |
+
|
28 |
+
def shape_to_np(self,shape, dtype="int"):
|
29 |
+
coords = np.zeros((68, 2), dtype=dtype)
|
30 |
+
for i in range(0, 68):
|
31 |
+
coords[i] = (shape.part(i).x, shape.part(i).y)
|
32 |
+
return coords
|
33 |
+
|
34 |
+
def align(self,img,box):
|
35 |
+
image=np.transpose(img,(1,2,0)).copy()
|
36 |
+
image=(image*255).astype("uint8")
|
37 |
+
|
38 |
+
x1=max(int(box[0])-config.PADDING,0)
|
39 |
+
x2=min(int(box[2])+config.PADDING,image.shape[1])
|
40 |
+
y1=max(int(box[1])-config.PADDING,0)
|
41 |
+
y2=min(int(box[3])+config.PADDING,image.shape[0])
|
42 |
+
|
43 |
+
dlib_box=dlib.rectangle(left=x1, top=y1, right=x2, bottom=y2)
|
44 |
+
|
45 |
+
face_keypoint=self.predictor(image,dlib_box)
|
46 |
+
face_keypoint=self.shape_to_np(face_keypoint)
|
47 |
+
|
48 |
+
left_eye_center=(np.mean(face_keypoint[36:42,0]),np.mean(face_keypoint[36:42,1]))
|
49 |
+
right_eye_center=(np.mean(face_keypoint[42:48,0]),np.mean(face_keypoint[42:48,1]))
|
50 |
+
|
51 |
+
dy=right_eye_center[1]-left_eye_center[1]
|
52 |
+
dx=right_eye_center[0]-left_eye_center[0]
|
53 |
+
|
54 |
+
angle=np.degrees(np.arctan2(dy, dx))
|
55 |
+
|
56 |
+
dist=np.sqrt((dx ** 2) + (dy ** 2))
|
57 |
+
desired_dist=self.desired_right_eye[0]-self.desired_left_eye[0]
|
58 |
+
desired_dist*=self.desired_face_width
|
59 |
+
scale=desired_dist/dist
|
60 |
+
|
61 |
+
eye_center=((left_eye_center[0] + right_eye_center[0]) // 2,
|
62 |
+
(left_eye_center[1] + right_eye_center[1]) // 2)
|
63 |
+
|
64 |
+
Matrix = cv2.getRotationMatrix2D(eye_center, angle, scale)
|
65 |
+
|
66 |
+
tX = self.desired_face_width * 0.5
|
67 |
+
tY = tY = self.desired_face_height * self.desired_left_eye[1]
|
68 |
+
Matrix[0, 2] += (tX - eye_center[0])
|
69 |
+
Matrix[1, 2] += (tY - eye_center[1])
|
70 |
+
|
71 |
+
(w, h) = (self.desired_face_width, self.desired_face_height)
|
72 |
+
aligned_img = cv2.warpAffine(image, Matrix, (w,h),flags=cv2.INTER_CUBIC)
|
73 |
+
|
74 |
+
return (aligned_img, Matrix, face_keypoint)
|
weights/face_detection_onnx_32.onnx
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:81ce635a5105481dfd45084b0d4715f9873f8e3e3326e3c768568d75fa2fbf67
|
3 |
+
size 76044313
|
weights/keypoint_68_weights.dat
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:fbdc2cb80eb9aa7a758672cbfdda32ba6300efe9b6e6c7a299ff7e736b11b92f
|
3 |
+
size 99693937
|
weights/siamese_onnx_32.onnx
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:02353e6dffae29fe97756ccf09153ed79feafb1edad158c81684919020733f26
|
3 |
+
size 45221862
|