SmartFace Embedded Toolkit  4.2.1
Loading...
Searching...
No Matches
example_face_identify.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 <regex>
8#include <vector>
9
10#include <iomanip>
11#include <sstream>
12
13#include "annotate.hpp"
14#include "solvers.h"
15#include "utils.hpp"
16#include <unordered_map>
17
18std::string image_gallery = "./assets/face/entities/";
19
20std::string image_probe = "assets/face/images/obiwan0.png";
21
23std::string solver_face_detect = SOLVER_FACE_DETECT;
24std::string solver_face_landmarks = SOLVER_FACE_LANDMARKS;
25std::string solver_face_template = SOLVER_FACE_EXTRACTION;
26
27const auto SOLVER_PARAMETERS = std::vector<SFESolverParameter>{};
28
30size_t face_size_min = 28;
31size_t face_size_max = 170;
32
35
43 SFEImage &image, const size_t face_size_min,
44 const size_t face_size_max,
45 size_t &recommended_width,
46 size_t &recommended_height) {
47
48 // If the face detection solver requires static input (filename contains width
49 // and height), we need to prepare the input image accordingly Typically the
50 // format is: face_detect_accurate_mask_w1920h1080_op11.onnxrt.solver NOTE:
51 // This can be hardcoded into the application, or we can parse the width and
52 // height from the solver filename
53
54 // Try to use regex capture to extract width and height from solver name
55 std::smatch width_height;
56 if (std::regex_match(solver_face_detect, width_height,
57 std::regex(".*w([0-9]+)h([0-9]+).*"))) {
58 recommended_width = std::stoi(width_height[1]);
59 recommended_height = std::stoi(width_height[2]);
60 } else {
61 // Solvers without width and height in name can accept dynamic input
62 // image size. For best results we recommend to scale the input image to
63 // a resolution recommended by the sfeFaceDetectInputSize function
64
65 // Use sfeFaceDetectInputSize to get recommended input image dimensions
66 // for given min_face_size/max_face_size
67 SFEDetectionInputSize input_size{};
69 image,
70 SFEFaceDetectionAccuracyType::SFE_FACE_DETECT_ACCURACY_TYPE_ACCURATE,
71 face_size_min, face_size_max, &input_size);
72 utils::checkError(error);
73 recommended_width = input_size.width;
74 recommended_height = input_size.height;
75 };
76}
77
78void printHelp() {
79 std::cout << "Help: Usage of the program." << std::endl;
80 std::cout << "Options:" << std::endl;
81 std::cout << "-h: Display help." << std::endl;
82 std::cout << "-p: Probe image file." << std::endl;
83 std::cout << "-g: Gallery folder." << std::endl;
84 std::cout << "-d: Path to detector solver." << std::endl;
85 std::cout << "-l: Path to landmarks solver." << std::endl;
86 std::cout << "-e: Path to extraction solver." << std::endl;
87 std::cout << "-t: Detection threshold. <0,1>" << std::endl;
88 std::cout << "-i: Identification threshold. <0,1>" << std::endl;
89 std::cout << "-m: Minimal face size in pixels to detect." << std::endl;
90 std::cout << "-x: Max face size in pixels to detect." << std::endl;
91}
92
93void detectFace(SFEImage &image, SFESolver &detector_solver,
94 SFEDetection &detected_face) {
95
96 SFEImage resized_image{};
97 DEFER(sfeImageFree(resized_image));
98 SFEError error{};
99 { // STAGE 1 Load image
100 size_t recommended_width{};
101 size_t recommended_height{};
102
103 // Calculate optimal input image size for face detection. Image will be
104 // resized to recommended size for optimal performance.
106 face_size_max, recommended_width,
107 recommended_height);
108
109 // Resize image to recommended size
110 // NOTE: This function will resize the image without preserving the aspect
111 // ratio of the image.
112
113 error = sfeImageResize(image, recommended_width, recommended_height,
114 &resized_image);
115 utils::checkError(error);
116 }
117
118 size_t detection_count = 1;
119 { // STAGE 2: Detect face in the image
120
121 // Detect face in the image
122 error = sfeDetect(detector_solver, resized_image, detection_threshold,
123 &detected_face, &detection_count);
124 utils::checkError(error);
125
126 if (detection_count == 0) {
127 std::ostringstream oss;
128 oss << "Error: No face detected in the image ";
129 throw std::runtime_error(oss.str());
130
131 } else if (detection_count > 1) {
132 std::ostringstream oss;
133 oss << "Error: Found " << detection_count
134 << " faces. Please provide an image with only one face.";
135
136 throw std::runtime_error(oss.str());
137 }
138
139 std::cout << "Detected face in the image." << std::endl;
140 }
141}
142
144int main(int argc, char *argv[]) {
145
146 { // STAGE 0: Parse command line arguments
147 // Map to store argument values
148 std::unordered_map<std::string, std::string> args;
149
150 // Parse command line arguments
151 for (int i = 1; i < argc; ++i) {
152 std::string arg = argv[i];
153 if (arg[0] == '-') {
154 if (arg == "-h") {
155 printHelp();
156 return 0;
157 }
158 // Check if there's a next argument and it isn't another option
159 if (i + 1 < argc && argv[i + 1][0] != '-') {
160 args[arg] = argv[++i];
161 } else {
162 std::cerr << "Option " << arg << " requires a value." << std::endl;
163 return 1;
164 }
165 } else {
166 std::cerr << "Unknown option: " << arg << std::endl;
167 return 1;
168 }
169 }
170
171 // Assign values to variables based on parsed arguments
172 if (args.count("-p"))
173 image_probe = args["-p"];
174 if (args.count("-g"))
175 image_gallery = args["-g"];
176 if (args.count("-d"))
177 solver_face_detect = args["-d"];
178 if (args.count("-l"))
179 solver_face_landmarks = args["-l"];
180 if (args.count("-e"))
181 solver_face_template = args["-e"];
182 if (args.count("-t"))
183 detection_threshold = std::stof(args["-t"]);
184 if (args.count("-i"))
185 identification_threshold = std::stof(args["-i"]);
186 if (args.count("-m"))
187 face_size_min = std::stoi(args["-m"]);
188 if (args.count("-x"))
189 face_size_max = std::stoi(args["-x"]);
190
191 utils::printFormatted("EXAMPLE PARAMETERS");
192 // Print resource names
193 std::cout << "Probe image: " << image_probe << std::endl;
194 std::cout << "Gallery image: " << image_gallery << std::endl;
195
196 // Print solver names
197 std::cout << "Face detection solver: " << solver_face_detect << std::endl;
198 std::cout << "Face landmarks solver: " << solver_face_landmarks
199 << std::endl;
200 std::cout << "Face template solver: " << solver_face_template << std::endl;
201
202 // Print other options
203 std::cout << "Min face size [px]: " << face_size_min << std::endl;
204 std::cout << "Max face size [px]: " << face_size_max << std::endl;
205 std::cout << "Detection threshold: " << detection_threshold << std::endl;
206 std::cout << "Identification threshold: " << identification_threshold
207 << std::endl;
208 }
209
210 utils::printToolkitInfo();
211
212 SFEError error{};
213
214 SFESolver detector_solver{};
215 DEFER(sfeSolverFree(detector_solver));
216 SFESolver landmarks_solver{};
217 DEFER(sfeSolverFree(landmarks_solver));
218 SFESolver template_solver{};
219 DEFER(sfeSolverFree(template_solver));
220 { // STAGE 1.1: loading solvers
221 utils::printFormatted("LOADING SOLVERS");
222 // Initialize face detection solver
223 error = sfeSolverCreate(
225 SOLVER_PARAMETERS.size(), &detector_solver);
226 utils::checkError(error);
227
228 // Initialize landmarks detection solver
229 error = sfeSolverCreate(
231 SOLVER_PARAMETERS.size(), &landmarks_solver);
232 utils::checkError(error);
233
234 // Initialize template extraction solver
235 error = sfeSolverCreate(
237 SOLVER_PARAMETERS.size(), &template_solver);
238 utils::checkError(error);
239 }
240
241 SFEFaceTemplate probe_face_template;
242 { // STAGE 1: Extract probe face template
243 utils::printFormatted("PROBE TEMPLATE EXTRACTION");
244
245 SFEImage image{};
246 DEFER(sfeImageFree(image));
247 { // STAGE 1.1: Load image
248 // Load image data from file
249 auto image_data = utils::readFile(image_probe);
250
251 // Decode image from data
252 error = sfeImageDecode(image_data.data(), image_data.size(), &image);
253 utils::checkError(error);
254 }
255
256 SFEDetection detected_face = {};
257 { // STAGE 1.2: Detect face in the image
258 // Detect a face in the image
259 detectFace(image, detector_solver, detected_face);
260 }
261
262 std::vector<SFEFaceLandmarks> landmarks(SFE_FACE_LANDMARK_COUNT);
263 { // STAGE 1.3: Get face landmarks
264 // Use landmarks detection solver to get relevant landmarks for
265 // the detected face
266 error = sfeFaceLandmarks(landmarks_solver, image, &detected_face,
267 landmarks.data());
268 utils::checkError(error);
269 }
270
272 { // STAGE 1.4: Extract probe face template
273 // Use template extraction solver to create new face
274 // template
275 error = sfeFaceTemplateExtract(template_solver, image, &detected_face,
276 landmarks.data(), &probe_face_template);
277 utils::checkError(error);
278 }
280
281 { // STAGE 1.5: (optional) Print template attributes
282 utils::printFormatted("PROBE TEMPLATE ATTRIBUTES");
283
285 SFEFaceTemplateVersion version = {};
286 error = sfeFaceTemplateVersion(&probe_face_template, &version);
287 utils::checkError(error);
288
289 auto version_minor = (int)version.version_minor - (int)'0';
290 std::cout << "Version of the probe template: " << version.version_major
291 << '.' << version_minor << std::endl;
293 }
294
296 { // STAGE 1.6: (optional) Export and re-import template
297 std::vector<uint8_t> buf(1024);
298 size_t size = buf.size();
299 error = sfeFaceTemplateExport(&probe_face_template, buf.data(), &size);
300 if (error && size > buf.size()) {
301 buf.resize(size);
302 size = buf.size();
303 error = sfeFaceTemplateExport(&probe_face_template, buf.data(), &size);
304 }
305 utils::checkError(error);
306
307 SFEFaceTemplate imported_template = {};
308 error = sfeFaceTemplateImport(buf.data(), size, &imported_template);
309 utils::checkError(error);
310
311 float roundtrip_score = 0.f;
312 error = sfeFaceTemplateMatch(&probe_face_template, &imported_template,
313 &roundtrip_score);
314 utils::checkError(error);
315 std::cout << "Export/import round-trip match score: " << roundtrip_score
316 << std::endl;
317 }
319 }
320
321 std::vector<std::string> image_paths{};
322 { // STAGE 2: Get gallery image paths
323 // Get folder names from the face image gallery
324 auto folder_names = utils::getFiles(image_gallery);
325
326 for (auto folder_name : folder_names) {
327 auto gallery_folder = image_gallery + folder_name + "/";
328
329 // Get image names from folder
330 auto image_names = utils::getFiles(gallery_folder);
331
332 // Extract template for each image in the folder
333 for (auto image_name : image_names) {
334
335 auto image_path = gallery_folder + image_name;
336
337 image_paths.push_back(image_path);
338 }
339 }
340 }
341
342 std::vector<SFEFaceTemplate> gallery_face_templates(image_paths.size());
343 std::vector<SFEDetection> detected_faces(image_paths.size());
344 std::vector<std::array<SFEFaceLandmarks, SFE_FACE_LANDMARK_COUNT>> landmarks(
345 image_paths.size());
346 { // STAGE 2: Load gallery image and extract face templates for each image in
347 // the gallery
348 utils::printFormatted("GALLERY TEMPLATE EXTRACTION");
349
350 for (size_t i = 0; i < image_paths.size(); i++) {
351 std::cout << "Processing image #" << i << ", path: " << image_paths[i]
352 << std::endl;
353 SFEImage image{};
354 DEFER(sfeImageFree(image));
355 { // STAGE 2.1: Load image
356 // Load image data from file
357 auto image_data = utils::readFile(image_paths[i]);
358
359 // Decode image from data
360 error = sfeImageDecode(image_data.data(), image_data.size(), &image);
361 utils::checkError(error);
362 }
363
364 { // STAGE 2.2: Detect face in the image
365 // Detect a face in the image
366 detectFace(image, detector_solver, detected_faces[i]);
367 }
368
369 { // STAGE 2.3: Get face landmarks
370 // Use landmarks detection solver to get relevant landmarks for
371 // the detected face
372 error = sfeFaceLandmarks(landmarks_solver, image, &detected_faces[i],
373 landmarks[i].data());
374 utils::checkError(error);
375 }
376
377 { // STAGE 2.3: Extract face template
378 // Use template extraction solver to get face_template solver
380 template_solver, image, &detected_faces[i], landmarks[i].data(),
381 &gallery_face_templates[i]);
382 utils::checkError(error);
383 std::cout << "Extracted face template." << std::endl;
384 }
385 std::cout << std::endl;
386 }
387 }
388
390 size_t candidate_count = 1;
391 int best_candidate_index = -1;
392 std::vector<SFETemplateIdentificationCandidate> identification_results(
393 image_paths.size());
394 { // STAGE 3: Identification
395 utils::printFormatted("1:N IDENTIFICATION");
396
398 &probe_face_template, gallery_face_templates.data(),
399 gallery_face_templates.size(), identification_threshold,
400 identification_results.data(), &candidate_count, 4);
401 utils::checkError(error);
402
403 // Resize the results vector to the actual number of candidates found, in
404 // the case there is less than candidate_count
405 identification_results.resize(candidate_count);
406
407 std::cout << "Found " << identification_results.size()
408 << " candidates above identification threshold "
409 << identification_threshold << std::endl;
410 for (auto &result : identification_results)
411 std::cout << "Template index: #" << result.index
412 << ", score: " << result.score << std::endl;
413
414 // Since it's sorted vector by score, the best candidate is the first one
415 best_candidate_index = identification_results[0].index;
416 }
418
420 { // STAGE 4: (optional) 1:1 matching with top candidate to showcase 1:1
421 // matching
422 utils::printFormatted("1:1 MATCHING WITH TOP CANDIDATE");
423
424 if (identification_results.size() == 0) {
425 std::cout << "No candidates found above identification threshold "
426 << identification_threshold << std::endl;
427 return 0;
428 }
429
430 auto best_candidate_template = gallery_face_templates[best_candidate_index];
431
432 float match_confidence;
433 error = sfeFaceTemplateMatch(&probe_face_template, &best_candidate_template,
434 &match_confidence);
435 utils::checkError(error);
436
437 std::cout << "Matching score of probe template with template index #"
438 << best_candidate_index << ", score: " << match_confidence
439 << std::endl;
440 }
442
443 { // STAGE: 5 Optional, get face crop of best face and its bounding box and
444 // save it to file
445 utils::printFormatted("SAVE FACE CROP AND ANNOTATED IMAGE");
446
447 auto best_face_index = identification_results[0].index;
448
449 SFEImage image{};
450 DEFER(sfeImageFree(image));
451 { // STAGE 5.1: Load image
452 // Load image data from file
453 auto image_data = utils::readFile(image_paths[best_face_index]);
454
455 // Decode image from data
456 error = sfeImageDecode(image_data.data(), image_data.size(), &image);
457 utils::checkError(error);
458 }
459
461 SFEFaceCrop crop_data = {};
462 DEFER(sfeImageFree(crop_data.crop_image));
463 { // STAGE 5.2: Get face crop
464 // Defines the size of the crop as an extension of
465 // the detection bounding box.
466 float face_size_extension = 2.0f;
467 SFEError error =
468 sfeFaceCrop(image, landmarks[best_face_index].data(),
469 face_size_extension, (float)(face_size_max), &crop_data);
470 utils::checkError(error);
471
472 std::cout << "Face crop extension: " << crop_data.face_size_extension
473 << ", width of cropped image: " << crop_data.crop_image.width
474 << "px, height of cropped image: "
475 << crop_data.crop_image.height << "px." << std::endl;
476 }
478
479 { // STAGE 5.3: Save face crop to file
480 auto png_file = std::vector<unsigned char>();
481 size_t size =
482 crop_data.crop_image.width * crop_data.crop_image.height * 3;
483 png_file.resize(size);
485 png_file.data(), &size);
486 utils::checkError(error2);
487
488 std::cout << "Face crop saved as crop_identified_person.png" << std::endl;
489 utils::saveFile("crop_identified_person.png", png_file);
490 }
491
492 { // STAGE 5.4: Annotate the image
493 // Render bounding box with detection confidence and identification score
494 std::stringstream label;
495 label << " D:" << std::fixed << std::setprecision(2)
496 << detected_faces[best_face_index].confidence
497 << " M:" << identification_results[0].score;
498
499 auto color = annotate::RED;
500
501 annotate::labelBox(image, label.str(),
502 detected_faces[best_candidate_index].bounding_box,
503 annotate::WHITE, color);
504
505 // Render landmarks
506 for (auto &landmark : landmarks[best_face_index]) {
507 auto x = landmark.x * image.width;
508 auto y = landmark.y * image.height;
509 annotate::circle(image, x, y, 3, annotate::YELLOW);
510 }
511
512 // Save annotated image
513 size_t size = image.width * image.height * 3;
514 auto png_file = std::vector<unsigned char>(size);
515 error = sfeImageEncode(image, SFE_IMAGE_FORMAT_PNG, png_file.data(), &size);
516 utils::checkError(error);
517 png_file.resize(size);
518 utils::saveFile("face_identify.png", png_file);
519
520 std::cout << std::endl;
521 std::cout << "Annotated image saved as face_identify.png" << std::endl;
522 }
523 }
524
525 utils::printFormatted("FINISHED");
526}
void printHelp()
float identification_threshold
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.
void detectFace(SFEImage &image, SFESolver &detector_solver, SFEDetection &detected_face)
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
std::string solver_face_template
std::string image_gallery
std::string image_path
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 sfeFaceTemplateIdentify(SFEFaceTemplate *probe_face_template, SFEFaceTemplate *templates_gallery, size_t gallery_size, float matching_score_threshold, SFETemplateIdentificationCandidate *out_candidates, size_t *in_out_candidates_count, size_t thread_count)
Get an ordered array of SFETemplateIdentificationCandidate from tested template best matches of probe...
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 sfeFaceTemplateMatch(SFEFaceTemplate *template1, SFEFaceTemplate *template2, float *out_matching_score)
Match two face templates.
#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 sfeFaceCrop(SFEImageView image, SFEFaceLandmarks face_landmarks[SFE_FACE_LANDMARK_COUNT], float face_size_extension, float max_face_size, SFEFaceCrop *out_crop)
Crop the face according the given landmarks, the face size extension and the maximal face size....
SFEError sfeFaceTemplateExport(const SFEFaceTemplate *face_template, uint8_t *bytes, size_t *size)
Export face template to bytes that can be shared between Innovatrics components.
SFEError sfeFaceTemplateImport(const uint8_t *bytes, size_t size, SFEFaceTemplate *out_face_template)
Import face template from bytes; format is auto-detected (iface-template protobuf or raw ICF 522-byte...
SFEError sfeFaceTemplateExtract(SFESolver solver, SFEImageView image, const SFEDetection *detection, const SFEFaceLandmarks face_landmarks[SFE_FACE_LANDMARK_COUNT], SFEFaceTemplate *out_face_template)
Extract template from source image and given face landmarks.
SFEError sfeFaceTemplateVersion(SFEFaceTemplate *face_template, SFEFaceTemplateVersion *out_face_template_version)
Get face template version.
Core detection - tagged union containing all detection types.
Detection input size struct.
Definition sfe_face.h:59
Face crop struct.
Definition sfe_face.h:367
SFEImage crop_image
Image of face cropped according the crop_box.
Definition sfe_face.h:374
float face_size_extension
Defines the size of the crop as an extension of the detection bounding box.
Definition sfe_face.h:370
Face template struct.
Definition sfe_face.h:107
uint8_t data[SFE_FACE_TEMPLATE_SIZE]
Definition sfe_face.h:108
Face template version struct.
Definition sfe_face.h:112
uint8_t version_major
major template version
Definition sfe_face.h:114
uint8_t version_minor
minor template version
Definition sfe_face.h:116
Raw owned raster image representation, HWC|BGR order.
Definition sfe_core.h:70
size_t width
Width of the image in pixels.
Definition sfe_core.h:72
size_t height
Height of the image in pixels.
Definition sfe_core.h:74