Facial recognition
Introduction
Face SDK allows you to perform the following operations to compare biometric face templates:
- Verification (1:1) — comparison of two face templates for belonging to the same person (comparison of two faces).
- Identification (1:N) — comparison of one biometric face template with other face templates (face search through face database). The result of the recognition is a similarity score between the compared templates.
Processing Blocks for facial recognition
FACE_TEMPLATE_EXTRACTOR
— used to build biometric face templates.VERIFICATION_MODULE
— used for comparison of two faces.MATCHER_MODULE
— used for searching faces in the database.TEMPLATE_INDEX
— used to create a database of biometric face templates for searching in MATCHER_MODULE module.
Modifications and versions of facial recognition blocks
Modification of the FACE_TEMPLATE_EXTRACTOR
processing block determines the template generation speed and recognition accuracy. The slower the module is, the higher its recognition accuracy.
Currently, the following modifications exist:
- 1000
- 100
- 50
- 30
Version | Template creation (ms) | Template size (bytes) |
---|---|---|
1 | 647 | 296 |
Version | Template creation (ms) | Template size (bytes) |
---|---|---|
1 | 79 | 280 |
Version | Template creation (ms) | Template size (bytes) |
---|---|---|
1 | 37 | 288 |
Version | Template creation (ms) | Template size (bytes) |
---|---|---|
1 | 23 | 280 |
The default modification is "1000"
.
For VERIFICATION_MODULE
, MATCHER_MODULE
, TEMPLATE_INDEX
blocks modification and version determines Template type.
Facial recognition blocks specification
Processing Block configurable parameters
Face template extractor
- The input Context must contain an image in binary format and objects array from Face Detector and Face Fitter:
Click here to expand the input Context specification
{
"image" : {
"format": "NDARRAY",
"blob": "data pointer",
"dtype": "uint8_t",
"shape": [height, width, channels]
},
"objects": [{
"id": {"type": "long", "minimum": 0},
"class": "face",
"confidence": {"double", "minimum": 0, "maximum": 1},
"bbox": [x1, y2, x2, y2]
"keypoints": {
"left_eye_brow_left": {"proj" : [x, y]},
"left_eye_brow_up": {"proj" : [x, y]},
"left_eye_brow_right": {"proj" : [x, y]},
"right_eye_brow_left": {"proj" : [x, y]},
"right_eye_brow_up": {"proj" : [x, y]},
"right_eye_brow_right": {"proj" : [x, y]},
"left_eye_left": {"proj" : [x, y]},
"left_eye": {"proj" : [x, y]},
"left_eye_right": {"proj" : [x, y]},
"right_eye_left": {"proj" : [x, y]},
"right_eye": {"proj" : [x, y]},
"right_eye_right": {"proj" : [x, y]},
"left_ear_bottom": {"proj" : [x, y]},
"nose_left": {"proj" : [x, y]},
"nose": {"proj" : [x, y]},
"nose_right": {"proj" : [x, y]},
"right_ear_bottom": {"proj" : [x, y]},
"mouth_left": {"proj" : [x, y]},
"mouth": {"proj" : [x, y]},
"mouth_right": {"proj" : [x, y]},
"chin": {"proj" : [x, y]},
"points": ["proj": [x, y]]
}
}]
}
- After calling the Estimation Processing Block, each object from the "objects" array will be added attributes corresponding to this block. Biometric template will be stored in binary sample.
Click here to expand the output Context specification
[{
"keypoints": {},
"template": {
"face_template_extractor_{modification}_{version}": {
"format": "NDARRAY",
"blob": "data pointer",
"dtype": "uint8_t",
"shape": [size]
}
}
}]
Verification module
- The input container Context must contain two biometric templates written in the fields
"template1"
and"template2"
. The template type must correspond to the modification of the Processing Block.
Click here to expand input Context Specification
{
"template1" : {
"face_template_extractor_{modification}_{version}": {
"format": "NDARRAY",
"blob": "data pointer",
"dtype": "uint8_t",
"shape": [size]
}
},
"template2" : {
"face_template_extractor_{modification}_{version}": {
"format": "NDARRAY",
"blob": "data pointer",
"dtype": "uint8_t",
"shape": [size]
}
}
}
- After the verification processing block is called, the result will be placed in
"result"
.
Click here to expand output Context Specification
[{
"template2": {},
"result": {
"distance": {"long", "minimum": 0},
"score": {"double", "minimum": 0, "maximum": 1},
"far": {"double", "minimum": 0, "maximum": 1},
"frr": {"double", "minimum": 0, "maximum": 1},
}
}]
Template index module
- Input Context must contain an array of biometric templates in the `"templates" field.
Click here to expand input Context Specification
{
"templates" : [
"face_template_extractor_{modification}_{version}": {
"format": "NDARRAY",
"blob": "data pointer",
"dtype": "uint8_t",
"shape": [size]
},
]
}
- After calling the Template Index block, the resulting index will be placed in the
"template_index"
field.
Click here to expand output Context Specification
[{
"templates": {},
"template_index": {"Non-serializable"}
}]
Matcher module
- Input Context must contain an array
"template_index"
, obtained after running the module"TEMPLATE_INDEX"
. and a set of searched biometric templates placed in the array"queries"
.
Click here to expand input Context Specification
{
"queries": [
"template" : {
"face_template_extractor_{modification}_{version}": {
"format": "NDARRAY",
"blob": "data pointer",
"dtype": "uint8_t",
"shape": [size]
},
}
]
"template_index": {"Non-serializable"}
}
- After the Matcher module is called, the result will be placed in the array
"results"
Click here to expand output Context Specification
[{
"template_index": {"Non-serializable"},
"result": [{
"distance": {"long", "minimum": 0},
"score": {"double", "minimum": 0, "maximum": 1},
"far": {"double", "minimum": 0, "maximum": 1},
"frr": {"double", "minimum": 0, "maximum": 1},
}]
}]
Facial recognition results
- distance — distance between compared template vectors. The smaller is the value, the higher is the confidence in correct recognition.
- far — probability of erroneous confirmations when the system takes images of different people for the image of the same person.
- frr — probability of erroneous rejections when the system mistakes two images of the same person for images of different people.
- score — degree of similarity of faces from 0 (0%) to 1 (100%). High degree of similarity means that two biometric templates belong to the same person.
Examples of working with facial recognition blocks
Face template extraction
To obtain a face template from an image, follow these steps:
Create a configuration Context container and specify the values of "unit_type" for "FACE_TEMPLATE_EXTRACTOR" and "modification" and "version" for the modification you are interested in.
Example of creating a processing block can be found here
Pass the Context container obtained after the operation of face detection and fitter processing blocks.
Call the face template extractor.
- C++
- Python
- Flutter
- C#
- Java
auto configCtx = service->createContext();
configCtx["unit_type"] = "FACE_TEMPLATE_EXTRACTOR";
configCtx["modification"] = "{modification}";
pbio::ProcessingBlock blockFaceExtractor = service->createProcessingBlock(configCtx);
//------------------
// Creating a processing block and a Context container with a binary image
//------------------
faceDetector(ioData)
faceFitter(ioData)
blockFaceExtractor(ioData);configCtx = {"unit_type": "FACE_TEMPLATE_EXTRACTOR", "modification": "{modification}" }
blockFaceExtractor = service.create_processing_block(configCtx)
#------------------
# Creating a processing block and a Context container with a binary image
#------------------
faceDetector(ioData)
faceFitter(ioData)
blockFaceExtractor(ioData)ProcessingBlock blockFaceExtractor = service.createProcessingBlock({"unit_type": "FACE_TEMPLATE_EXTRACTOR", "modification": "{modification}"});
//------------------
// Creating a processing block and a Context container with a binary image
//------------------
Context ioData = faceDetector.process(ioData);
Context ioData = faceFitter.process(ioData);
Context ioData = blockFaceExtractor.process(ioData);Dictionary<object, object> configCtx = new();
configCtx["unit_type"] = "FACE_TEMPLATE_EXTRACTOR";
configCtx["modification"] = "{modification}";
ProcessingBlock blockFaceExtractor = service.CreateProcessingBlock(configCtx);
//------------------
// Creating a processing block and a Context container with a binary image
//------------------
faceDetector.Invoke(ioData);
faceFitter.Invoke(ioData);
blockFaceExtractor.Invoke(ioData);Context configCtx = service.createContext();
configCtx.get("unit_type").setString("FACE_TEMPLATE_EXTRACTOR");
configCtx.get("modification").setString("{modification}");
ProcessingBlock blockFaceExtractor = service.createProcessingBlock(configCtx);
//------------------
// Creating a processing block and a Context container with a binary image
//------------------
faceDetector.process(ioData)
faceFitter.process(ioData)
blockFaceExtractor.process(ioData);
Saving a face template
To save a face template to a file, follow these steps:
Get the binary data of the face template and the size of the template.
Create a file to write the binary data.
Write the binary data of the face template to the file.
- C++
- Python
- Flutter
- C#
- Java
auto template_ctx = ioData["objects"][0]["template"];
size_t template_size = template_ctx["face_template_extractor_{modification}_{version}"]["shape"][0].getLong();
uint8_t* template_ptr = template_ctx["face_template_extractor_{modification}_{version}"]["blob"].getDataPtr();
std::ofstream out("template.bin", std::ios::binary);
out.write(reinterpret_cast<char*>(template_ptr), template_size);template_ctx = ioData["objects"][0]["template"]
template_size = template_ctx["face_template_extractor_{modification}_{version}"]["shape"][0].get_value()
template_bytes = template_ctx["face_template_extractor_{modification}_{version}"]["blob"].get_bytes(template_size)
with open("template.bin", "wb") as file:
file.write(template_bytes)Context templateCtx = ioData["objects"][0]["template"];
int templateSize = templateCtx["face_template_extractor_{modification}_{version}"]["shape"][0].get_value();
Uint8List templateBytes = templateCtx["face_template_extractor_{modification}_{version}"]["blob"].getBytes(templateSize);
final out = File("template.bin");
await out.writeAsBytes(templateBytes);Context templateCtx = ioData["objects"][0]["template"];
int templateSize = (int) templateCtx["face_template_extractor_{modification}_{version}"]["shape"][0].GetLong();
byte[] templateBytes = templateCtx["face_template_extractor_{modification}_{version}"]["blob"].GetBytes(templateSize);
File.WriteAllBytes("template.bin", templateBytes);Context template_ctx = ioData.get("objects").get(0).get("template");
int template_size = (int)template_ctx.get("face_template_extractor_{modification}_{version}").get("shape").get(0).getLong();
byte[] template_bytes = template_ctx.get("face_template_extractor_{modification}_{version}").get("blob").getBytes(template_size);
try (FileOutputStream stream = new FileOutputStream("template.bin"))
{
stream.write(template_bytes);
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
Loading a face template
To load a face template from a file, follow these steps:
Open the file to read the binary data.
Create a byte array and read the data from the file.
Create a Context container for the face template.
- C++
- Python
- Flutter
- C#
- Java
std::ifstream input("template.bin", std::ios::binary);
uint8_t* template_ptr = new uint8_t[template_size];
input.read(reinterpret_cast<char*>(template_ptr), template_size);
auto template_ctx = service.createContext();
template_ctx["face_template_extractor_{modification}_{version}"]["blob"].setDataPtr(template_ptr, template_size);
template_ctx["face_template_extractor_{modification}_{version}"]["shape"].push_back(template_size);
template_ctx["face_template_extractor_{modification}_{version}"]["dtype"] = "uint8_t";
template_ctx["face_template_extractor_{modification}_{version}"]["format"] = "NDARRAY";with open("template.bin", "rb") as file:
template_bytes = file.read(template_size)
template_ctx = service.create_context({
"face_template_extractor_{modification}_{version}": {
"blob": template_bytes,
"shape": [template_size],
"dtype": "uint8_t",
"format": "NDARRAY",
}
})final input = File("template.bin");
Uint8List templateBytes = await input.readAsBytes();
Context templateCtx = service.createContext({
"face_template_extractor_{modification}_{version}": {
"blob": templateBytes,
"shape": [templateSize],
"dtype": "uint8_t",
"format": "NDARRAY",
}
});byte [] templateBytes = File.ReadAllBytes("template.bin");
Context templateCtx = service.CreateContext(new Dictionary<object, object>()
{
{
"face_template_extractor_{modification}_{version}", new Dictionary<object, object>()
{
{"blob", templateBytes},
{"shape", new List<object>(){templateSize}},
{"dtype", "uint8_t"},
{"format", "NDARRAY"},
}
}
});byte[] template_bytes = new byte[template_size];
try (FileInputStream stream = new FileInputStream("template.bin"))
{
stream.read(template_bytes);
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
Context template_ctx = service.createContext();
template_ctx.get("face_template_extractor_{modification}_{version}").get("blob").setBytes(template_bytes);
template_ctx.get("face_template_extractor_{modification}_{version}").get("shape").pushBack(template_size);
template_ctx.get("face_template_extractor_{modification}_{version}").get("dtype").setString("uint8_t");
template_ctx.get("face_template_extractor_{modification}_{version}").get("format").pushBack("NDARRAY");
Face verification
Create a configuration Context container and specify the values for "unit_type" as "VERIFICATION_MODULE", and specify the "modification" and "version" for the modification you are interested in.
Generate a Context container according to the specification. Pass the contents of the "template" field obtained when calling "FACE_TEMPLATE_EXTRACTOR" to keys "template1" and "template2".
Call the verification module.
- C++
- Python
- Flutter
- C#
- Java
Context verificationConfig = service.createContext();
verificationConfig["unit_type"] = "VERIFICATION_MODULE";
verificationConfig["modification"] = "{modification}";
api::ProcessingBlock verificationModule = service.createProcessingBlock(verificationConfig);
Context verificationData = service.createContext();
verificationData["template1"] = ioData["objects"][0]["template"];
verificationData["template2"] = ioData2["objects"][0]["template"];
verificationModule(verificationData);configCtx = {"unit_type": "VERIFICATION_MODULE", "modification": "{modification}"}
verificationModule = service.create_processing_block(configCtx)
verificationData = service.create_context({})
verificationData["template1"] = ioData1["objects"][0]["template"]
verificationData["template2"] = ioData2["objects"][0]["template"]
verificationModule(verificationData)
ProcessingBlock verificationModule = service.createProcessingBlock({"unit_type": "VERIFICATION_MODULE", "modification": "{modification}"});
Context verificationData = service.createContext({"template1": {}, "template2": {}});
verificationData["template1"].placeValues(ioData1["objects"][0]["template"])
verificationData["template2"].placeValues(ioData2["objects"][0]["template"])
verificationData = verificationModule.process(verificationData);Dictionary<object, object> verificationConfig = new();
verificationConfig["unit_type"] = "VERIFICATION_MODULE";
verificationConfig["modification"] = "{modification}";
ProcessingBlock verificationModule = service.CreateProcessingBlock(verificationConfig);
Context verificationData = service.CreateContext(null);
verificationData["template1"] = ioData["objects"][0]["template"];
verificationData["template2"] = ioData2["objects"][0]["template"];
verificationModule.Invoke(verificationData);Context verificationConfig = service.createContext();
verificationConfig.get("unit_type").setString("VERIFICATION_MODULE");
verificationConfig.get("modification").setString("{modification}");
ProcessingBlock verificationModule = service.createProcessingBlock(verificationConfig);
Context verificationData = service.createContext();
verificationData.get("template1").setContext(ioData.get("objects").get(0).get("template"));
verificationData.get("template2").setContext(ioData2.get("objects").get(0).get("template"));
verificationModule.process(verificationData);
Search for faces in the database
Create a configuration Context container and specify the values of "unit_type" for "TEMPLATE_INDEX", and specify the "modification" and "version" for the modification you are interested in.
Generate an input Context container according to the specification for "TEMPLATE_INDEX".
Call the processing block to create the template database.
Create a configuration Context container and specify the values of "unit_type" for "MATCHER_MODULE", and specify the "modification" and "version" for the modification you are interested in.
Generate an input Context container according to the specification for "MATCHER_MODULE".
Call the matcher module.
- C++
- Python
- Flutter
- C#
- Java
Context templateIndexConfig = service.createContext();
templateIndexConfig["unit_type"] = "TEMPLATE_INDEX";
templateIndexConfig["modification"] = "{modification}";
api::ProcessingBlock templateIndex = service.createProcessingBlock(templateIndexConfig);
// Creating the input Context container for templateIndex
Context templatesData = service.createContext();
for (const Context& object : ioData["objects"])
{
templatesData["templates"].push_back(object["template"]);
}
templateIndex(templatesData);
Context matcherConfig = service.createContext();
matcherConfig["unit_type"] = "MATCHER_MODULE";
matcherConfig["modification"] = "{modification}";
api::ProcessingBlock matcherModule = service.createProcessingBlock(matcherConfig);
// Forming the input Context container for matcherModule
Context matcherData = service.createContext();
matcherData["template_index"] = templatesData["template_index"];
matcherData["queries"].push_back(ioData2["objects"][0]);
matcherModule(matcherData);template_index = service.create_processing_block({"unit_type": "TEMPLATE_INDEX", "modification": "{modification}"})
templatesData = service.create_context([])
for object in ioData["objects"]:
templatesData["templates"].push_back(object["template"])
template_index(templatesData)
matcher_module = service.create_processing_block({"unit_type": "MATCHER_MODULE", "modification": "{modification}"})
matcher_data = service.create_context({})
matcher_data["template_index"] = templatesData["template_index"]
matcher_data["queries"].push_back(ioData2["objects"][0])
matcher_module(matcher_data)ProcessingBlock templateIndex = service.createProcessingBlock({"unit_type": "TEMPLATE_INDEX", "modification": "{modification}"});
// Creating the input Context container for templateIndex
Context templatesData = service.createContext({"templates": []});
for (int i = 0; i < ioData["objects"].len(); i++){
templatesData["templates"].pushBack(ioData["objects"][i]["template"]);
}
templateIndex.process(templatesData);
matcherModule = service.createProcessingBlock({"unit_type": "MATCHER_MODULE", "modification": "{modification}"});
// Forming the input Context container for matcherModule
Context matcherData = service.createContext({"template_index": {}, "queries": []});
matcherData["template_index"] = templatesData["template_index"];
matcherData["queries"].pushBack(ioData2["objects"][0]);
matcherModule.process(matcherData);Dictionary<object, object> templateIndexConfig = new();
templateIndexConfig["unit_type"] = "TEMPLATE_INDEX";
templateIndexConfig["modification"] = "{modification}";
ProcessingBlock templateIndex = service.CreateProcessingBlock(templateIndexConfig);
// Creating the input Context container for templateIndex
Context templatesData = service.CreateContext(new Dictionary<object, object>{{ "templates", new() }});
for (int i = 0; i < (int)ioData["objects"].Length(); i++)
{
templatesData["templates"].PushBack(ioData["objects"][i]["template"]);
}
templateIndex.Invoke(templatesData);
Dictionary<object, object> matcherConfig = new();
matcherConfig["unit_type"] = "MATCHER_MODULE";
matcherConfig["modification"] = "{modification}";
ProcessingBlock matcherModule = service.CreateProcessingBlock(matcherConfig);
// Creating the input Context container for matcherModule
Context matcherData = service.CreateContext(new Dictionary<object, object>{
{ "template_index", new() },
{ "queries", new() },
});
matcherData["template_index"] = templatesData["template_index"];
matcherData["queries"].PushBack(ioData2["objects"][0]);
matcherModule.Invoke(matcherData);
Context templateIndexConfig = service.createContext();
templateIndexConfig.get("unit_type").setString("TEMPLATE_INDEX");
templateIndexConfig.get("modification").setString("{modification}");
ProcessingBlock templateIndex = service.createProcessingBlock(templateIndexConfig);
// Creating the input Context container for templateIndex
Context templatesData = service.createContext();
Context objects = ioData.get("objects");
for (int i = 0; i < objects.size(); i++)
{
templatesData.get("templates").pushBack(objects.get(i).get("template"));
}
templateIndex.process(templatesData);
Context matcherConfig = service.createContext();
matcherConfig.get("unit_type").setString("MATCHER_MODULE");
matcherConfig.get("modification").setString("{modification}");
ProcessingBlock matcherModule = service.createProcessingBlock(matcherConfig);
// Forming the input Context container for matcherModule
Context matcherData = service.createContext();
matcherData.get("template_index").setContext(templatesData.get("template_index"));
matcherData.get("queries").pushBack(ioData2.get("objects").get(0));
matcherModule.process(matcherData);