Quality control
Facial Image Quality Control Processing Block
Facial Image Quality Control
helps reduce recognition errors by excluding low-quality facial images from facial recognition pipeline — for example, images that are noisy, too small, rotated in profile, or otherwise unsuitable.
Biometric templates extracted from such images usually do not accurately match the profiles in the database, which inevitably leads to facial recognition errors.
Face SDK provides several approaches to quality control, corresponding to different modifications of the QUALITY_CONTROL
processing block:
core
— the defaultQUALITY_CONTROL
modification that evaluates the most critical quality parameters (face size, frontal alignment, image noise, etc.) for both recognition and Liveness estimation. The output is a pass/fail verdict along with a list of checks that were not passed.estimation
— the modification that uses a neural network to generate a numerical score indicating how suitable the facial image is for recognition. While this score effectively filters out low-quality images, it is hard to interpret, because it does not return direct feedback on what exactly is wrong with the image and how to improve it. This modification is suitable for scenarios where providing end users with guidance on improving their photos is not necessary.
Modification | Version | Face SDK version | Detection time CPU (ms)* | Detection time GPU (ms)** |
---|---|---|---|---|
core | 1 | 3.28 | 39 | 39 |
estimation | 1 | 3.19 | 95 | 5 |
** - GPU (NVIDIA GTX 10xx series)
All Quality Control
modifications expect an input Context container, which includes a binary image and an array of objects obtained from the Face Detector and Face Fitter processing blocks:
Click to expand the Context input container 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]}
}
}]
}
core modification
Creating a Processing Block for the core
modification is the same as for any other block, but it has a number of specific parameters:
{
"unit_type": "QUALITY_CONTROL",
"modification": "core",
"version": 1,
"mode": ["recognition", "liveness"],
"preset": {"minimal", "optimal", "maximum"},
"{check_name}_threshold": 0
}
“mode” — requires passing an array of strings that specify the processing block's operating modes. Currently, the available options are “liveness” and “recognition” — pass either one of these values or both. The modes differ in their sets of checks and default threshold values.
“preset” — accepts one of three values: “minimal”, “optimal”, ‘maximum’. The value passed to “preset” determines the specific threshold values for the check:
“minimal” — corresponds to the least demanding threshold values. Suitable for most use cases, filters out a small number of images.
“optimal” — corresponds to average threshold values. Suitable for cases where image quality issues are not generally expected, discards a larger number of images, and significantly reduces errors.
“maximum” — corresponds to the most demanding quality threshold values. Suitable for cases where maximum image quality is required, discards the largest number of low-quality images.
You can adjust the threshold for a specific check by passing the parameter
“{check_name}_threshold”
and the corresponding numerical value to Context, for example,“eyes_distance_check”: 42
.
The table below displays a list of checks implemented in the core
modification, with threshold values corresponding to different presets. Note that when both modes are enabled at the same time, all checks are applied. If a check is present in both “liveness” and “recognition”, the most stringent threshold is used.
- Quality Control for Face Recognition
- Quality Control for Liveness Detection
Check | Evaluated parameter | minimal threshold | optimal threshold | maximum threshold |
---|---|---|---|---|
noise | Maximum acceptable image noise in score from 0 to 1 | 0.21 | 0.4 | 0.5 |
dynamic_range | Maximum acceptable dynamic range of intensity in scores from 0 to 2.55 | 0.72 | 0.96 | 1.36 |
sharpness | Maximum acceptable image sharpness in score from 0 to 1 | 0.09 | 0.19 | 0.33 |
pitch | Maximum acceptable face rotation around the pitch axis in degrees | 44 | 33 | 23 |
yaw | Maximum acceptable face rotation around the yaw axis in degrees | 63 | 50 | 50 |
face_overflow | Face out of image in score from 0 to 1 | 0.0 | 0.0 | 0.0 |
eye_distance | Distance between pupils in pixels | 25 | 35 | 35 |
Check | Evaluated parameter | minimal threshold | optimal threshold | maximum threshold |
---|---|---|---|---|
noise | Maximum acceptable image noise in score from 0 to 1 | 0.32 | 0.5 | 0.5 |
dynamic_range | Maximum acceptable dynamic range of intensity in scores from 0 to 2.55 | 0.5 | 0.7 | 1.2 |
sharpness | Maximum acceptable image sharpness in score from 0 to 1 | 0.1 | 0.14 | 0.2 |
pitch | Maximum acceptable face rotation around the pitch axis in degrees | 30 | 30 | 20 |
yaw | Maximum acceptable face rotation around the yaw axis in degrees | 44 | 28 | 28 |
face_overflow | Face out of image in score from 0 to 1 | 0.0 | 0.0 | 0.0 |
glasses | Estimate the presence of sunglasses on a scale of 0 to 1 | 0.95 | 0.95 | 0.95 |
face_size_on_image | Minimum proportion of the face relative to the entire image | 0.011 | 0.013 | 0.013 |
gray_scale | Checks whether the image is color. Not configurable. | - | - | - |
The graphs below show, using the “recognition” mode as an example, how certain checks at different threshold values affect the FRR recognition error at a fixed FAR=10E-6. These graphs are based on internal 3DiVi datasets, which correspond to remote identification and cooperative ACS cases.
- pitch
- yaw
- sharpness
- noise
- dynamic_range
Output Context Specification
After calling the Quality Control
processing block, attributes corresponding to this block will be added to each object from the “objects”
array. The format of the output container Context modification core
looks as follows:
{
"quality": {
"value": {"type": "boolean"} // true — all checks passed , false — there are failured checks
"failed_checks":[ // list of failured checks; if value==true this list is empty
{"check": {check_name}, // check name
"result": {chec_value}, // failed result value
},
...
]
}
estimation modification
In the "estimation" modification, a specialized neural network evaluates the overall quality of a facial image. The network is trained to minimize recognition errors, and the output of the check is a "confidence"
score ranging from 0 (lowest quality) to 1 (highest quality).
Note that the neural network was optimized not for human-perceived image quality or ISO standards, but for minimizing recognition errors. Therefore, the "confidence"
value may be difficult to interpret directly.
This modification is recommended only in scenarios where end-user feedback is not required. For cases such as remote identification or access control, the core
modification is typically preferred.
- 0.01 – Basic filter. Only the lowest-quality faces are discarded. Recommended for most cases where it’s important not to lose faces.
- 0.1 – Stricter option. Suitable for systems where a small loss of detections is acceptable in exchange for improved recognition reliability.
- 0.4 – Optimal balance for scenarios with high quality requirements and large data flow (for example, access control).
Output Context Specification
After calling the Quality Control
processing block, attributes corresponding to this block will be added to each object from the “objects”
array.
{
"quality": {
"value": {"type": "boolean"}, // is check passed?
"confidence" : {"type": "double", "minimum": 0, "maximum": 1} // 0 - the worst quality, 1 - the best quality
}
}
Example of working with the Quality Control processing block
Create a Context configuration container and specify the values
"unit_type"
,"modification"
,"version"
, of the block. To create a processing block, follow Working with Processing Block.Pass the container-context obtained after the Face Detector and Face Fitter processing blocks.
Call the evaluation processing block.
Get the result of the processing block.
- C++
- Python
- Flutter
- C#
- Java
- Kotlin
- NodeJS
- Go
auto configCtx = service->createContext();
configCtx["unit_type"] = "QUALITY_CONTROL";
pbio::ProcessingBlock blockQuality = service->createProcessingBlock(configCtx);
//------------------
// creation of face detection processing blocks, and Context container with binary image
//------------------
faceDetector(ioData);
faceFitter(ioData);
blockQuality(ioData);
bool passed = ioData["objects"][0]["quality"]["value"].getBool;
configCtx = {"unit_type": "QUALITY_CONTROL"}
blockQuality = service.create_processing_block(configCtx)
#------------------
# creation of face detection processing blocks, and Context container with binary image
#------------------
faceDetector(ioData)
faceFitter(ioData)
blockQuality(ioData)
passed = ioData["objects"][0]["quality"]["value"].get_value()
ProcessingBlock blockQuality = service.createProcessingBlock({"unit_type": "QUALITY_CONTROL"});
//------------------
// creation of face detection processing blocks, and Context container with binary image
//------------------
faceDetector.process(ioData);
faceFitter.process(ioData);
blockQuality.process(ioData);
bool passed = ioData["objects"][0]["quality"]["value"].get_value();
Dictionary<object, object> configCtx = new();
configCtx["unit_type"] = "QUALITY_CONTROL";
ProcessingBlock blockQuality = service.CreateProcessingBlock(configCtx);
//------------------
// creation of face detection processing blocks, and Context container with binary image
//------------------
faceDetector.Invoke(ioData);
faceFitter.Invoke(ioData);
blockQuality.Invoke(ioData);
bool passed = ioData["objects"][0]["quality"]["value"].GetBool();
Context configCtx = service.createContext();
configCtx.get("unit_type").setString("QUALITY_CONTROL");
ProcessingBlock blockQuality = service.createProcessingBlock(configCtx);
//------------------
// creation of face detection processing blocks, and Context container with binary image
//------------------
faceDetector.process(ioData);
faceFitter.process(ioData);
blockQuality.process(ioData);
boolean passed = ioData.get("objects").get(0).get("quality").get("value").getBool();
val configCtx = service.createContext()
configCtx["unit_type"].string = "QUALITY_CONTROL"
val blockQuality = service.createProcessingBlock(configCtx)
//------------------
// creation of face detection processing blocks, and Context container with binary image
//------------------
faceDetector.process(ioData)
faceFitter.process(ioData)
blockQuality.process(ioData)
val passed = ioData["objects"][0]["quality"]["value"].boolean
let configCtx = new facerec.Context();
configCtx.get("unit_type").value = "QUALITY_CONTROL";
let blockQuality = new facerec.ProcessingBlock(configCtx);
//------------------
// creation of face detection processing blocks, and Context container with binary image
//------------------
faceDetector.process(ioData);
faceFitter.process(ioData);
blockQuality.process(ioData);
let passed = ioData.get("objects").get(0).get("quality").get("value").value
configContext, err := facesdk.CreateContext()
context, err := configContext.GetOrInsertByKey("unit_type")
err = context.SetString("QUALITY_CONTROL")
defer configContext.Close()
blockQuality, err := service.CreateProcessingBlock(configContext)
defer blockQuality.Close()
//------------------
// creation of face detection processing blocks, and Context container with binary image
//------------------
err = faceDetector.Process(ioData);
err = faceFitter.Process(ioData);
err = blockQuality.Process(ioData);
context, err = ioData.GetByKey("objects")
context, err = context.GetByIndex(0)
context, err = context.GetByKey("quality")
context, err = context.GetByKey("value")
passed, err = context.GetBool()