doguscank commited on
Commit
b3f76cd
·
verified ·
1 Parent(s): 4a0a71b

Create facenet.py

Browse files
Files changed (1) hide show
  1. facenet.py +197 -0
facenet.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This script is mostly based on the openpose preprocessor script of
2
+ # the sd-webui-controlnet project by Mikubill.
3
+ # https://github.com/Mikubill/sd-webui-controlnet/blob/main/annotator/openpose/face.py
4
+
5
+ import numpy as np
6
+ import onnxruntime as ort
7
+ import cv2
8
+ from PIL import Image
9
+ import pathlib
10
+ from typing import Tuple, Union, List
11
+ from tqdm import tqdm
12
+
13
+
14
+ def smart_resize(image: np.ndarray, shape: Tuple[int, int]) -> np.ndarray:
15
+ """
16
+ Resize an image to a target shape while preserving aspect ratio.
17
+
18
+ Parameters
19
+ ----------
20
+ image : np.ndarray
21
+ The input image.
22
+ shape : Tuple[int, int]
23
+ The target shape (height, width).
24
+
25
+ Returns
26
+ -------
27
+ np.ndarray
28
+ The resized image
29
+ """
30
+
31
+ Ht, Wt = shape
32
+ if image.ndim == 2:
33
+ Ho, Wo = image.shape
34
+ Co = 1
35
+ else:
36
+ Ho, Wo, Co = image.shape
37
+ if Co == 3 or Co == 1:
38
+ k = float(Ht + Wt) / float(Ho + Wo)
39
+ return cv2.resize(
40
+ image,
41
+ (int(Wt), int(Ht)),
42
+ interpolation=cv2.INTER_AREA if k < 1 else cv2.INTER_LANCZOS4,
43
+ )
44
+ else:
45
+ return np.stack(
46
+ [smart_resize(image[:, :, i], shape) for i in range(Co)], axis=2
47
+ )
48
+
49
+
50
+ class FaceLandmarkDetector:
51
+ """
52
+ The OpenPose face landmark detector model using ONNXRuntime.
53
+
54
+ Parameters
55
+ ----------
56
+ face_model_path : str
57
+ The path to the ONNX model file.
58
+ """
59
+
60
+ def __init__(self, face_model_path: pathlib.Path) -> None:
61
+ """
62
+ Initialize the OpenPose face landmark detector model.
63
+
64
+ Parameters
65
+ ----------
66
+ face_model_path : pathlib.Path
67
+ The path to the ONNX model file.
68
+ """
69
+
70
+ # Initialize ONNX runtime session
71
+ self.session = ort.InferenceSession(
72
+ face_model_path, providers=["CPUExecutionProvider"]
73
+ )
74
+ self.input_name = self.session.get_inputs()[0].name
75
+
76
+ def _inference(self, face_img: np.ndarray) -> np.ndarray:
77
+ """
78
+ Run the OpenPose face landmark detector model on an image.
79
+
80
+ Parameters
81
+ ----------
82
+ face_img : np.ndarray
83
+ The input image.
84
+
85
+ Returns
86
+ -------
87
+ np.ndarray
88
+ The detected keypoints.
89
+ """
90
+
91
+ # face_img should be a numpy array: H x W x C (likely RGB or BGR)
92
+ H, W, C = face_img.shape
93
+
94
+ # Preprocessing
95
+ w_size = 384 # ONNX is exported for this size
96
+ # Resize input image
97
+ resized_img = cv2.resize(
98
+ face_img, (w_size, w_size), interpolation=cv2.INTER_LINEAR
99
+ )
100
+
101
+ # Normalize: /256.0 - 0.5 (mimicking original code)
102
+ x_data = resized_img.astype(np.float32) / 256.0 - 0.5
103
+
104
+ # Convert to channel-first format: (C, H, W)
105
+ x_data = np.transpose(x_data, (2, 0, 1))
106
+
107
+ # Add batch dimension: (1, C, H, W)
108
+ x_data = np.expand_dims(x_data, axis=0)
109
+
110
+ # Run inference
111
+ outputs = self.session.run(None, {self.input_name: x_data})
112
+
113
+ # Assuming the model's last output corresponds to the heatmaps
114
+ # and is shaped like (1, num_parts, h_out, w_out)
115
+ heatmaps_original = outputs[-1]
116
+
117
+ # Remove batch dimension: (num_parts, h_out, w_out)
118
+ heatmaps_original = np.squeeze(heatmaps_original, axis=0)
119
+
120
+ # Resize the heatmaps back to the original image size
121
+ num_parts = heatmaps_original.shape[0]
122
+ heatmaps = np.zeros((num_parts, H, W), dtype=np.float32)
123
+ for i in range(num_parts):
124
+ heatmaps[i] = cv2.resize(
125
+ heatmaps_original[i], (W, H), interpolation=cv2.INTER_LINEAR
126
+ )
127
+
128
+ peaks = self.compute_peaks_from_heatmaps(heatmaps)
129
+
130
+ return peaks
131
+
132
+ def __call__(
133
+ self,
134
+ face_img: Union[np.ndarray, List[np.ndarray], Image.Image, List[Image.Image]],
135
+ ) -> List[np.ndarray]:
136
+ """
137
+ Run the OpenPose face landmark detector model on an image.
138
+
139
+ Parameters
140
+ ----------
141
+ face_img : Union[np.ndarray, Image.Image, List[Image.Image]]
142
+ The input image or a list of input images.
143
+
144
+ Returns
145
+ -------
146
+ List[np.ndarray]
147
+ The detected keypoints.
148
+ """
149
+
150
+ if isinstance(face_img, Image.Image):
151
+ image_list = [np.array(face_img)]
152
+ elif isinstance(face_img, list):
153
+ if isinstance(face_img[0], Image.Image):
154
+ image_list = [np.array(img) for img in face_img]
155
+ elif isinstance(face_img, np.ndarray):
156
+ if face_img.ndim == 4:
157
+ image_list = [img for img in face_img]
158
+
159
+ results = []
160
+
161
+ for image in tqdm(image_list):
162
+ keypoints = self._inference(image)
163
+ results.append(keypoints)
164
+
165
+ return results
166
+
167
+ def compute_peaks_from_heatmaps(self, heatmaps: np.ndarray) -> np.ndarray:
168
+ """
169
+ Compute the peaks from the heatmaps.
170
+
171
+ Parameters
172
+ ----------
173
+ heatmaps : np.ndarray
174
+ The heatmaps.
175
+
176
+ Returns
177
+ -------
178
+ np.ndarray
179
+ The peaks, which are keypoints.
180
+ """
181
+
182
+ all_peaks = []
183
+ for part in range(heatmaps.shape[0]):
184
+ map_ori = heatmaps[part].copy()
185
+ binary = np.ascontiguousarray(map_ori > 0.05, dtype=np.uint8)
186
+
187
+ if np.sum(binary) == 0:
188
+ all_peaks.append([-1, -1])
189
+ continue
190
+
191
+ positions = np.where(binary > 0.5)
192
+ intensities = map_ori[positions]
193
+ mi = np.argmax(intensities)
194
+ y, x = positions[0][mi], positions[1][mi]
195
+ all_peaks.append([x, y])
196
+
197
+ return np.array(all_peaks)