SmartFace Embedded Toolkit  4.2.1
Loading...
Searching...
No Matches
example_face_liveness.cpp
Go to the documentation of this file.
1
5#include "sfe_toolkit/sfe_core.h"
6#include "sfe_toolkit/sfe_face.h"
7#include <iomanip>
8#include <regex>
9#include <sstream>
10#include <vector>
11
12#include "annotate.hpp"
13#include "solvers.h"
14#include "utils.hpp"
15#include <unordered_map>
16
17std::string image_probe = "assets/face/images/obiwan0.png";
18
20std::string solver_face_detect = SOLVER_FACE_DETECT;
21std::string solver_face_landmarks = SOLVER_FACE_LANDMARKS;
22std::string solver_face_liveness = SOLVER_FACE_LIVENESS;
23
24const auto SOLVER_PARAMETERS = std::vector<SFESolverParameter>{};
25
27size_t face_size_min = 28;
28size_t face_size_max = 170;
29
31
39 SFEImage &image, const size_t face_size_min,
40 const size_t face_size_max,
41 size_t &recommended_width,
42 size_t &recommended_height) {
43
44 // If the face detection solver requires static input (filename contains width
45 // and height), we need to prepare the input image accordingly Typically the
46 // format is: face_detect_accurate_mask_w1920h1080_op11.onnxrt.solver NOTE:
47 // This can be hardcoded into the application, or we can parse the width and
48 // height from the solver filename
49
50 // Try to use regex capture to extract width and height from solver name
51 std::smatch width_height;
52 if (std::regex_match(solver_face_detect, width_height,
53 std::regex(".*w([0-9]+)h([0-9]+).*"))) {
54 recommended_width = std::stoi(width_height[1]);
55 recommended_height = std::stoi(width_height[2]);
56 } else {
58 // Solvers without width and height in name can accept dynamic input
59 // image size. For best results we recommend to scale the input image to
60 // a resolution recommended by the sfeFaceDetectInputSize function
61
62 // Use sfeFaceDetectInputSize to get recommended input image dimensions
63 // for given min_face_size/max_face_size
64 SFEDetectionInputSize input_size{};
66 image,
67 SFEFaceDetectionAccuracyType::SFE_FACE_DETECT_ACCURACY_TYPE_ACCURATE,
68 face_size_min, face_size_max, &input_size);
69 utils::checkError(error);
70 recommended_width = input_size.width;
71 recommended_height = input_size.height;
73 };
74}
75
76inline void printFaceDetectionInfo(SFEDetection &detected_face) {
77 std::cout << "SFEDetection{ " << std::endl;
78 std::cout << " confidence: " << detected_face.confidence << std::endl;
79 std::cout << " SFEBoundingBox{ " << std::endl;
80 std::cout << " x: " << detected_face.bounding_box.x << std::endl;
81 std::cout << " y: " << detected_face.bounding_box.y << std::endl;
82 std::cout << " width: " << detected_face.bounding_box.width << std::endl;
83 std::cout << " height: " << detected_face.bounding_box.height << std::endl;
84 std::cout << " }" << std::endl;
85 std::cout << " } " << std::endl;
86 std::cout << std::endl;
87}
88
89inline void prinFaceAreaInfo(SFEFaceArea &face_area) {
90 std::cout << "SFEFaceArea{ " << std::endl;
91 std::cout << " size: " << face_area.size << " [px]" << std::endl;
92 std::cout << " area: " << face_area.area << std::endl;
93 std::cout << " area_in_image: " << face_area.area_in_image << std::endl;
94 std::cout << "}" << std::endl;
95 std::cout << std::endl;
96}
97
98inline void printFaceHeadPose(SFEFaceHeadPose &head_pose) {
99 std::cout << "SFEFaceHeadPose{" << std::endl;
100 std::cout << " pitch: " << head_pose.pitch << std::endl;
101 std::cout << " yaw: " << head_pose.yaw << std::endl;
102 std::cout << " roll: " << head_pose.roll << std::endl;
103 std::cout << "} " << std::endl;
104 std::cout << std::endl;
105}
106
107inline void printFaceSize(size_t face_size) {
108 std::cout << "Face size: " << face_size << " [px]" << std::endl;
109 std::cout << std::endl;
110}
111
112inline void printMaskConfidence(float mask_confidence) {
113 std::cout << "Mask confidence: " << mask_confidence << std::endl;
114 std::cout << std::endl;
115}
116
117inline void
119 std::cout << "SFEFaceQualityAttributes{" << std::endl;
120 std::cout << " sharpness: " << quality_attributes.sharpness << std::endl;
121 std::cout << " brightness: " << quality_attributes.brightness << std::endl;
122 std::cout << " contrast: " << quality_attributes.contrast << std::endl;
123 std::cout << " unique_intensity_levels: "
124 << quality_attributes.unique_intensity_levels << std::endl;
125 std::cout << "} " << std::endl;
126 std::cout << std::endl;
127}
128
129void printHelp() {
130 std::cout << "Help: Usage of the program." << std::endl;
131 std::cout << "Options:" << std::endl;
132 std::cout << "-h: Display help." << std::endl;
133 std::cout << "-p: Probe image file." << std::endl;
134 std::cout << "-d: Path to detector solver." << std::endl;
135 std::cout << "-l: Path to landmarks solver." << std::endl;
136 std::cout << "-i: Path to liveness solver." << std::endl;
137 std::cout << "-t: Face detection threshold. <0,1>" << std::endl;
138 std::cout << "-m: Minimal face size in pixels to detect." << std::endl;
139 std::cout << "-x: Max face size in pixels to detect." << std::endl;
140}
141
143int main(int argc, char *argv[]) {
144
145 { // STAGE: 0 Parse command line arguments
146 // Map to store argument values
147 std::unordered_map<std::string, std::string> args;
148
149 // Parse command line arguments
150 for (int i = 1; i < argc; ++i) {
151 std::string arg = argv[i];
152 if (arg[0] == '-') {
153 if (arg == "-h") {
154 printHelp();
155 return 0;
156 }
157 // Check if there's a next argument and it isn't another option
158 if (i + 1 < argc && argv[i + 1][0] != '-') {
159 args[arg] = argv[++i];
160 } else {
161 std::cerr << "Option " << arg << " requires a value." << std::endl;
162 return 1;
163 }
164 } else {
165 std::cerr << "Unknown option: " << arg << std::endl;
166 return 1;
167 }
168 }
169
170 // Assign values to variables based on parsed arguments
171 if (args.count("-p"))
172 image_probe = args["-p"];
173 if (args.count("-d"))
174 solver_face_detect = args["-d"];
175 if (args.count("-l"))
176 solver_face_landmarks = args["-l"];
177 if (args.count("-i"))
178 solver_face_liveness = args["-i"];
179 if (args.count("-t"))
180 detection_threshold = std::stof(args["-t"]);
181 if (args.count("-m"))
182 face_size_min = std::stoi(args["-m"]);
183 if (args.count("-x"))
184 face_size_max = std::stoi(args["-x"]);
185
186 utils::printFormatted("PARAMETERS");
187 // Print resource names
188 std::cout << "Probe image: " << image_probe << std::endl;
189
190 // Print solver names
191 std::cout << "Face detection solver: " << solver_face_detect << std::endl;
192 std::cout << "Face landmarks solver: " << solver_face_landmarks
193 << std::endl;
194 std::cout << "Face liveness solver: " << solver_face_liveness << std::endl;
195
196 // Print other options
197 std::cout << "Min face size [px]: " << face_size_min << std::endl;
198 std::cout << "Max face size [px]: " << face_size_max << std::endl;
199 std::cout << "Detection threshold: " << detection_threshold << std::endl;
200 }
201
202 utils::printToolkitInfo();
203
204 SFEError error{};
205
206 SFESolver detector_solver{};
207 DEFER(sfeSolverFree(detector_solver));
208 SFESolver landmarks_solver{};
209 DEFER(sfeSolverFree(landmarks_solver));
210 SFESolver liveness_solver{};
211 DEFER(sfeSolverFree(liveness_solver));
212 { // STAGE 1: loading solvers
213 utils::printFormatted("LOADING solvers");
214 // Initialize face detection solver
215 error = sfeSolverCreate(
217 SOLVER_PARAMETERS.size(), &detector_solver);
218 utils::checkError(error);
219
220 // Initialize landmarks detection solver
221 error = sfeSolverCreate(
223 SOLVER_PARAMETERS.size(), &landmarks_solver);
224 utils::checkError(error);
225
226 // Initialize face liveness solver
227 error = sfeSolverCreate(
229 SOLVER_PARAMETERS.size(), &liveness_solver);
230 utils::checkError(error);
231 }
232
233 SFEImage image{};
234 DEFER(sfeImageFree(image));
235 SFEImage resized_image{};
236 DEFER(sfeImageFree(resized_image));
237 { // STAGE 2: Load image
238
239 utils::printFormatted("FACE DETECTION");
241 // Load image data from file
242 auto image_data = utils::readFile(image_probe);
243
244 // Decode image from data
245 error = sfeImageDecode(image_data.data(), image_data.size(), &image);
246 utils::checkError(error);
248
249 size_t recommended_width{};
250 size_t recommended_height{};
251
252 // Calculate optimal input image size for face detection. Image will be
253 // resized to recommended size for optimal performance.
255 face_size_max, recommended_width,
256 recommended_height);
257
259 // Resize image to recommended size
260 // NOTE: This function will resize the image without preserving the aspect
261 // ratio of the image.
262 error = sfeImageResize(image, recommended_width, recommended_height,
263 &resized_image);
264 utils::checkError(error);
266 }
268 SFEDetection detected_face = {};
269 { // STAGE 3: Detect face in the image
270 size_t detection_count = 1;
271 // Detect face in the image
272 error = sfeDetect(detector_solver, resized_image, detection_threshold,
273 &detected_face, &detection_count);
274 utils::checkError(error);
275
276 if (detection_count == 0) {
277 std::cout << "No face detected in the probe image." << std::endl;
278 return 0;
279 } else {
280 std::cout << "Found " << detection_count
281 << " face(s) in the probe image. Using the face with highest "
282 "confidence."
283 << std::endl;
284 }
285 }
287
289 std::vector<SFEFaceLandmarks> landmarks(SFE_FACE_LANDMARK_COUNT);
290 { // STAGE 4: Get face landmarks
291
292 // Use landmarks detection solver to get relevant landmarks for
293 // the detected face
294 error = sfeFaceLandmarks(landmarks_solver, image, &detected_face,
295 landmarks.data());
296 utils::checkError(error);
297 }
299
301 SFEFaceLiveness liveness = {};
302 { // STAGE 5: Liveness check
303 utils::printFormatted("LIVENESS CHECK");
304 error = sfeFaceLivenessPassive(liveness_solver, image, landmarks.data(),
305 &liveness);
306 utils::checkError(error);
307 }
309
310 { // STAGE 6: Print the liveness score
311 std::cout << "Liveness score is " << liveness.score;
312 // Recommended threshold for liveness detection, see EER threshold for distant fast mode in the documentation
313 static const float LIVENESS_THRESHOLD = 0.81f;
314 if (liveness.score < LIVENESS_THRESHOLD) {
315 std::cout << " which is below " << LIVENESS_THRESHOLD
316 << " threshold. Face is spoof." << std::endl;
317 } else {
318 std::cout << " which is above " << LIVENESS_THRESHOLD
319 << " threshold. Face is genuine." << std::endl;
320 }
321 }
322
323 // Optional face attributes to check for further analysis. See the
324 // documentation for more information.
325 float face_size = 0;
326 float mask_confidence;
327 SFEFaceArea face_area = {};
328 SFEFaceHeadPose head_pose{};
329 SFEFaceQualityAttributes quality_attributes{};
330 { // STAGE 6: (optional) Face attributes
331 utils::printFormatted("FACE ATTRIBUTES");
332
334 error = sfeFaceSize(image, landmarks.data(), &face_size);
335 utils::checkError(error);
337
339 error = sfeFaceMaskConfidence(landmarks.data(), &mask_confidence);
340 utils::checkError(error);
342
344 error = sfeFaceArea(image, landmarks.data(), &face_area);
345 utils::checkError(error);
347
349 error = sfeFaceHeadPose(image, landmarks.data(), &head_pose);
350 utils::checkError(error);
352
354 error =
355 sfeFaceQualityAttributes(image, landmarks.data(), &quality_attributes);
356 utils::checkError(error);
358 }
359
360 { // STAGE 8: Print the face attributes
361 printFaceDetectionInfo(detected_face);
362 printFaceSize(face_size);
363 printMaskConfidence(mask_confidence);
364 prinFaceAreaInfo(face_area);
365 printFaceHeadPose(head_pose);
366 printFaceQualityAttributes(quality_attributes);
367 }
368
369 { // STAGE 9: Annotate the image
370 // Render bounding box
371 std::stringstream label;
372 label << "Face" << std::fixed << std::setprecision(2)
373 << " d:" << detected_face.confidence << " m:" << mask_confidence
374 << " l:" << liveness.score;
375
376 annotate::labelBox(image, label.str(), detected_face.bounding_box, annotate::WHITE,
377 annotate::GREEN);
378
379 // Render landmarks
380 for (auto &landmark : landmarks) {
381 auto x = landmark.x * image.width;
382 auto y = landmark.y * image.height;
383 annotate::circle(image, x, y, 3, annotate::YELLOW);
384 }
385
386 // Save annotated image
387 size_t size = image.width * image.height * 3;
388 auto png_file = std::vector<unsigned char>(size);
389 error = sfeImageEncode(image, SFE_IMAGE_FORMAT_PNG, png_file.data(), &size);
390 utils::checkError(error);
391 png_file.resize(size);
392 utils::saveFile("face_liveness.png", png_file);
393
394 std::cout << std::endl;
395 std::cout << "Annotated image saved to face_liveness.png" << std::endl;
396 }
397
398 utils::printFormatted("FINISHED");
399}
void printHelp()
void getRecommendedImageSize(const std::string &solver_face_detect, SFEImage &image, const size_t face_size_min, const size_t face_size_max, size_t &recommended_width, size_t &recommended_height)
Get recommended image size for face detection.
std::string solver_face_detect
Solvers to use in example, the defaults are filled in by CMake.
std::string image_probe
float detection_threshold
const auto SOLVER_PARAMETERS
size_t face_size_max
size_t face_size_min
Required size of the face to be detected.
std::string solver_face_landmarks
void printMaskConfidence(float mask_confidence)
std::string solver_face_liveness
void printFaceSize(size_t face_size)
void printFaceHeadPose(SFEFaceHeadPose &head_pose)
void prinFaceAreaInfo(SFEFaceArea &face_area)
void printFaceQualityAttributes(SFEFaceQualityAttributes &quality_attributes)
void printFaceDetectionInfo(SFEDetection &detected_face)
void sfeSolverFree(SFESolver solver)
Free memory associated with SFESolver.
SFEError sfeImageResize(SFEImageView image, size_t width, size_t height, SFEImage *out_image)
Resize image.
void * SFESolver
Solver provides an abstract interface over inference models and engines.
Definition sfe_core.h:102
void * SFEError
Error type used to hold optional error message.
Definition sfe_core.h:97
void sfeImageFree(SFEImage image)
Free memory associated with SFEImage.
@ SFE_IMAGE_FORMAT_PNG
Portable Network Graphics format.
Definition sfe_core.h:53
SFEError sfeImageEncode(SFEImageView image, SFEImageFormat image_format, unsigned char *out_data, size_t *in_out_data_len)
Encode SFEImage into a buffer with specified image_format.
SFEError sfeSolverCreate(const char *solver_file, const SFESolverParameter *solver_parameters, size_t solver_parameters_count, SFESolver *out_solver)
Create new solver from solver file.
SFEError sfeImageDecode(const unsigned char *data, size_t data_len, SFEImage *out_image)
Decode SFEImage from raw image data of various formats. Eg. PNG, JPEG ..
SFEError sfeDetect(SFESolver solver, SFEImageView image, float threshold, SFEDetection *out_detections, size_t *in_out_detection_count)
Detect objects in the source image using unified detection API.
SFEError sfeFaceArea(SFEImageView image, SFEFaceLandmarks face_landmarks[SFE_FACE_LANDMARK_COUNT], SFEFaceArea *out_area)
Get face area.
SFEError sfeFaceSize(SFEImageView image, SFEFaceLandmarks face_landmarks[SFE_FACE_LANDMARK_COUNT], float *face_size)
Get the face size in pixels from the face landmarks and the source image. Face size is defined as a m...
SFEError sfeFaceDetectInputSize(SFEImageView image, SFEFaceDetectionAccuracyType detection_mode, size_t min_face_size, size_t max_face_size, SFEDetectionInputSize *out_input_size)
Calculation of recommended input image width and height according to desired minimal and maximal size...
SFEError sfeFaceMaskConfidence(const SFEFaceLandmarks face_landmarks[SFE_FACE_LANDMARK_COUNT], float *out_mask_confidence)
Get confidence from given landmarks if the face is wearing a face mask.
#define SFE_FACE_LANDMARK_COUNT
Definition sfe_face.h:17
SFEError sfeFaceLandmarks(SFESolver solver, SFEImageView image, const SFEDetection *detection, SFEFaceLandmarks out_face_landmarks[SFE_FACE_LANDMARK_COUNT])
Detect 23 landmarks of the face detected in the area of source image marked with detection.
SFEError sfeFaceLivenessPassive(SFESolver solver, SFEImageView image, SFEFaceLandmarks face_landmarks[SFE_FACE_LANDMARK_COUNT], SFEFaceLiveness *out_liveness)
Passive liveness score calculation.
SFEError sfeFaceHeadPose(SFEImageView image, SFEFaceLandmarks face_landmarks[SFE_FACE_LANDMARK_COUNT], SFEFaceHeadPose *out_head_pose)
Calculate angle rotations of head towards camera reference frame from given landmarks.
SFEError sfeFaceQualityAttributes(SFEImageView image, SFEFaceLandmarks face_landmarks[SFE_FACE_LANDMARK_COUNT], SFEFaceQualityAttributes *out_quality_attributes)
Calculate face image quality attributes from given source image and landmarks data.
float y
Y coordinate of the top left corner of the bounding box relative to source image size - range <0,...
float width
Width of the bounding box relative to source image size - range <0,1>
float x
X coordinate of the top left corner of the bounding box relative to source image size - range <0,...
float height
Height of the bounding box relative to source image size - range <0,1>
Core detection - tagged union containing all detection types.
SFEBoundingBox bounding_box
Bounding box (common for all detection types)
float confidence
Detection confidence - range <0,1> (common for all detection types)
Detection input size struct.
Definition sfe_face.h:59
Face area struct.
Definition sfe_face.h:237
float area_in_image
size of face area intersected with the whole image and relative to the whole image; value in range <0...
Definition sfe_face.h:244
float area
size of face area relative to the whole image; value in range <0,1>
Definition sfe_face.h:241
float size
absolute face size
Definition sfe_face.h:239
Face head pose struct containing angle rotations of head.
Definition sfe_face.h:259
float yaw
Face attribute representing angle rotation of head towards camera reference frame around Y-axis as pe...
Definition sfe_face.h:265
float pitch
Face attribute representing angle rotation of head towards camera reference frame around X-axis as pe...
Definition sfe_face.h:262
float roll
Face attribute representing angle rotation of head towards camera reference frame around Z-axis as pe...
Definition sfe_face.h:268
Face liveness struct.
Definition sfe_face.h:220
float score
Normalized passive liveness score - range <0,1>
Definition sfe_face.h:222
Face quality attributes struct.
Definition sfe_face.h:283
float unique_intensity_levels
Normalized face attribute for evaluating whether an area of face has appropriate number of unique int...
Definition sfe_face.h:305
float sharpness
Normalized face attribute for evaluating whether an area of face image is not blurred....
Definition sfe_face.h:288
float brightness
Normalized face attribute for evaluating whether an area of face is correctly exposed....
Definition sfe_face.h:293
float contrast
Normalized face attribute for evaluating whether an area of face is contrast enough....
Definition sfe_face.h:299
Raw owned raster image representation, HWC|BGR order.
Definition sfe_core.h:70